jsir 2.3.1 → 2.3.3

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,7 @@ const {
11
11
  createConsole, setTips, delTips,
12
12
  getEditor, errorStr, getConfigDir,
13
13
  getFullPath, parseUniqueName, toUniqueName, isJsirFileName, toJsirFileName,
14
- getOr, getAlias, wrapperJsirText, eia
14
+ getAlias, wrapperJsirText, eia, getKeyTips, getValTips
15
15
  } = $lib;
16
16
  const _args = process.argv.slice(2).map(trim);
17
17
  const evalCode = require('../deps/evalCode')
@@ -28,6 +28,7 @@ const _libDataDir = getLibDataDir()
28
28
  const _fileWatcherMap = {}
29
29
  const _types = setting.fileType
30
30
  const console = createConsole();
31
+ const room = require('../deps/room');
31
32
 
32
33
  let lastFilterArg = '';
33
34
  let _cmdMapFile = setting.name + 'CmdMap.json'
@@ -138,7 +139,7 @@ function getFileOpenExe(fileName) {
138
139
 
139
140
  function checkWorkspaces() {
140
141
  let localWorkspace = _libDataDir + '/local';
141
- global.$workspaceMap = {
142
+ setting.workspaceMap = {
142
143
  'local': localWorkspace
143
144
  }
144
145
  mkdir(localWorkspace)
@@ -149,15 +150,15 @@ function checkWorkspaces() {
149
150
  continue
150
151
  }
151
152
  if (fs.existsSync(workspace)) {
152
- $workspaceMap[space] = workspace;
153
+ setting.workspaceMap[space] = workspace;
153
154
  }
154
155
  }
155
- arrayDataFile(_workspaceConfigFile, () => Object.values($workspaceMap))
156
- initWorkspace()
156
+ arrayDataFile(_workspaceConfigFile, () => Object.values(setting.workspaceMap))
157
157
  }
158
158
 
159
159
  async function start() {
160
160
  checkWorkspaces()
161
+ initWorkspace()
161
162
 
162
163
  lastFilterArg = getConfig("lastFilterArg");
163
164
  _cmdMap = objDataFile(_cmdMapFile);
@@ -288,7 +289,7 @@ async function nextLine(callback, promptStr, hidden) {
288
289
  }
289
290
 
290
291
  function defaultPromptStr(showKey = false) {
291
- let tips = [getTipStr(showKey), $defaultSpace].filter(i => i).join(':') + `> `;
292
+ let tips = [getTipStr(showKey), setting.defaultSpace].filter(i => i).join(':') + `> `;
292
293
  _isTipsDoneShowKey = showKey;
293
294
  return tips;
294
295
  }
@@ -345,28 +346,7 @@ function initRl(callback, promptStr, hidden) {
345
346
  }
346
347
 
347
348
  function getTipStr(showKey = false) {
348
- let items = [];
349
- for (let key of Object.keys(global.$tips)) {
350
- let val = global.$tips[key].map(i => {
351
- let item = trim(i)
352
- if (item.indexOf(',') !== -1) {
353
- item = `[${item}]`;
354
- }
355
- return item;
356
- }).join("|");
357
- key = trim(key)
358
-
359
- let item
360
- if (!showKey && vl(val)) {
361
- item = val;
362
- } else if (vl(key)) {
363
- item = key
364
- }
365
- if (item) {
366
- items.push(item)
367
- }
368
- }
369
- let tips = items.join(',')
349
+ let tips = showKey ? getKeyTips().join(","):getValTips().join(",")
370
350
  if (!showKey && !_isTipsDoneShowKey && tips.length > 64) {
371
351
  return '...'
372
352
  }
@@ -380,6 +360,10 @@ function closeRl() {
380
360
  }
381
361
 
382
362
  function _nextLine(callback, promptStr, hidden, resolve, end, isText) {
363
+ if (!setting.enableNextLine) {
364
+ console.$log(warnStr("[warn]"), "NextLine Disabled");
365
+ return
366
+ }
383
367
  end = trim(end)
384
368
  if (!_haveWrapperInput) {
385
369
  return
@@ -872,14 +856,14 @@ function help(filterFn) {
872
856
  }
873
857
 
874
858
  function delTipsByIndex(idxs) {
875
- let keys = Object.keys($tips)
859
+ let keys = Object.keys(setting.tips)
876
860
  let indexKeyMap = {}
877
861
  for(let i = 1;i<=keys.length;i++) {
878
862
  indexKeyMap[i] = keys[i - 1]
879
863
  }
880
864
  let params = []
881
865
  for (const index of idxs) {
882
- if ($tips.hasOwnProperty(index)) {
866
+ if (setting.tips.hasOwnProperty(index)) {
883
867
  params.push(index)
884
868
  } else if (indexKeyMap[Number(index)]) {
885
869
  params.push(indexKeyMap[Number(index)])
@@ -895,7 +879,7 @@ function delTipsByIndex(idxs) {
895
879
  }
896
880
 
897
881
  function getPackageVersion(space, name) {
898
- let moduleDir = $workspaceMap[space] + '/node_modules'
882
+ let moduleDir = setting.workspaceMap[space] + '/node_modules'
899
883
  let dir = moduleDir + "/" + name;
900
884
  if (fs.existsSync(dir) && fs.existsSync(dir + '/package.json')) {
901
885
  return require(dir + '/package.json').version;
@@ -908,12 +892,12 @@ function copyToSpace(uniqueName) {
908
892
  let path = getFullPath(uniqueName)
909
893
  let text = String(fs.readFileSync(path))
910
894
  let fileName = parseUniqueName(uniqueName)[1];
911
- let targetPath = $workspaceMap[$defaultSpace] + '/' + fileName;
895
+ let targetPath = setting.workspaceMap[setting.defaultSpace] + '/' + fileName;
912
896
  if (fs.existsSync(targetPath)) {
913
- console.warn(`${fileName} already exist in ${$defaultSpace}`)
897
+ console.warn(`${fileName} already exist in ${setting.defaultSpace}`)
914
898
  } else {
915
899
  fs.writeFileSync(targetPath, text)
916
- console.info(`get ${uniqueName} to ${$defaultSpace}`)
900
+ console.info(`get ${uniqueName} to ${setting.defaultSpace}`)
917
901
  }
918
902
  }
919
903
 
@@ -1076,7 +1060,7 @@ const keywordDef = {
1076
1060
  } else {
1077
1061
  let pair = parseUniqueName(uniqueName)
1078
1062
  let newName =trim(args.slice(1).join(" "))
1079
- rename($workspaceMap[pair[0]], pair[1], toJsirFileName(newName), true)
1063
+ rename(setting.workspaceMap[pair[0]], pair[1], toJsirFileName(newName), true)
1080
1064
  }
1081
1065
  },
1082
1066
  args: {
@@ -1165,8 +1149,8 @@ const keywordDef = {
1165
1149
  return;
1166
1150
  }
1167
1151
 
1168
- let aFiles = fs.readdirSync($workspaceMap[aSpace]).filter(isJsirFileName)
1169
- let bFiles = fs.readdirSync($workspaceMap[bSpace]).filter(isJsirFileName)
1152
+ let aFiles = fs.readdirSync(setting.workspaceMap[aSpace]).filter(isJsirFileName)
1153
+ let bFiles = fs.readdirSync(setting.workspaceMap[bSpace]).filter(isJsirFileName)
1170
1154
 
1171
1155
  let result = compareMode[mode](aSpace, bSpace, aFiles, bFiles);
1172
1156
  resetCmdMap(arrayToCmdMap(result))
@@ -1208,8 +1192,8 @@ const keywordDef = {
1208
1192
  Object.keys(packages).forEach(key => {
1209
1193
  let info = packages[key];
1210
1194
  info.version = getPackageVersion(info.space, info.name)
1211
- if (global.$packages.hasOwnProperty(info.name)) {
1212
- info.loaded = global.$packages[info.name]
1195
+ if (setting.packages.hasOwnProperty(info.name)) {
1196
+ info.loaded = setting.packages[info.name]
1213
1197
  }
1214
1198
  if (info.loaded && info.loaded !== info.space) {
1215
1199
  let version = getPackageVersion(info.loaded, info.name)
@@ -1252,14 +1236,14 @@ const keywordDef = {
1252
1236
  let newWorkspace = args.join(' ')
1253
1237
  let isSwitch = false
1254
1238
  if (newWorkspace) {
1255
- if ($workspaceMap[newWorkspace]) {
1239
+ if (setting.workspaceMap[newWorkspace]) {
1256
1240
  isSwitch = true;
1257
1241
  initWorkspace(newWorkspace)
1258
1242
  } else if (newWorkspace.startsWith('-')) {
1259
1243
  let name = newWorkspace.replace(/^-\s*/, '');
1260
1244
  arrayDataFile(_workspaceConfigFile, arr => arr.filter(path => name !== getSpaceFromDir(path)))
1261
1245
  } else if (newWorkspace.startsWith("/")) {
1262
- let workspaces = Object.values($workspaceMap)
1246
+ let workspaces = Object.values(setting.workspaceMap)
1263
1247
  workspaces.push(newWorkspace)
1264
1248
  arrayDataFile(_workspaceConfigFile, () => workspaces)
1265
1249
  } else {
@@ -1267,10 +1251,11 @@ const keywordDef = {
1267
1251
  }
1268
1252
  }
1269
1253
  checkWorkspaces()
1270
- let workspaces = Object.values($workspaceMap)
1254
+ initWorkspace(setting.defaultSpace)
1255
+ let workspaces = Object.values(setting.workspaceMap)
1271
1256
  let items = workspaces.map((path, index) => {
1272
1257
  return {
1273
- name: (getSpaceFromDir(path) === $defaultSpace ? '*':' ') + getSpaceFromDir(path),
1258
+ name: (getSpaceFromDir(path) === setting.defaultSpace ? '*':' ') + getSpaceFromDir(path),
1274
1259
  path
1275
1260
  }
1276
1261
  })
@@ -1344,6 +1329,7 @@ const keywordDef = {
1344
1329
  quit: {
1345
1330
  comment: 'Exit',
1346
1331
  exeFn: (args) => {
1332
+ room.offRoom();
1347
1333
  delTips();
1348
1334
  console.log(infoStr("Bye!"));
1349
1335
  _noAppendNextLine = true;
@@ -1356,6 +1342,8 @@ const keywordDef = {
1356
1342
  _noAppendNextLine = false
1357
1343
  resetCmdMap()
1358
1344
  console.log(warnStr(`(${setting.name} ${packageJson.version}) You can start with .help, use * to expand context.`))
1345
+ room.onRoom()
1346
+ nextLine()
1359
1347
  },
1360
1348
  short: 'p'
1361
1349
  },
@@ -1370,7 +1358,7 @@ const keywordDef = {
1370
1358
  if (args.length > 1) {
1371
1359
  _args = args.slice(1)
1372
1360
  }
1373
- await eia(`cd "${$workspaceMap[global.$defaultSpace]}";${cmd}`, _args, true)
1361
+ await eia(`cd "${setting.workspaceMap[setting.defaultSpace]}";${cmd}`, _args, true)
1374
1362
  },
1375
1363
  short: 'E'
1376
1364
  },
@@ -1389,12 +1377,12 @@ const keywordDef = {
1389
1377
  }
1390
1378
  }
1391
1379
  let suffix = 'log'
1392
- let cmdStr = 'less -R'
1380
+ let cmdStr = 'less -R +G'
1393
1381
  if (type.indexOf("e") !== -1) {
1394
1382
  suffix = 'error'
1395
1383
  }
1396
1384
  if (type.indexOf("t") !== -1) {
1397
- cmdStr = 'tail -n 50 -f'
1385
+ cmdStr = 'less -R +F'
1398
1386
  }
1399
1387
  path = path + '.' + suffix;
1400
1388
  if (!fs.existsSync(path)) {
@@ -1623,13 +1611,13 @@ function getJsirFileSuffix(name) {
1623
1611
  function initWorkspace(space) {
1624
1612
  let current = getConfig("workspace");
1625
1613
  let workspace = space || current || 'local'
1626
- if (!$workspaceMap[current]) {
1614
+ if (!setting.workspaceMap[workspace]) {
1627
1615
  workspace = 'local'
1628
1616
  }
1629
- if (workspace !== current) {
1617
+ if (workspace !== current && _noAppendNextLine) {
1630
1618
  setConfig("workspace", workspace)
1631
1619
  }
1632
- global.$defaultSpace = workspace;
1620
+ setting.defaultSpace = workspace;
1633
1621
  }
1634
1622
 
1635
1623
  function filterCmdAndList(arg) {
@@ -1655,10 +1643,10 @@ function filterCmd(arg){
1655
1643
  let spaceName
1656
1644
  if (arg.indexOf('/') !== -1) {
1657
1645
  let index = arg.indexOf('/');
1658
- spaceName = trim(arg.substring(0, index)) || $defaultSpace
1646
+ spaceName = trim(arg.substring(0, index)) || setting.defaultSpace
1659
1647
  arg = trim(arg.substring(index + 1))
1660
1648
  }
1661
- for (let workspace of Object.values($workspaceMap)) {
1649
+ for (let workspace of Object.values(setting.workspaceMap)) {
1662
1650
  let spaceTmp = getSpaceFromDir(workspace);
1663
1651
  if (!spaceName || spaceName === spaceTmp) {
1664
1652
  cmds.push(..._filterCmd(workspace, arg));
@@ -2063,7 +2051,7 @@ async function evalText($text = '', $cmdName = '', $args = []) {
2063
2051
  let pair = parseUniqueName($cmdName)
2064
2052
  currSpace = pair[0]
2065
2053
  } else {
2066
- currSpace = $defaultSpace
2054
+ currSpace = setting.defaultSpace
2067
2055
  }
2068
2056
  let $requires = async (...matchItems) => {
2069
2057
  let result = []
@@ -2080,22 +2068,13 @@ async function evalText($text = '', $cmdName = '', $args = []) {
2080
2068
  $require, $requires,
2081
2069
  nextLine, nextText,
2082
2070
  wrapperInput, filterCmd,
2083
- currSpace,
2071
+ currSpace, setting.defaultSpace, setting.workspaceMap,
2084
2072
  $homeDir, $lib, _cmdMap);
2085
2073
  }
2086
2074
 
2087
- function clearFileLock() {
2088
- let fileLockMap = getOr(global, `$fileLock`, {})
2089
- for (let file of Object.keys(fileLockMap)) {
2090
- fp.rmdir(file)
2091
- delete fileLockMap[file]
2092
- }
2093
- }
2094
-
2095
2075
  function sigExit() {
2096
2076
  if (_noAppendNextLine) {
2097
2077
  delTips();
2098
- clearFileLock();
2099
2078
  process.exit(0);
2100
2079
  } else {
2101
2080
  nextLine();
@@ -2104,15 +2083,12 @@ function sigExit() {
2104
2083
 
2105
2084
  process.on('uncaughtException',function(err){
2106
2085
  console.$error('uncaughtException', err)
2107
- _noAppendNextLine || nextLine()
2108
2086
  })
2109
2087
  process.on('unhandledRejection',function(err){
2110
2088
  console.$error('unhandledRejection', err)
2111
- _noAppendNextLine || nextLine()
2112
2089
  })
2113
2090
  process.on('rejectionHandled',function(err){
2114
2091
  console.$error('rejectionHandled', err)
2115
- _noAppendNextLine || nextLine()
2116
2092
  })
2117
2093
  process.on('SIGINT', sigExit);
2118
2094
  process.on('SIGTERM', sigExit);
package/deps/evalCode.js CHANGED
@@ -3,7 +3,7 @@ module.exports = async ($text = '', $cmdName = '', $args = [],
3
3
  $require, $requires,
4
4
  $nextLine, $nextText,
5
5
  $enter, $filterCmd,
6
- $currentSpace,
6
+ $currentSpace, $defaultSpace, $workspaceMap,
7
7
  $homeDir, $lib,
8
8
  $cmdMap) => {
9
9
  const $defArgs = () => $args;
@@ -26,13 +26,15 @@ module.exports = async ($text = '', $cmdName = '', $args = [],
26
26
  const $errorTag = $lib.errorTag;
27
27
 
28
28
  const $context = {
29
+ $defArgs,
29
30
  $data, $config, $file,
30
31
  $require, $requires, $import,
31
32
  $text, $cmdName, $args,
32
33
  $nextLine, $nextText,
33
34
  $setTips, $delTips,
34
35
  $enter, $filterCmd,
35
- $currentSpace, $homeDir, $lib, $cmdMap,
36
+ $currentSpace, $defaultSpace, $workspaceMap,
37
+ $homeDir, $lib, $cmdMap
36
38
  }
37
39
  let console = $lib.createConsole($cmdName);
38
40
  return await eval(`(async ()=>{${$text};
package/deps/room.js ADDED
@@ -0,0 +1,232 @@
1
+ const os = require('os');
2
+ const {fileJson, fileLock, vl, createConsole, getKeyTips, getValTips} = require('./util');
3
+ const roomDataFile = "jsirRoom.json"
4
+ const console = createConsole();
5
+ const ping = require("ping");
6
+ const net = require("net");
7
+ const setting = require('../deps/setting')
8
+
9
+ function isPidAlive(pid) {
10
+ try {
11
+ process.kill(Number(pid), 0); // 信号 0 不会实际发送,但会检查进程是否存在
12
+ return true; // PID 存在
13
+ } catch (err) {
14
+ if (err.code === 'ESRCH') {
15
+ return false; // 进程不存在
16
+ }
17
+ console.$error(`check isPidAlive ${pid} failed`, err)
18
+ return false;
19
+ }
20
+ }
21
+
22
+ function onRoom() {
23
+ return
24
+ if (!setting.roomTid[0]) {
25
+ _onRoom();
26
+ }
27
+ }
28
+
29
+ function _onRoom() {
30
+ setting.roomTid[0] = setTimeout(async () => {
31
+ try {
32
+ await _initRoom();
33
+ } catch (e) {
34
+ console.$error('initRoom', e)
35
+ }
36
+ if (setting.roomTid[0]) {
37
+ _onRoom();
38
+ }
39
+ }, 1000)
40
+ }
41
+
42
+ function offRoom() {
43
+ return;
44
+ if (setting.roomTid[0]) {
45
+ clearTimeout(setting.roomTid[0])
46
+ setting.roomTid[0] = null
47
+ }
48
+ }
49
+
50
+ async function getTailScaleNodes() {
51
+ console.$log("getNodes")
52
+ let ips = getLocalIPs().ipv4;
53
+ ips = ips.filter(i => i.startsWith("100.")); // tailScale ip前缀
54
+ let nodes = []
55
+ for (let ip of ips) {
56
+ let netIps = await scanLocalNetwork(ip);
57
+ netIps = netIps.filter(i => i !== ip)
58
+ nodes.push(...netIps)
59
+ }
60
+ return await sshEnables(nodes);
61
+ }
62
+
63
+ async function initRoomInfo() {
64
+ await fileLock(roomDataFile + "getNodes", async () => {
65
+ let nodes = await getTailScaleNodes();
66
+ await fileJson(roomDataFile, async room => {
67
+ // 设置roomName
68
+ let name = os.hostname();
69
+ if (!vl(room.name) || room.name !== name) {
70
+ room.name = name;
71
+ console.$log("set roomName", name)
72
+ }
73
+
74
+ room.nodes = nodes;
75
+ room.lastUpdateTime = Date.now()
76
+ console.$log("init room", room.name)
77
+ })
78
+ }, false)
79
+ }
80
+
81
+ async function _initRoom() {
82
+ let roomUpdateTouchTime = false;
83
+ await fileJson(roomDataFile, async room => {
84
+ await initRoomJsir(room)
85
+ if (!vl(room.lastUpdateTime) || (Date.now() - room.lastUpdateTime) > 9000) {
86
+ roomUpdateTouchTime = true;
87
+ }
88
+ })
89
+
90
+ if (roomUpdateTouchTime) {
91
+ initRoomInfo();
92
+ }
93
+ }
94
+
95
+ async function getEventLoopDelay() {
96
+ const start = process.hrtime.bigint();
97
+ const delay = 1; // 用于测量的短暂延迟(单位:毫秒)
98
+ const timeoutPromise = new Promise(resolve => setTimeout(resolve, delay));
99
+ return await timeoutPromise.then(() => {
100
+ const end = process.hrtime.bigint();
101
+ return Number(Math.max(Number(end - start) / 1e4 - (delay * 1e2), 0).toFixed(0)); // 避免负值
102
+ });
103
+ }
104
+
105
+ async function initRoomJsir(room) {
106
+ room.jsir = room.jsir || {};
107
+
108
+ for (let pid of [...Object.keys(room.jsir)]) {
109
+ if (!isPidAlive(pid)) {
110
+ delete room.jsir[pid]
111
+ }
112
+ }
113
+ room.jsir[process.pid] = {
114
+ pid: process.pid,
115
+ space: setting.defaultSpace,
116
+ tips: getKeyTips().reduce((obj, key, index) => {
117
+ obj[key] = getValTips()[index];
118
+ return obj;
119
+ }, {}),
120
+ busy: await getEventLoopDelay(),
121
+ back: isRunningInBackground(),
122
+ lastUpdateTime: Date.now()
123
+ }
124
+ console.$log("init jsir", process.pid)
125
+ }
126
+
127
+ function isRunningInBackground() {
128
+ return !(process.stdout.isTTY && process.stdin.isTTY);
129
+ }
130
+
131
+ /**
132
+ * 获取本机所有内网 IP
133
+ * @returns {{ ipv4: string[], ipv6: string[] }}
134
+ */
135
+ function getLocalIPs() {
136
+ const networkInterfaces = os.networkInterfaces();
137
+
138
+ const result = {
139
+ ipv4: [],
140
+ ipv6: []
141
+ };
142
+
143
+ for (const interfaceName of Object.keys(networkInterfaces)) {
144
+ for (const netInfo of networkInterfaces[interfaceName]) {
145
+ const { family, address, internal } = netInfo;
146
+
147
+ // 只关心非回环地址
148
+ if (!internal) {
149
+ if (family === 'IPv4') {
150
+ result.ipv4.push(address);
151
+ } else if (family === 'IPv6') {
152
+ result.ipv6.push(address);
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ return result;
159
+ }
160
+
161
+ async function sshEnables(ips) {
162
+ // 检查开放 22 端口的 IP
163
+ const sshEnabledIPs = [];
164
+ const portCheckPromises = ips.map((ip) =>
165
+ checkPortOpen(ip, 22).then((isOpen) => {
166
+ if (isOpen) {
167
+ sshEnabledIPs.push(ip);
168
+ }
169
+ })
170
+ );
171
+
172
+ await Promise.all(portCheckPromises);
173
+ return sshEnabledIPs;
174
+ }
175
+
176
+ /**
177
+ * 检查指定 IP 的指定端口是否开放
178
+ * @param {string} ip - 目标 IP 地址
179
+ * @param {number} port - 目标端口
180
+ * @returns {Promise<boolean>} - 是否开放
181
+ */
182
+ function checkPortOpen(ip, port) {
183
+ return new Promise((resolve) => {
184
+ const socket = new net.Socket();
185
+ socket.setTimeout(1000); // 设置超时时间
186
+
187
+ socket
188
+ .connect(port, ip, () => {
189
+ socket.destroy(); // 成功连接后关闭 socket
190
+ resolve(true);
191
+ })
192
+ .on("error", () => {
193
+ socket.destroy(); // 遇到错误时关闭 socket
194
+ resolve(false);
195
+ })
196
+ .on("timeout", () => {
197
+ socket.destroy(); // 超时时关闭 socket
198
+ resolve(false);
199
+ });
200
+ });
201
+ }
202
+
203
+ /**
204
+ * 扫描局域网中的可访问IP
205
+ * @param {string} localIP - 本机内网IP地址
206
+ * @returns {Promise<string[]>} - 可访问的IP地址列表
207
+ */
208
+ async function scanLocalNetwork(localIP) {
209
+ const subnet = localIP.substring(0, localIP.lastIndexOf('.') + 1); // 例如 "192.168.1."
210
+ const reachableIPs = [];
211
+
212
+ // 扫描 1 到 254 的 IP
213
+ const promises = [];
214
+ for (let i = 1; i <= 254; i++) {
215
+ const ip = `${subnet}${i}`;
216
+ promises.push(
217
+ ping.promise.probe(ip, { timeout: 1 }).then((res) => {
218
+ if (res.alive) {
219
+ reachableIPs.push(ip);
220
+ }
221
+ })
222
+ );
223
+ }
224
+
225
+ await Promise.all(promises);
226
+ return reachableIPs;
227
+ }
228
+
229
+ module.exports = {
230
+ onRoom,
231
+ offRoom
232
+ }
package/deps/setting.js CHANGED
@@ -17,5 +17,11 @@ module.exports = {
17
17
  exeKey,
18
18
  initKey,
19
19
  fileKey,
20
- defaultType
20
+ defaultType,
21
+ packages: {},
22
+ tips: {},
23
+ defaultSpace: 'local',
24
+ workspaceMap: {},
25
+ enableNextLine: true,
26
+ roomTid: []
21
27
  }
package/deps/util.js CHANGED
@@ -16,11 +16,7 @@ const _types = setting.fileType
16
16
  const _typeKeys = Object.keys(_types)
17
17
 
18
18
  global.$newInput = false
19
- global.$workspaceMap = {}
20
- global.$defaultSpace = 'local'
21
19
  global.$newError = false
22
- global.$tips = {}
23
- global.$packages = {}
24
20
 
25
21
  let libDataDir;
26
22
  let lockDir;
@@ -256,7 +252,7 @@ function getFullPath(name) {
256
252
  }
257
253
  let uniqueName = toUniqueName(name)
258
254
  let pair = parseUniqueName(uniqueName)
259
- let space = $workspaceMap[pair[0]]
255
+ let space = setting.workspaceMap[pair[0]]
260
256
  if (!space) {
261
257
  return ''
262
258
  }
@@ -282,10 +278,10 @@ function toUniqueName(name, space) {
282
278
  } else if (name.indexOf('/') !== -1) {
283
279
  uniqueName = name;
284
280
  } else {
285
- uniqueName = (space || $defaultSpace) + '/' + name;
281
+ uniqueName = (space || setting.defaultSpace) + '/' + name;
286
282
  }
287
283
  let pair = parseUniqueName(uniqueName);
288
- return (pair[0] || space || $defaultSpace) + '/' + toJsirFileName(pair[1])
284
+ return (pair[0] || space || setting.defaultSpace) + '/' + toJsirFileName(pair[1])
289
285
  }
290
286
 
291
287
  function toJsirFileName(name) {
@@ -700,7 +696,7 @@ function iarrayDataFile(fileName, fn, fmt) {
700
696
  }
701
697
  function getInitName(fileName) {
702
698
  fileName = trim(fileName)
703
- let homeDir = global.$workspaceMap[$defaultSpace]
699
+ let homeDir = setting.workspaceMap[setting.defaultSpace]
704
700
  if (!fileName.startsWith("/")) {
705
701
  fileName = setting.initKey + ' ' + fileName.replace(new RegExp(`^${setting.initKey}\\s+`), '')
706
702
  if (!/\..+/.test(fileName)) {
@@ -762,14 +758,14 @@ async function fileExist(path) {
762
758
  }
763
759
  }
764
760
 
765
- async function fileJson(key, fn, fmt = true) {
761
+ async function fileJson(key, fn, fmt = true, safeMs = 49000) {
766
762
  `
767
763
  多进程安全文件读写
768
764
  `
769
765
  let dataDir = getLibDataDir() + "/data"
770
766
  let fileName = trim(key)
771
767
  let path = dataDir + "/" + fileName;
772
- let homeDir = global.$workspaceMap[$defaultSpace]
768
+ let homeDir = setting.workspaceMap[setting.defaultSpace]
773
769
  let isInit = fileName.startsWith(setting.initKey);
774
770
  if (isInit) {
775
771
  path = homeDir + '/' + toJsirFileName(fileName);
@@ -793,12 +789,12 @@ async function fileJson(key, fn, fmt = true) {
793
789
  result = val
794
790
  }
795
791
  await fp.writeFile(path, prefixStr + JSON.stringify(result, null, fmt ? 2:null))
796
- });
792
+ }, true, safeMs);
797
793
  return result;
798
794
  }
799
795
 
800
796
  function setModulePaths(space) {
801
- let moduleDir = $workspaceMap[space] + '/node_modules'
797
+ let moduleDir = setting.workspaceMap[space] + '/node_modules'
802
798
  if (module.paths.indexOf(moduleDir) === -1) {
803
799
  module.paths.splice(0, module.paths.length)
804
800
  module.paths.push(moduleDir)
@@ -808,13 +804,13 @@ function setModulePaths(space) {
808
804
  }
809
805
 
810
806
  function enrichPackages(moduleName, space) {
811
- if (!global.$packages.hasOwnProperty(moduleName)) {
812
- global.$packages[moduleName] = space
807
+ if (!setting.packages.hasOwnProperty(moduleName)) {
808
+ setting.packages[moduleName] = space
813
809
  }
814
810
  }
815
811
 
816
812
  function requireG(moduleName, space){
817
- space = space || global.$defaultSpace;
813
+ space = space || setting.defaultSpace;
818
814
  let moduleDir = setModulePaths(space);
819
815
  if (module.paths.indexOf(moduleDir) === -1) {
820
816
  module.paths.splice(0, module.paths.length)
@@ -836,7 +832,7 @@ function requireG(moduleName, space){
836
832
  }
837
833
 
838
834
  async function importG(moduleName, space) {
839
- space = space || global.$defaultSpace;
835
+ space = space || setting.defaultSpace;
840
836
  let moduleDir = setModulePaths(space);
841
837
  try {
842
838
  let result = await import(moduleName);
@@ -1041,7 +1037,7 @@ function getLockDir() {
1041
1037
  return lockDir;
1042
1038
  }
1043
1039
 
1044
- function getLockFile(key) {
1040
+ function getLockKeyDir(key) {
1045
1041
  if (!key) {
1046
1042
  throw "invalid args"
1047
1043
  }
@@ -1050,36 +1046,110 @@ function getLockFile(key) {
1050
1046
  return lockDir + "/" + key;
1051
1047
  }
1052
1048
 
1053
- async function _fileLock(key, fn) {
1049
+ /**
1050
+ * 从目录中读取过期时间戳
1051
+ * @param {string} lockDir - 锁目录路径
1052
+ * @returns {Promise<number | null>} 如果目录下存在过期时间戳文件,则返回其数值;否则返回 null
1053
+ */
1054
+ async function readExpireTimestamp(lockDir) {
1055
+ try {
1056
+ const files = await fp.readdir(lockDir);
1057
+ // 假设我们只会创建一个文件,其文件名包含过期时间,如 `expire-1672531200000`
1058
+ const expireFile = files.find(name => name.startsWith('expire-'));
1059
+ if (!expireFile) {
1060
+ return null;
1061
+ }
1062
+ const timestamp = parseInt(expireFile.replace('expire-', ''), 10);
1063
+ return isNaN(timestamp) ? null : timestamp;
1064
+ } catch (err) {
1065
+ // 读取目录出错(可能不存在?)直接返回 null
1066
+ return null;
1067
+ }
1068
+ }
1069
+
1070
+ async function _fileLock(key, fn, expireMs = 49000) {
1054
1071
  `
1055
1072
  文件锁,返回true/false,
1056
1073
  如果没锁住则不执行fn
1057
1074
  `
1058
- if (!key || !fn) {
1059
- throw new Error('invalid args')
1075
+ if (!key || typeof fn !== 'function') {
1076
+ throw new Error('invalid arguments');
1060
1077
  }
1061
- let file = getLockFile(key)
1078
+
1079
+ let lockKeyDir = getLockKeyDir(key)
1080
+ const now = Date.now();
1081
+ const expireAt = expireMs > 0 ? now + expireMs : 0;
1082
+
1083
+ // 1. 尝试判断锁目录是否已存在
1084
+ let lockExists = false;
1062
1085
  try {
1063
- await fp.mkdir(file);
1064
- } catch (e) {
1086
+ await fp.access(lockKeyDir); // 若存在则不抛错
1087
+ lockExists = true;
1088
+ } catch (_) {
1089
+ // 不存在时会抛ENOENT错误,说明还没有锁
1090
+ lockExists = false;
1091
+ }
1092
+
1093
+ if (lockExists) {
1094
+ // 目录存在时,读取文件名中的过期时间戳
1095
+ const storedExpireAt = await readExpireTimestamp(lockKeyDir);
1096
+ if (storedExpireAt === null) {
1097
+ // 理论上不会出现没有“expire-xxxx”文件的情况,除非中途被手动改动
1098
+ // 可以选择强制删除目录,或者直接返回 false
1099
+ return false;
1100
+ }
1101
+ if (storedExpireAt === 0) {
1102
+ // 说明当时设置的是永不过期
1103
+ return false;
1104
+ }
1105
+ if (storedExpireAt > now) {
1106
+ // 说明锁尚未过期
1107
+ return false;
1108
+ }
1109
+ // 如果过期了,则清理原目录,以便重新加锁
1110
+ try {
1111
+ await fp.rm(lockKeyDir, { recursive: true, force: true });
1112
+ } catch (err) {
1113
+ // 删除失败,可能是权限问题;这里直接返回 false 或者抛错
1114
+ return false;
1115
+ }
1116
+ }
1117
+
1118
+ // 2. 创建锁目录
1119
+ try {
1120
+ await fp.mkdir(lockKeyDir, { recursive: false });
1121
+ } catch (err) {
1122
+ // 如果 mkdir 依旧失败,说明在这段时间里又被别人抢先创建了
1123
+ return false;
1124
+ }
1125
+
1126
+ // 3. 在锁目录下创建一个文件,文件名带“过期时间戳”
1127
+ // expireAt === 0 表示永不过期
1128
+ const expireFilename = `expire-${expireAt}`;
1129
+ const expireFilePath = path.join(lockKeyDir, expireFilename);
1130
+ try {
1131
+ await fp.writeFile(expireFilePath, '');
1132
+ } catch (err) {
1133
+ // 无法写文件就释放锁并抛错
1134
+ try {
1135
+ await fp.rm(lockKeyDir, { recursive: true, force: true });
1136
+ } catch (_) {}
1065
1137
  return false;
1066
1138
  }
1067
- let fileLockMap = getOr(global, `$fileLock`, {})
1068
- fileLockMap[file] = true;
1139
+
1140
+ // 4. 成功加锁后执行 fn
1069
1141
  try {
1070
1142
  await fn();
1071
1143
  return true;
1072
- } catch (e) {
1073
- throw e;
1074
1144
  } finally {
1145
+ // 5. 执行完毕后,手动释放锁目录(也可选择保留到过期自动失效,但一般都建议主动释放)
1075
1146
  try {
1076
- await fp.rmdir(file)
1077
- delete fileLockMap[file]
1078
- } catch (_){}
1147
+ await fp.rm(lockKeyDir, { recursive: true, force: true });
1148
+ } catch (_) {}
1079
1149
  }
1080
1150
  }
1081
1151
 
1082
- async function fileLock(key, fn, wait = true) {
1152
+ async function fileLock(key, fn, wait = true, expireMs = 49000) {
1083
1153
  `
1084
1154
  文件锁, 默认一直等待,直到加锁成功执行fn
1085
1155
  wait = false, 加锁失败则不执行fn
@@ -1087,13 +1157,13 @@ async function fileLock(key, fn, wait = true) {
1087
1157
  `
1088
1158
  if (wait) {
1089
1159
  while (true) {
1090
- if (await _fileLock(key, fn)) {
1160
+ if (await _fileLock(key, fn, expireMs)) {
1091
1161
  break;
1092
1162
  }
1093
- await sleep(49);
1163
+ await sleep(9);
1094
1164
  }
1095
1165
  } else {
1096
- await _fileLock(key, fn);
1166
+ await _fileLock(key, fn, expireMs);
1097
1167
  }
1098
1168
  }
1099
1169
 
@@ -1116,19 +1186,19 @@ function setTips(key, value, onRm) {
1116
1186
  if (!vl(key) || key.indexOf(",") !== -1) {
1117
1187
  throw "invalid tip key";
1118
1188
  }
1119
- getOr(global.$tips, key, []).push(value);
1189
+ getOr(setting.tips, key, []).push(value);
1120
1190
  if (onRm) {
1121
1191
  getOr(_tipsOnRm, key, []).push(onRm)
1122
1192
  }
1123
1193
  }
1124
1194
 
1125
1195
  function delTips(...keys) {
1126
- for (let key of Object.keys($tips)) {
1196
+ for (let key of Object.keys(setting.tips)) {
1127
1197
  if (keys.length === 0) {
1128
- delete $tips[key]
1198
+ delete setting.tips[key]
1129
1199
  tipsOnRmCallback(key)
1130
1200
  } else if (keys.indexOf(key) !== -1) {
1131
- delete $tips[key]
1201
+ delete setting.tips[key]
1132
1202
  tipsOnRmCallback(key)
1133
1203
  }
1134
1204
  }
@@ -1186,7 +1256,7 @@ function fileCleaner(path, maxChars) {
1186
1256
  `
1187
1257
  cleanFiles[path] = Date.now() + (1000 * 60)
1188
1258
  let flag = "sys"
1189
- if (!$tips[flag]) {
1259
+ if (!setting.tips.hasOwnProperty(flag)) {
1190
1260
  timer(flag, 1000 * 9, () => {
1191
1261
  for (let aPath of Object.keys(cleanFiles)) {
1192
1262
  if (cleanFiles[aPath] < Date.now()) {
@@ -1399,14 +1469,17 @@ async function eia(cmd, args = [], shell = false) {
1399
1469
  `
1400
1470
  当前进程不会卡住,输入输出由cmd进程持有
1401
1471
  `
1472
+ setting.enableNextLine = false;
1402
1473
  let child = spawn(cmd, args, {stdio:"inherit", shell});
1403
1474
  return new Promise((resolve, reject) => {
1404
1475
  // 监听子进程的关闭事件
1405
1476
  child.on('close', (code) => {
1477
+ setting.enableNextLine = true;
1406
1478
  resolve(code);
1407
1479
  });
1408
1480
  // 可选:监听子进程的错误事件
1409
1481
  child.on('error', (err) => {
1482
+ setting.enableNextLine = true;
1410
1483
  reject(err)
1411
1484
  });
1412
1485
  })
@@ -1864,6 +1937,29 @@ end tell
1864
1937
  }
1865
1938
  }
1866
1939
 
1940
+ function getKeyTips() {
1941
+ let items = [];
1942
+ for (let key of Object.keys(setting.tips)) {
1943
+ items.push(trim(key))
1944
+ }
1945
+ return items
1946
+ }
1947
+
1948
+ function getValTips() {
1949
+ let items = [];
1950
+ for (let key of Object.keys(setting.tips)) {
1951
+ let val = setting.tips[key].map(i => {
1952
+ let item = trim(i)
1953
+ if (item.indexOf(',') !== -1) {
1954
+ item = `[${item}]`;
1955
+ }
1956
+ return item;
1957
+ }).join("|");
1958
+ items.push(val)
1959
+ }
1960
+ return items
1961
+ }
1962
+
1867
1963
  module.exports = {
1868
1964
  wrapperJsirText,
1869
1965
  run,
@@ -1964,5 +2060,7 @@ module.exports = {
1964
2060
  createDirs,
1965
2061
  getTempDir,
1966
2062
  terminalRun,
1967
- eia
2063
+ eia,
2064
+ getKeyTips,
2065
+ getValTips
1968
2066
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsir",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "JavaScript Script Management Tool",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,6 +22,7 @@
22
22
  "chokidar": "^3.5.2",
23
23
  "console.table": "^0.10.0",
24
24
  "dayjs": "^1.10.4",
25
- "pad": "^3.2.0"
25
+ "pad": "^3.2.0",
26
+ "ping": "^0.4.4"
26
27
  }
27
28
  }