xshell 1.0.43 → 1.0.45

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/file.js CHANGED
@@ -234,11 +234,11 @@ export async function fmove(src, dst, { overwrite = false, print = true } = {})
234
234
  - overwrite?: `true` 默认覆盖(不检查效率更高) better performance without check */
235
235
  export async function frename(fp, fp_, { fpd, print = true, overwrite = true } = {}) {
236
236
  if (fpd) {
237
- fp = path.join(fpd, fp);
238
- fp_ = path.join(fpd, fp_);
237
+ assert(fpd.isdir);
238
+ fp = fpd + fp;
239
+ fp_ = fpd + fp_;
239
240
  }
240
- else if (!path.isAbsolute(fp) || !path.isAbsolute(fp_))
241
- throw new Error(t('fp 和 fp_ 必须是绝对路径'));
241
+ assert(path.isAbsolute(fp) && path.isAbsolute(fp_), t('fp 和 fp_ 必须是绝对路径'));
242
242
  if (print)
243
243
  console.log(t('重命名'), fp, '→', fp_);
244
244
  if (!overwrite && fexists(fp_))
@@ -256,8 +256,18 @@ export async function frename(fp, fp_, { fpd, print = true, overwrite = true } =
256
256
  export async function fmkdir(fpd, { print = true, mode, } = {}) {
257
257
  assert(path.isAbsolute(fpd), t('fpd 必须是绝对路径: ') + fpd);
258
258
  assert(fpd.isdir, t('fpd 必须以 / 结尾: ') + fpd);
259
- // CallingfsPromises.mkdir() when path is a directory that exists results in a rejection only when recursive is false.
260
- const fpd_ = (await fsp.mkdir(fpd, { recursive: true, mode }))?.replaceAll('\\', '/');
259
+ // 类似 D:/ 这样的根路径,调用 fsp.mkdir 会报错无权限
260
+ if (fpd.length === 3 &&
261
+ 'A' <= fpd[0] && fpd[0] <= 'Z' && fpd[1] === ':' && fpd[2] === '/')
262
+ if (fexists(fpd)) {
263
+ if (print)
264
+ console.log(t('已存在文件夹'), fpd);
265
+ return fpd;
266
+ }
267
+ else
268
+ throw new Error(t('不存在且无法创建文件夹 {{fpd}}', { fpd }));
269
+ // Calling fs.promises.mkdir() when path is a directory that exists results in a rejection only when recursive is false.
270
+ const fpd_ = (await fsp.mkdir(fpd, { recursive: true, mode }))?.fpd;
261
271
  if (fpd_) {
262
272
  if (print)
263
273
  console.log(t('已创建文件夹'), fpd);
package/net.browser.d.ts CHANGED
@@ -125,10 +125,13 @@ export interface Message<TData extends any[] = any[]> {
125
125
  bins?: number[];
126
126
  }
127
127
  /** 通过创建 remote 对象对 websocket rpc 进行抽象
128
- 调用方使用 remote.call 进行调用
129
- 被调方在创建 remote 对象时传入 funcs 注册处理函数,并使用 remote.handle 方法处理 websocket message
130
- 未连接时自动连接,断开后自动重连 (保证执行 send 时已连接,否则报错)
131
- 从设计上与 rpc 状态与底层连接的状态无关 (通过传入 url 创建的),底层连接断开后,只要检测到断线,会尝试重连,如果重连成功,则不影响调用的状态
128
+ 创建 remote 对象时传入 funcs 注册处理函数,使得对端能通过 (rpc message).func 调用
129
+ 使用 remote.handle 方法处理对端发来的 websocket message,对于 websocket 连接接收方需要手动绑定 websocket message 事件到 remote.handle
130
+ 使用 remote.call 进行一元 rpc
131
+ 使用 remote.send 结合 message.id 进行复杂 rpc
132
+ 创建后等到首个 remote.call 或 remote.send 时自动建立实际 websocket 连接
133
+ rpc 状态与底层连接的状态无关,如果是传入 url 创建的 remote 实例,remote.send 时检测到断线会自动建立新的 websocket 连接
134
+
132
135
  @example
133
136
  // Zero 继承自 Remote 并通过 call 实现了一些方法
134
137
  let zero = new Zero({ local: true })
@@ -145,42 +148,54 @@ export interface Message<TData extends any[] = any[]> {
145
148
 
146
149
  zero.send({ id, func: 'subscribe_stdio' }) */
147
150
  export declare class Remote {
148
- /** 是否为建立 rpc 连接的发起方 */
151
+ /** 在构造 Remote 时, this.initiator = Boolean(url || websocket)
152
+ - true: 作为 websocket 连接发起方
153
+ - false: 作为 websocket 连接接收方 */
149
154
  initiator: boolean;
150
- /** websocket url */
155
+ /** 作为 websocket 连接发起方,对端的 url 地址 */
151
156
  url?: string;
152
- /** 主动发起 websocket 连接的客户端 (构造函数传 url) 有这个属性 */
157
+ /** 作为 websocket 连接发起方有 websocket lock */
153
158
  lwebsocket?: Lock<WebSocket>;
154
- /** 通过 rpc message.func 被调用的 rpc 函数 */
159
+ /** websocket 连接发起方,接收方,都能被对端通过 (rpc message).func 调用的 rpc 函数 */
155
160
  funcs: Record<string, MessageHandler>;
156
- /** map<id, message handler>: 通过 rpc message.id 找到对应的 handler, unary rpc 接收方不需要设置 handlers, 发送方需要 */
161
+ /** map<id, message handler>: 通过 (rpc message).id 找到对应的 handler
162
+ 一元 rpc 接收方不需要设置 handlers, 发送方需要 */
157
163
  handlers: Map<number, MessageHandler>;
164
+ /** `false` 打印所有交互的 rpc messages */
158
165
  print: boolean;
159
- pconnect: Promise<any>;
160
166
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
161
167
  static parse<TData extends any[] = any[]>(buffer: Uint8Array): Message<TData>;
162
168
  static pack({ id, func, data, done, error }: Message): Uint8Array;
163
- /** 作为 rpc 发起方,可以通过传入 url 或者 websocket 定义远程 Remote
164
- 作为 rpc 接收方,可以不传 url 和 websocket 定义本地 Remote */
165
- constructor({ url, funcs, websocket, on_error }?: {
169
+ /** 作为 websocket 连接发起方,传入 url websocket,定义远程 Remote
170
+ 作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
171
+ constructor({ url, funcs, print, websocket, on_error }?: {
166
172
  url?: string;
167
173
  funcs?: Remote['funcs'];
174
+ print?: boolean;
168
175
  websocket?: WebSocket;
169
176
  on_error?(error: WebSocketConnectionError, websocket: WebSocket): void;
170
177
  });
171
178
  on_error?(error: WebSocketConnectionError, websocket: WebSocket): void;
172
- /** 幂等,保证 websocket 已连接,否则抛出异常,不需要手动调用,在其它方法中已默认自动重连
173
- 作为接收方需要传入使用的 websocket 连接,确保这个这个连接的状态 */
179
+ /** 幂等,保证 websocket 已连接,否则抛出异常
180
+ 一般情况不需要手动调用,在其它方法中会自动调用这个方法,除非需要手动建立 websocket 连接并确保成功
181
+ 连接断开后,通过传入 url 参数构造的 remote 实例会自动创建新的 websocket 连接,而通过传入 websocket 参数构造的实例只会检查连接状态,在断开时抛出异常
182
+ 作为 websocket 连接发起方,不需要传入 websocket
183
+ 作为 websocket 连接接收方,需要传入使用的 websocket 连接,确保这个这个连接的状态 */
174
184
  connect(websocket?: WebSocket): Promise<void>;
185
+ /** 作为 websocket 连接发起方手动关闭到对端的 websocket 连接 */
175
186
  disconnect(): void;
176
- /** 接收 websocket 连接的本地 remote 必传 websocket 参数;发起端选传,如果传了必须等于 this.websocket_lock.resource
187
+ /** 发送 message 到对端 remote
188
+ 作为 websocket 连接发起方,不需要传入 websocket
189
+ 作为 websocket 连接接收方,必传 websocket 参数
177
190
  发送或连接出错时自动清理 message.id 对应的 handler */
178
191
  send(message: Message, websocket?: WebSocket): Promise<void>;
179
- /** 处理接收到的 websocket message 并解析, 根据 id dispatch 到对应的 handler 进行处理
180
- 如果 message.done == true 则清理 handler
181
- 如果 handler 返回了值,则包装为 message 发送
192
+ /** 处理接收到的 websocket message 并解析, 根据 message.id message.func 分发到对应的 handler 进行处理
193
+ 如果 message.done == true 则对端指示当前 remote 可以清理 handler
194
+ 如果 handler 返回了值 (一定是数组),则包装为 message 发送
182
195
  使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
183
196
  handle(data: Uint8Array, websocket: WebSocket): Promise<void>;
184
- /** 调用 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应) */
185
- call<TReturn extends any[] = any[]>(func: string, args?: any[]): Promise<TReturn>;
197
+ /** 调用对端 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应)
198
+ 作为 websocket 连接发起方,不需要传入 websocket
199
+ 作为 websocket 连接接收方,必传 websocket 参数 */
200
+ call<TReturn extends any[] = any[]>(func: string, args?: any[], websocket?: WebSocket): Promise<TReturn>;
186
201
  }
package/net.browser.js CHANGED
@@ -235,10 +235,13 @@ export async function connect_websocket(url, { protocols, on_message, on_error,
235
235
  });
236
236
  }
237
237
  /** 通过创建 remote 对象对 websocket rpc 进行抽象
238
- 调用方使用 remote.call 进行调用
239
- 被调方在创建 remote 对象时传入 funcs 注册处理函数,并使用 remote.handle 方法处理 websocket message
240
- 未连接时自动连接,断开后自动重连 (保证执行 send 时已连接,否则报错)
241
- 从设计上与 rpc 状态与底层连接的状态无关 (通过传入 url 创建的),底层连接断开后,只要检测到断线,会尝试重连,如果重连成功,则不影响调用的状态
238
+ 创建 remote 对象时传入 funcs 注册处理函数,使得对端能通过 (rpc message).func 调用
239
+ 使用 remote.handle 方法处理对端发来的 websocket message,对于 websocket 连接接收方需要手动绑定 websocket message 事件到 remote.handle
240
+ 使用 remote.call 进行一元 rpc
241
+ 使用 remote.send 结合 message.id 进行复杂 rpc
242
+ 创建后等到首个 remote.call 或 remote.send 时自动建立实际 websocket 连接
243
+ rpc 状态与底层连接的状态无关,如果是传入 url 创建的 remote 实例,remote.send 时检测到断线会自动建立新的 websocket 连接
244
+
242
245
  @example
243
246
  // Zero 继承自 Remote 并通过 call 实现了一些方法
244
247
  let zero = new Zero({ local: true })
@@ -255,18 +258,21 @@ export async function connect_websocket(url, { protocols, on_message, on_error,
255
258
 
256
259
  zero.send({ id, func: 'subscribe_stdio' }) */
257
260
  export class Remote {
258
- /** 是否为建立 rpc 连接的发起方 */
261
+ /** 在构造 Remote 时, this.initiator = Boolean(url || websocket)
262
+ - true: 作为 websocket 连接发起方
263
+ - false: 作为 websocket 连接接收方 */
259
264
  initiator;
260
- /** websocket url */
265
+ /** 作为 websocket 连接发起方,对端的 url 地址 */
261
266
  url;
262
- /** 主动发起 websocket 连接的客户端 (构造函数传 url) 有这个属性 */
267
+ /** 作为 websocket 连接发起方有 websocket lock */
263
268
  lwebsocket;
264
- /** 通过 rpc message.func 被调用的 rpc 函数 */
269
+ /** websocket 连接发起方,接收方,都能被对端通过 (rpc message).func 调用的 rpc 函数 */
265
270
  funcs;
266
- /** map<id, message handler>: 通过 rpc message.id 找到对应的 handler, unary rpc 接收方不需要设置 handlers, 发送方需要 */
271
+ /** map<id, message handler>: 通过 (rpc message).id 找到对应的 handler
272
+ 一元 rpc 接收方不需要设置 handlers, 发送方需要 */
267
273
  handlers = new Map();
274
+ /** `false` 打印所有交互的 rpc messages */
268
275
  print = false;
269
- pconnect;
270
276
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
271
277
  static parse(buffer) {
272
278
  const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -287,6 +293,7 @@ export class Remote {
287
293
  return message;
288
294
  }
289
295
  static pack({ id, func, data = [], done, error }) {
296
+ assert('length' in data, t('message.data 必须是数组'));
290
297
  let data_ = new Array(data.length);
291
298
  let bins = [];
292
299
  let bufs = [];
@@ -300,7 +307,6 @@ export class Remote {
300
307
  else
301
308
  data_[i] = item;
302
309
  }
303
- // 有可能 data_json 含有循环引用导致 JSON.stringify 报错
304
310
  const data_json = {
305
311
  id,
306
312
  ...func ? { func } : {},
@@ -309,27 +315,36 @@ export class Remote {
309
315
  ...data_.length ? { data: data_ } : {},
310
316
  ...bins.length ? { bins } : {},
311
317
  };
318
+ // 有可能 data_json 含有循环引用导致 JSON.stringify 报错
312
319
  const str_json = encoder.encode(JSON.stringify(data_json));
313
320
  let dv = new DataView(new ArrayBuffer(4));
314
321
  dv.setUint32(0, str_json.length, true);
315
322
  return concat([dv, str_json, ...bufs]);
316
323
  }
317
- /** 作为 rpc 发起方,可以通过传入 url 或者 websocket 定义远程 Remote
318
- 作为 rpc 接收方,可以不传 url 和 websocket 定义本地 Remote */
319
- constructor({ url, funcs = {}, websocket, on_error } = {}) {
324
+ /** 作为 websocket 连接发起方,传入 url websocket,定义远程 Remote
325
+ 作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
326
+ constructor({ url, funcs, print, websocket, on_error } = {}) {
320
327
  assert(!(url && websocket), t('构建 Remote 时 url 和 websocket 最多只能传一个'));
321
328
  this.initiator = Boolean(url || websocket);
322
- this.url = url;
323
- this.funcs = funcs;
329
+ if (url)
330
+ this.url = url;
331
+ if (funcs)
332
+ this.funcs = funcs;
333
+ if (print !== undefined)
334
+ this.print = print;
324
335
  if (on_error)
325
336
  this.on_error = on_error;
326
- this.lwebsocket = new Lock(websocket || null);
337
+ if (this.initiator)
338
+ this.lwebsocket = new Lock(websocket || null);
327
339
  }
328
340
  on_error(error, websocket) {
329
341
  throw error;
330
342
  }
331
- /** 幂等,保证 websocket 已连接,否则抛出异常,不需要手动调用,在其它方法中已默认自动重连
332
- 作为接收方需要传入使用的 websocket 连接,确保这个这个连接的状态 */
343
+ /** 幂等,保证 websocket 已连接,否则抛出异常
344
+ 一般情况不需要手动调用,在其它方法中会自动调用这个方法,除非需要手动建立 websocket 连接并确保成功
345
+ 连接断开后,通过传入 url 参数构造的 remote 实例会自动创建新的 websocket 连接,而通过传入 websocket 参数构造的实例只会检查连接状态,在断开时抛出异常
346
+ 作为 websocket 连接发起方,不需要传入 websocket
347
+ 作为 websocket 连接接收方,需要传入使用的 websocket 连接,确保这个这个连接的状态 */
333
348
  async connect(websocket) {
334
349
  if (this.initiator)
335
350
  if (this.lwebsocket.resource?.readyState === WebSocket.OPEN)
@@ -344,8 +359,6 @@ export class Remote {
344
359
  // 底层连接断开后自动重连对上层应该是无感知的,除非再次连接时失败
345
360
  if (websocket?.readyState === WebSocket.OPEN)
346
361
  return;
347
- else if (!this.url)
348
- throw new Error(t('创建 Remote 时传入的 websocket 连接已断开'));
349
362
  else // 重连
350
363
  this.lwebsocket.resource = await connect_websocket(this.url, {
351
364
  on_message: (data, websocket) => {
@@ -354,29 +367,25 @@ export class Remote {
354
367
  on_error: this.on_error.bind(this)
355
368
  });
356
369
  });
357
- else if (websocket.readyState === WebSocket.OPEN)
358
- return;
359
- else
370
+ else if (websocket.readyState !== WebSocket.OPEN)
360
371
  throw new Error(t('传入的 websocket 连接已断开'));
361
372
  }
373
+ /** 作为 websocket 连接发起方手动关闭到对端的 websocket 连接 */
362
374
  disconnect() {
363
375
  this.lwebsocket.resource?.close(1000);
364
376
  }
365
- /** 接收 websocket 连接的本地 remote 必传 websocket 参数;发起端选传,如果传了必须等于 this.websocket_lock.resource
377
+ /** 发送 message 到对端 remote
378
+ 作为 websocket 连接发起方,不需要传入 websocket
379
+ 作为 websocket 连接接收方,必传 websocket 参数
366
380
  发送或连接出错时自动清理 message.id 对应的 handler */
367
381
  async send(message, websocket) {
382
+ if (this.print)
383
+ console.log('remote.send:', message);
368
384
  try {
369
- if (this.initiator) {
370
- assert(!websocket || websocket === this.lwebsocket.resource);
371
- await this.connect();
372
- websocket = this.lwebsocket.resource;
373
- }
374
- else
375
- await this.connect(websocket);
385
+ await this.connect(websocket);
376
386
  if (!message.id)
377
387
  message.id = genid();
378
- // 不需要独占 websocket
379
- websocket.send(Remote.pack(message));
388
+ (websocket || this.lwebsocket.resource).send(Remote.pack(message));
380
389
  }
381
390
  catch (error) {
382
391
  if (message.id)
@@ -384,21 +393,21 @@ export class Remote {
384
393
  throw error;
385
394
  }
386
395
  }
387
- /** 处理接收到的 websocket message 并解析, 根据 id dispatch 到对应的 handler 进行处理
388
- 如果 message.done == true 则清理 handler
389
- 如果 handler 返回了值,则包装为 message 发送
396
+ /** 处理接收到的 websocket message 并解析, 根据 message.id message.func 分发到对应的 handler 进行处理
397
+ 如果 message.done == true 则对端指示当前 remote 可以清理 handler
398
+ 如果 handler 返回了值 (一定是数组),则包装为 message 发送
390
399
  使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
391
400
  async handle(data, websocket) {
392
401
  const message = Remote.parse(data);
393
402
  const { id, func, done } = message;
394
403
  if (this.print)
395
- console.log(message);
404
+ console.log('remote.handle:', message);
396
405
  let handler;
397
406
  if (func)
398
407
  handler = this.funcs[func];
399
408
  else {
400
409
  handler = this.handlers.get(id);
401
- if (done)
410
+ if (done && handler)
402
411
  this.handlers.delete(id);
403
412
  }
404
413
  try {
@@ -421,8 +430,10 @@ export class Remote {
421
430
  throw error;
422
431
  }
423
432
  }
424
- /** 调用 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应) */
425
- async call(func, args) {
433
+ /** 调用对端 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应)
434
+ 作为 websocket 连接发起方,不需要传入 websocket
435
+ 作为 websocket 连接接收方,必传 websocket 参数 */
436
+ async call(func, args, websocket) {
426
437
  return new Promise(async (resolve, reject) => {
427
438
  const id = genid();
428
439
  this.handlers.set(id, (message) => {
@@ -434,7 +445,7 @@ export class Remote {
434
445
  this.handlers.delete(id);
435
446
  });
436
447
  try {
437
- await this.send({ id, func, data: args }); // 不需要 done: true, 因为对面的 remote.handlers 中不会有这个 id 的 handler
448
+ await this.send({ id, func, data: args }, websocket); // 不需要 done: true, 因为对面的 remote.handlers 中不会有这个 id 的 handler
438
449
  }
439
450
  catch (error) {
440
451
  reject(error);
package/net.d.ts CHANGED
@@ -173,10 +173,13 @@ export interface Message<TData extends any[] = any[]> {
173
173
  bins?: number[];
174
174
  }
175
175
  /** 通过创建 remote 对象对 websocket rpc 进行抽象
176
- 调用方使用 remote.call 进行调用
177
- 被调方在创建 remote 对象时传入 funcs 注册处理函数,并使用 remote.handle 方法处理 websocket message
178
- 未连接时自动连接,断开后自动重连 (保证执行 send 时已连接,否则报错)
179
- 从设计上与 rpc 状态与底层连接的状态无关 (通过传入 url 创建的),底层连接断开后,只要检测到断线,会尝试重连,如果重连成功,则不影响调用的状态
176
+ 创建 remote 对象时传入 funcs 注册处理函数,使得对端能通过 (rpc message).func 调用
177
+ 使用 remote.handle 方法处理对端发来的 websocket message,对于 websocket 连接接收方需要手动绑定 websocket message 事件到 remote.handle
178
+ 使用 remote.call 进行一元 rpc
179
+ 使用 remote.send 结合 message.id 进行复杂 rpc
180
+ 创建后等到首个 remote.call 或 remote.send 时自动建立实际 websocket 连接
181
+ rpc 状态与底层连接的状态无关,如果是传入 url 创建的 remote 实例,remote.send 时检测到断线会自动建立新的 websocket 连接
182
+
180
183
  @example
181
184
  // Zero 继承自 Remote 并通过 call 实现了一些方法
182
185
  let zero = new Zero({ local: true })
@@ -193,42 +196,59 @@ export interface Message<TData extends any[] = any[]> {
193
196
 
194
197
  zero.send({ id, func: 'subscribe_stdio' }) */
195
198
  export declare class Remote {
196
- /** 是否为建立 rpc 连接的发起方 */
199
+ /** 在构造 Remote 时, this.initiator = Boolean(url || websocket)
200
+ - true: 作为 websocket 连接发起方
201
+ - false: 作为 websocket 连接接收方 */
197
202
  initiator: boolean;
198
- /** websocket url */
203
+ /** 作为 websocket 连接发起方,对端的 url 地址 */
199
204
  url?: string;
200
- /** 主动发起 websocket 连接的客户端 (构造函数传 url) 有这个属性 */
205
+ /** 作为 websocket 连接发起方有 websocket lock */
201
206
  lwebsocket?: Lock<WebSocket>;
202
- /** 通过 rpc message.func 被调用的 rpc 函数 */
207
+ /** websocket 连接发起方,接收方,都能被对端通过 (rpc message).func 调用的 rpc 函数 */
203
208
  funcs: Record<string, MessageHandler>;
204
- /** map<id, message handler>: 通过 rpc message.id 找到对应的 handler, unary rpc 接收方不需要设置 handlers, 发送方需要 */
209
+ /** map<id, message handler>: 通过 (rpc message).id 找到对应的 handler
210
+ 一元 rpc 接收方不需要设置 handlers, 发送方需要 */
205
211
  handlers: Map<number, MessageHandler>;
212
+ /** `false` 打印所有交互的 rpc messages */
206
213
  print: boolean;
207
- pconnect: Promise<any>;
208
214
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
209
215
  static parse<TData extends any[] = any[]>(buffer: Uint8Array): Message<TData>;
210
216
  static pack({ id, func, data, done, error }: Message): Uint8Array;
211
- /** 作为 rpc 发起方,可以通过传入 url 或者 websocket 定义远程 Remote
212
- 作为 rpc 接收方,可以不传 url 和 websocket 定义本地 Remote */
213
- constructor({ url, funcs, websocket, on_error }?: {
217
+ /** 作为 websocket 连接发起方,传入 url websocket,定义远程 Remote
218
+ 作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
219
+ constructor({ url, funcs, print, websocket, on_error }?: {
214
220
  url?: string;
215
221
  funcs?: Remote['funcs'];
222
+ print?: boolean;
216
223
  websocket?: WebSocket;
217
224
  on_error?(error: WebSocketConnectionError, websocket: WebSocket): void;
218
225
  });
219
226
  on_error?(error: WebSocketConnectionError, websocket: WebSocket): void;
220
- /** 幂等,保证 websocket 已连接,否则抛出异常,不需要手动调用,在其它方法中已默认自动重连
221
- 作为接收方需要传入使用的 websocket 连接,确保这个这个连接的状态 */
227
+ /** 幂等,保证 websocket 已连接,否则抛出异常
228
+ 一般情况不需要手动调用,在其它方法中会自动调用这个方法,除非需要手动建立 websocket 连接并确保成功
229
+ 连接断开后,通过传入 url 参数构造的 remote 实例会自动创建新的 websocket 连接,而通过传入 websocket 参数构造的实例只会检查连接状态,在断开时抛出异常
230
+ 作为 websocket 连接发起方,不需要传入 websocket
231
+ 作为 websocket 连接接收方,需要传入使用的 websocket 连接,确保这个这个连接的状态 */
222
232
  connect(websocket?: WebSocket): Promise<void>;
233
+ /** 作为 websocket 连接发起方手动关闭到对端的 websocket 连接 */
223
234
  disconnect(): void;
224
- /** 接收 websocket 连接的本地 remote 必传 websocket 参数;发起端选传,如果传了必须等于 this.websocket_lock.resource
235
+ /** 发送 message 到对端 remote
236
+ 作为 websocket 连接发起方,不需要传入 websocket
237
+ 作为 websocket 连接接收方,必传 websocket 参数
225
238
  发送或连接出错时自动清理 message.id 对应的 handler */
226
239
  send(message: Message, websocket?: WebSocket): Promise<void>;
227
- /** 处理接收到的 websocket message 并解析, 根据 id dispatch 到对应的 handler 进行处理
228
- 如果 message.done == true 则清理 handler
229
- 如果 handler 返回了值,则包装为 message 发送
240
+ /** 处理接收到的 websocket message 并解析, 根据 message.id message.func 分发到对应的 handler 进行处理
241
+ 如果 message.done == true 则对端指示当前 remote 可以清理 handler
242
+ 如果 handler 返回了值 (一定是数组),则包装为 message 发送
230
243
  使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
231
244
  handle(data: Uint8Array, websocket: WebSocket): Promise<void>;
232
- /** 调用 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应) */
233
- call<TReturn extends any[] = any[]>(func: string, args?: any[]): Promise<TReturn>;
245
+ /** 调用对端 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应)
246
+ 作为 websocket 连接发起方,不需要传入 websocket
247
+ 作为 websocket 连接接收方,必传 websocket 参数 */
248
+ call<TReturn extends any[] = any[]>(func: string, args?: any[], websocket?: WebSocket): Promise<TReturn>;
249
+ }
250
+ /** 通常将连接到 server 的 client 抽象为下面的结构 */
251
+ export interface RemoteClient {
252
+ remote: Remote;
253
+ websocket: WebSocket;
234
254
  }
package/net.js CHANGED
@@ -388,10 +388,13 @@ on_message, on_error, on_close }) {
388
388
  });
389
389
  }
390
390
  /** 通过创建 remote 对象对 websocket rpc 进行抽象
391
- 调用方使用 remote.call 进行调用
392
- 被调方在创建 remote 对象时传入 funcs 注册处理函数,并使用 remote.handle 方法处理 websocket message
393
- 未连接时自动连接,断开后自动重连 (保证执行 send 时已连接,否则报错)
394
- 从设计上与 rpc 状态与底层连接的状态无关 (通过传入 url 创建的),底层连接断开后,只要检测到断线,会尝试重连,如果重连成功,则不影响调用的状态
391
+ 创建 remote 对象时传入 funcs 注册处理函数,使得对端能通过 (rpc message).func 调用
392
+ 使用 remote.handle 方法处理对端发来的 websocket message,对于 websocket 连接接收方需要手动绑定 websocket message 事件到 remote.handle
393
+ 使用 remote.call 进行一元 rpc
394
+ 使用 remote.send 结合 message.id 进行复杂 rpc
395
+ 创建后等到首个 remote.call 或 remote.send 时自动建立实际 websocket 连接
396
+ rpc 状态与底层连接的状态无关,如果是传入 url 创建的 remote 实例,remote.send 时检测到断线会自动建立新的 websocket 连接
397
+
395
398
  @example
396
399
  // Zero 继承自 Remote 并通过 call 实现了一些方法
397
400
  let zero = new Zero({ local: true })
@@ -408,18 +411,21 @@ on_message, on_error, on_close }) {
408
411
 
409
412
  zero.send({ id, func: 'subscribe_stdio' }) */
410
413
  export class Remote {
411
- /** 是否为建立 rpc 连接的发起方 */
414
+ /** 在构造 Remote 时, this.initiator = Boolean(url || websocket)
415
+ - true: 作为 websocket 连接发起方
416
+ - false: 作为 websocket 连接接收方 */
412
417
  initiator;
413
- /** websocket url */
418
+ /** 作为 websocket 连接发起方,对端的 url 地址 */
414
419
  url;
415
- /** 主动发起 websocket 连接的客户端 (构造函数传 url) 有这个属性 */
420
+ /** 作为 websocket 连接发起方有 websocket lock */
416
421
  lwebsocket;
417
- /** 通过 rpc message.func 被调用的 rpc 函数 */
422
+ /** websocket 连接发起方,接收方,都能被对端通过 (rpc message).func 调用的 rpc 函数 */
418
423
  funcs;
419
- /** map<id, message handler>: 通过 rpc message.id 找到对应的 handler, unary rpc 接收方不需要设置 handlers, 发送方需要 */
424
+ /** map<id, message handler>: 通过 (rpc message).id 找到对应的 handler
425
+ 一元 rpc 接收方不需要设置 handlers, 发送方需要 */
420
426
  handlers = new Map();
427
+ /** `false` 打印所有交互的 rpc messages */
421
428
  print = false;
422
- pconnect;
423
429
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
424
430
  static parse(buffer) {
425
431
  const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -440,6 +446,7 @@ export class Remote {
440
446
  return message;
441
447
  }
442
448
  static pack({ id, func, data = [], done, error }) {
449
+ assert('length' in data, t('message.data 必须是数组'));
443
450
  let data_ = new Array(data.length);
444
451
  let bins = [];
445
452
  let bufs = [];
@@ -453,7 +460,6 @@ export class Remote {
453
460
  else
454
461
  data_[i] = item;
455
462
  }
456
- // 有可能 data_json 含有循环引用导致 JSON.stringify 报错
457
463
  const data_json = {
458
464
  id,
459
465
  ...func ? { func } : {},
@@ -462,27 +468,36 @@ export class Remote {
462
468
  ...data_.length ? { data: data_ } : {},
463
469
  ...bins.length ? { bins } : {},
464
470
  };
471
+ // 有可能 data_json 含有循环引用导致 JSON.stringify 报错
465
472
  const str_json = encoder.encode(JSON.stringify(data_json));
466
473
  let dv = new DataView(new ArrayBuffer(4));
467
474
  dv.setUint32(0, str_json.length, true);
468
475
  return concat([dv, str_json, ...bufs]);
469
476
  }
470
- /** 作为 rpc 发起方,可以通过传入 url 或者 websocket 定义远程 Remote
471
- 作为 rpc 接收方,可以不传 url 和 websocket 定义本地 Remote */
472
- constructor({ url, funcs = {}, websocket, on_error } = {}) {
477
+ /** 作为 websocket 连接发起方,传入 url websocket,定义远程 Remote
478
+ 作为 websocket 连接接收方,不传 url 和 websocket,定义本地 Remote */
479
+ constructor({ url, funcs, print, websocket, on_error } = {}) {
473
480
  assert(!(url && websocket), t('构建 Remote 时 url 和 websocket 最多只能传一个'));
474
481
  this.initiator = Boolean(url || websocket);
475
- this.url = url;
476
- this.funcs = funcs;
482
+ if (url)
483
+ this.url = url;
484
+ if (funcs)
485
+ this.funcs = funcs;
486
+ if (print !== undefined)
487
+ this.print = print;
477
488
  if (on_error)
478
489
  this.on_error = on_error;
479
- this.lwebsocket = new Lock(websocket || null);
490
+ if (this.initiator)
491
+ this.lwebsocket = new Lock(websocket || null);
480
492
  }
481
493
  on_error(error, websocket) {
482
494
  throw error;
483
495
  }
484
- /** 幂等,保证 websocket 已连接,否则抛出异常,不需要手动调用,在其它方法中已默认自动重连
485
- 作为接收方需要传入使用的 websocket 连接,确保这个这个连接的状态 */
496
+ /** 幂等,保证 websocket 已连接,否则抛出异常
497
+ 一般情况不需要手动调用,在其它方法中会自动调用这个方法,除非需要手动建立 websocket 连接并确保成功
498
+ 连接断开后,通过传入 url 参数构造的 remote 实例会自动创建新的 websocket 连接,而通过传入 websocket 参数构造的实例只会检查连接状态,在断开时抛出异常
499
+ 作为 websocket 连接发起方,不需要传入 websocket
500
+ 作为 websocket 连接接收方,需要传入使用的 websocket 连接,确保这个这个连接的状态 */
486
501
  async connect(websocket) {
487
502
  if (this.initiator)
488
503
  if (this.lwebsocket.resource?.readyState === WebSocket.OPEN)
@@ -497,8 +512,6 @@ export class Remote {
497
512
  // 底层连接断开后自动重连对上层应该是无感知的,除非再次连接时失败
498
513
  if (websocket?.readyState === WebSocket.OPEN)
499
514
  return;
500
- else if (!this.url)
501
- throw new Error(t('创建 Remote 时传入的 websocket 连接已断开'));
502
515
  else // 重连
503
516
  this.lwebsocket.resource = await connect_websocket(this.url, {
504
517
  on_message: (data, websocket) => {
@@ -507,29 +520,25 @@ export class Remote {
507
520
  on_error: this.on_error.bind(this)
508
521
  });
509
522
  });
510
- else if (websocket.readyState === WebSocket.OPEN)
511
- return;
512
- else
523
+ else if (websocket.readyState !== WebSocket.OPEN)
513
524
  throw new Error(t('传入的 websocket 连接已断开'));
514
525
  }
526
+ /** 作为 websocket 连接发起方手动关闭到对端的 websocket 连接 */
515
527
  disconnect() {
516
528
  this.lwebsocket.resource?.close(1000);
517
529
  }
518
- /** 接收 websocket 连接的本地 remote 必传 websocket 参数;发起端选传,如果传了必须等于 this.websocket_lock.resource
530
+ /** 发送 message 到对端 remote
531
+ 作为 websocket 连接发起方,不需要传入 websocket
532
+ 作为 websocket 连接接收方,必传 websocket 参数
519
533
  发送或连接出错时自动清理 message.id 对应的 handler */
520
534
  async send(message, websocket) {
535
+ if (this.print)
536
+ console.log('remote.send:', message);
521
537
  try {
522
- if (this.initiator) {
523
- assert(!websocket || websocket === this.lwebsocket.resource);
524
- await this.connect();
525
- websocket = this.lwebsocket.resource;
526
- }
527
- else
528
- await this.connect(websocket);
538
+ await this.connect(websocket);
529
539
  if (!message.id)
530
540
  message.id = genid();
531
- // 不需要独占 websocket
532
- websocket.send(Remote.pack(message));
541
+ (websocket || this.lwebsocket.resource).send(Remote.pack(message));
533
542
  }
534
543
  catch (error) {
535
544
  if (message.id)
@@ -537,21 +546,21 @@ export class Remote {
537
546
  throw error;
538
547
  }
539
548
  }
540
- /** 处理接收到的 websocket message 并解析, 根据 id dispatch 到对应的 handler 进行处理
541
- 如果 message.done == true 则清理 handler
542
- 如果 handler 返回了值,则包装为 message 发送
549
+ /** 处理接收到的 websocket message 并解析, 根据 message.id message.func 分发到对应的 handler 进行处理
550
+ 如果 message.done == true 则对端指示当前 remote 可以清理 handler
551
+ 如果 handler 返回了值 (一定是数组),则包装为 message 发送
543
552
  使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
544
553
  async handle(data, websocket) {
545
554
  const message = Remote.parse(data);
546
555
  const { id, func, done } = message;
547
556
  if (this.print)
548
- console.log(message);
557
+ console.log('remote.handle:', message);
549
558
  let handler;
550
559
  if (func)
551
560
  handler = this.funcs[func];
552
561
  else {
553
562
  handler = this.handlers.get(id);
554
- if (done)
563
+ if (done && handler)
555
564
  this.handlers.delete(id);
556
565
  }
557
566
  try {
@@ -574,8 +583,10 @@ export class Remote {
574
583
  throw error;
575
584
  }
576
585
  }
577
- /** 调用 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应) */
578
- async call(func, args) {
586
+ /** 调用对端 remote 中的 func, 只适用于最简单的一元 rpc (请求, 响应)
587
+ 作为 websocket 连接发起方,不需要传入 websocket
588
+ 作为 websocket 连接接收方,必传 websocket 参数 */
589
+ async call(func, args, websocket) {
579
590
  return new Promise(async (resolve, reject) => {
580
591
  const id = genid();
581
592
  this.handlers.set(id, (message) => {
@@ -587,7 +598,7 @@ export class Remote {
587
598
  this.handlers.delete(id);
588
599
  });
589
600
  try {
590
- await this.send({ id, func, data: args }); // 不需要 done: true, 因为对面的 remote.handlers 中不会有这个 id 的 handler
601
+ await this.send({ id, func, data: args }, websocket); // 不需要 done: true, 因为对面的 remote.handlers 中不会有这个 id 的 handler
591
602
  }
592
603
  catch (error) {
593
604
  reject(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -45,9 +45,9 @@
45
45
  ]
46
46
  },
47
47
  "dependencies": {
48
- "@babel/core": "^7.22.9",
49
- "@babel/parser": "^7.22.7",
50
- "@babel/traverse": "^7.22.8",
48
+ "@babel/core": "^7.22.10",
49
+ "@babel/parser": "^7.22.10",
50
+ "@babel/traverse": "^7.22.10",
51
51
  "@koa/cors": "^4.0.0",
52
52
  "@types/ws": "^8.5.5",
53
53
  "byte-size": "^8.1.1",
@@ -62,7 +62,7 @@
62
62
  "fs-extra": "^11.1.1",
63
63
  "gulp-sort": "^2.0.0",
64
64
  "hash-string": "^1.0.0",
65
- "i18next": "^23.2.11",
65
+ "i18next": "^23.4.2",
66
66
  "i18next-scanner": "^4.3.0",
67
67
  "js-cookie": "^3.0.5",
68
68
  "koa": "^2.14.2",
@@ -71,44 +71,44 @@
71
71
  "lodash": "^4.17.21",
72
72
  "map-stream": "0.0.7",
73
73
  "mime-types": "^2.1.35",
74
- "ora": "^6.3.1",
74
+ "ora": "^7.0.1",
75
75
  "qs": "^6.11.2",
76
76
  "react": "^18.2.0",
77
- "react-i18next": "^13.0.2",
77
+ "react-i18next": "^13.0.3",
78
78
  "resolve-path": "^1.4.0",
79
79
  "strip-ansi": "^7.1.0",
80
80
  "through2": "^4.0.2",
81
81
  "tough-cookie": "^4.1.3",
82
82
  "tslib": "^2.6.1",
83
83
  "typescript": "^5.1.6",
84
- "undici": "^5.22.1",
84
+ "undici": "^5.23.0",
85
85
  "vinyl": "^3.0.0",
86
86
  "vinyl-fs": "^4.0.0",
87
87
  "ws": "^8.13.0"
88
88
  },
89
89
  "devDependencies": {
90
- "@babel/types": "^7.22.5",
90
+ "@babel/types": "^7.22.10",
91
91
  "@types/babel__traverse": "^7.20.1",
92
92
  "@types/byte-size": "^8.1.0",
93
93
  "@types/chardet": "^0.8.1",
94
94
  "@types/fs-extra": "^11.0.1",
95
95
  "@types/gulp-sort": "2.0.1",
96
96
  "@types/js-cookie": "^3.0.3",
97
- "@types/koa": "^2.13.6",
97
+ "@types/koa": "^2.13.8",
98
98
  "@types/koa-compress": "^4.0.3",
99
- "@types/lodash": "^4.14.195",
99
+ "@types/lodash": "^4.14.196",
100
100
  "@types/mime-types": "^2.1.1",
101
- "@types/node": "^20.4.4",
101
+ "@types/node": "^20.4.8",
102
102
  "@types/qs": "^6.9.7",
103
- "@types/react": "^18.2.16",
103
+ "@types/react": "^18.2.18",
104
104
  "@types/through2": "^2.0.38",
105
105
  "@types/tough-cookie": "^4.0.2",
106
106
  "@types/vinyl-fs": "^3.0.2",
107
- "@types/vscode": "^1.80.0",
108
- "@typescript-eslint/eslint-plugin": "^6.2.0",
109
- "@typescript-eslint/parser": "^6.2.0",
110
- "eslint": "^8.45.0",
111
- "eslint-plugin-react": "^7.33.0",
107
+ "@types/vscode": "^1.81.0",
108
+ "@typescript-eslint/eslint-plugin": "^6.3.0",
109
+ "@typescript-eslint/parser": "^6.3.0",
110
+ "eslint": "^8.46.0",
111
+ "eslint-plugin-react": "^7.33.1",
112
112
  "eslint-plugin-xlint": "^1.0.6"
113
113
  },
114
114
  "scripts": {
package/process.d.ts CHANGED
@@ -48,6 +48,7 @@ interface StartOptions {
48
48
  - detached?: `false` 是否断开和 child 的关系 (ignore stdio, unref) whether to break the connection with child (ignore stdio, unref)
49
49
  */
50
50
  export declare function start(exe: string, args?: string[], { cwd, encoding, print, stdio, detached, env, window: _window, }?: StartOptions): Promise<ChildProcess>;
51
+ export declare function start_nodejs(js: string, args?: string[], options?: StartOptions): Promise<ChildProcess>;
51
52
  export interface CallOptions extends StartOptions {
52
53
  throw_code?: boolean;
53
54
  input?: string;
package/process.js CHANGED
@@ -80,6 +80,9 @@ export async function start(exe, args = [], { cwd, encoding = 'utf-8', print = t
80
80
  }
81
81
  return child;
82
82
  }
83
+ export async function start_nodejs(js, args = [], options) {
84
+ return start(exe_nodejs, [js, ...args], options);
85
+ }
83
86
  export async function call(exe, args = [], options = {}) {
84
87
  const { encoding = 'utf-8', throw_code = true, input, } = options;
85
88
  let { stdio = 'pipe', print = true } = options;
@@ -204,7 +207,7 @@ export async function term_nodejs(fp_js, args = [], { title, inspect, tsnode = f
204
207
  ...resolve ? ['--experimental-specifier-resolution=node'] : [],
205
208
  ...title ? [`--title=${title}`] : [],
206
209
  ...inspect ? [
207
- _break ? `--inspect-brk=0.0.0.0:${inspect}` : `--inspect=0.0.0.0:${inspect}`
210
+ _break ? `--inspect-brk=localhost:${inspect}` : `--inspect=localhost:${inspect}`
208
211
  ] : [],
209
212
  ...tsnode ? [
210
213
  '--loader=ts-node/esm',
package/repl.d.ts CHANGED
@@ -4,7 +4,7 @@ import type { Context } from 'koa';
4
4
  import './prototype.js';
5
5
  declare global {
6
6
  var __: any;
7
- var started_at: Date;
7
+ var tstarted: Date;
8
8
  var repl_router: (ctx: Context) => Promise<boolean>;
9
9
  }
10
10
  /** 谨慎使用,webpack 打包后可能会变成 /d:/1/mod/node_modules/xshell/ 这样的编译期路径 */
package/repl.js CHANGED
@@ -54,14 +54,14 @@ function trans_import_2_require(import_decl) {
54
54
  // (import_clause.namedBindings as NamedImports).elements
55
55
  return [
56
56
  // import mod from 'mod'
57
- // global.mod = __importDefault(require("mod")).default
57
+ // globalThis.mod = __importDefault(require("mod")).default
58
58
  ...import_clause.name ? [
59
- factory.createExpressionStatement(factory.createAssignment(factory.createPropertyAccessExpression(factory.createIdentifier('global'), import_clause.name), factory.createPropertyAccessExpression(factory.createCallExpression(factory.createIdentifier('__importDefault'), [], [require_module_stmt]), 'default')))
59
+ factory.createExpressionStatement(factory.createAssignment(factory.createPropertyAccessExpression(factory.createIdentifier('globalThis'), import_clause.name), factory.createPropertyAccessExpression(factory.createCallExpression(factory.createIdentifier('__importDefault'), [], [require_module_stmt]), 'default')))
60
60
  ] : [],
61
61
  // import * as mod from 'mod'
62
- // global.mod = __importStar(require("mod"))
62
+ // globalThis.mod = __importStar(require("mod"))
63
63
  ...import_clause.namedBindings && isNamespaceImport(import_clause.namedBindings) ? [
64
- factory.createExpressionStatement(factory.createAssignment(factory.createPropertyAccessExpression(factory.createIdentifier('global'), import_clause.namedBindings.name), factory.createCallExpression(factory.createIdentifier('__importStar'), [], [require_module_stmt])))
64
+ factory.createExpressionStatement(factory.createAssignment(factory.createPropertyAccessExpression(factory.createIdentifier('globalThis'), import_clause.namedBindings.name), factory.createCallExpression(factory.createIdentifier('__importStar'), [], [require_module_stmt])))
65
65
  ] : [],
66
66
  // import { element2 as element2_, element3 as element3_, other, default as mod2 } from 'mod2'
67
67
  ...import_clause.namedBindings && isNamedImports(import_clause.namedBindings) ? [
@@ -69,10 +69,10 @@ function trans_import_2_require(import_decl) {
69
69
  factory.createVariableStatement([], [factory.createVariableDeclaration(factory.createObjectBindingPattern(
70
70
  // ImportSpecifier[] -> ObjectBindingPattern.BindingElement[]
71
71
  import_clause.namedBindings.elements.map(import_specifier => factory.createBindingElement(undefined, import_specifier.propertyName, import_specifier.name, import_specifier.propertyName?.text === 'default' ? require_module_stmt : undefined))), undefined, undefined, require_module_stmt)]),
72
- // Object.assign(global, { element2_, element3 })
72
+ // Object.assign(globalThis, { element2_, element3 })
73
73
  factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Object'), 'assign'), [],
74
74
  // ImportSpecifier[] -> ObjectBindingPattern.BindingElement[]
75
- [factory.createIdentifier('global'), factory.createObjectLiteralExpression(import_clause.namedBindings.elements.map(import_specifier => factory.createShorthandPropertyAssignment(import_specifier.name)), false)])),
75
+ [factory.createIdentifier('globalThis'), factory.createObjectLiteralExpression(import_clause.namedBindings.elements.map(import_specifier => factory.createShorthandPropertyAssignment(import_specifier.name)), false)])),
76
76
  factory.createExpressionStatement(import_clause.namedBindings.elements.length === 1 ?
77
77
  // (a)
78
78
  factory.createParenthesizedExpression(import_clause.namedBindings.elements[0].name)
@@ -111,7 +111,7 @@ function trans_class_decl_2_expr(class_decl) {
111
111
  return class_decl;
112
112
  // export class C { } 这样的语句已经在 trans_export_stmt 中被去掉了 export modifier
113
113
  return factory.createVariableStatement([], factory.createVariableDeclarationList([
114
- factory.createVariableDeclaration(class_decl.name, undefined, undefined, factory.createClassExpression(undefined, class_decl.name, undefined, undefined, class_decl.members))
114
+ factory.createVariableDeclaration(class_decl.name, undefined, undefined, factory.createClassExpression(undefined, class_decl.name, undefined, class_decl.heritageClauses, class_decl.members))
115
115
  ]));
116
116
  }
117
117
  function get_expr_of_stmt(statement) {
@@ -141,7 +141,7 @@ function trans_return_2_expr(stmt) {
141
141
  return stmt;
142
142
  return factory.createExpressionStatement(stmt.expression);
143
143
  }
144
- function assign_var_decl(var_decl, destination = 'global') {
144
+ function assign_var_decl(var_decl, destination = 'globalThis') {
145
145
  if (!isVariableStatement(var_decl))
146
146
  return [var_decl];
147
147
  // VariableDeclaration[] -> ObjectLiteral[]
@@ -243,7 +243,7 @@ export async function eval_ts(code) {
243
243
  export async function repl_ts(code) {
244
244
  console.log(); // 离之前的输出有一个空行
245
245
  const timer = new Timer();
246
- const __ = global.__ = await eval_ts(code);
246
+ const __ = globalThis.__ = await eval_ts(code);
247
247
  timer.stop();
248
248
  console.log(inspect(__, { limit: inspection_limit }));
249
249
  if (timer.get() >= 500)
@@ -292,7 +292,7 @@ export async function start_repl() {
292
292
  })(),
293
293
  ]);
294
294
  console.log(('-'.repeat(30) + '\n' +
295
- t('xshell 启动成功,用时 ') + delta2str(new Date().getTime() - global.started_at.getTime()) + '\n' +
295
+ t('xshell 启动成功,用时 ') + delta2str(new Date().getTime() - globalThis.tstarted.getTime()) + '\n' +
296
296
  t('正在监听: http://localhost:8421\n') +
297
297
  '-'.repeat(30)).green);
298
298
  }
@@ -306,7 +306,7 @@ export async function exit() {
306
306
  process.exit();
307
307
  }
308
308
  export async function pollute_global() {
309
- Object.assign(global, {
309
+ Object.assign(globalThis, {
310
310
  __importDefault: (mod) => ((mod?.__esModule) ? mod : { default: mod }),
311
311
  __importStar: (mod) => {
312
312
  if (mod?.__esModule)
@@ -320,7 +320,7 @@ export async function pollute_global() {
320
320
  result.default = mod;
321
321
  return result;
322
322
  },
323
- exports: global
323
+ exports: globalThis
324
324
  });
325
325
  await Promise.all([
326
326
  pollute_module_default_export('lodash/omit.js', 'omit'),
@@ -339,9 +339,9 @@ export async function pollute_global() {
339
339
  console.log(t('所有模块加载成功'));
340
340
  }
341
341
  export async function pollute_module_exports(fp_mod) {
342
- Object.assign(global, await import(fp_mod));
342
+ Object.assign(globalThis, await import(fp_mod));
343
343
  }
344
344
  export async function pollute_module_default_export(fp_mod, name) {
345
- global[name] = (await import(fp_mod)).default;
345
+ globalThis[name] = (await import(fp_mod)).default;
346
346
  }
347
347
  //# sourceMappingURL=repl.js.map
package/server.js CHANGED
@@ -156,7 +156,7 @@ export class Server {
156
156
  // ------------ log
157
157
  this.logger(ctx);
158
158
  // ------------ repl_router hook
159
- if (await global.repl_router?.(ctx))
159
+ if (await globalThis.repl_router?.(ctx))
160
160
  return;
161
161
  if (await this.router(ctx))
162
162
  return;
package/utils.d.ts CHANGED
@@ -89,7 +89,7 @@ export declare function has_chinese(str: string): boolean;
89
89
  export declare function escape_line_feed(str: string): string;
90
90
  /** util.inspect(obj)
91
91
  - options
92
- - limit?: `10000`
92
+ - limit?: `10000` 传 0 时不限制
93
93
  - omit?: string[] */
94
94
  export declare function inspect(obj: any, options?: util.InspectOptions & {
95
95
  limit?: number;
package/utils.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Stream, Writable, Transform } from 'stream';
2
2
  import util from 'util';
3
- import omit from 'lodash/omit.js';
4
3
  import { t } from './i18n/instance.js';
5
4
  import './prototype.js';
6
5
  export const noop = () => { };
@@ -253,16 +252,20 @@ export function escape_line_feed(str) {
253
252
  }
254
253
  /** util.inspect(obj)
255
254
  - options
256
- - limit?: `10000`
255
+ - limit?: `10000` 传 0 时不限制
257
256
  - omit?: string[] */
258
257
  export function inspect(obj, options = {}) {
259
- if (options.omit)
260
- obj = omit(obj, [inspect.custom, ...(options.omit || [])]);
258
+ if (options.omit) {
259
+ obj = { ...obj };
260
+ for (const key of options.omit)
261
+ delete obj[key];
262
+ // 改变了 obj 的引用,去掉 inspect.custom 防止无限递归 inspect
263
+ delete obj[inspect.custom];
264
+ }
261
265
  let text = util.inspect(obj, options);
262
- if (!('limit' in options))
263
- options.limit = 10000;
264
- if (options.limit && text.length > options.limit)
265
- return `${text.slice(0, options.limit)}……'\u001b[39m\n`;
266
+ const limit = options.limit ?? 10000;
267
+ if (limit && text.length > limit)
268
+ return `${text.slice(0, limit)}……'\u001b[39m\n`;
266
269
  else
267
270
  return text;
268
271
  }
package/xshell.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- global.started_at = new Date();
2
+ globalThis.tstarted = new Date();
3
3
  import { start_repl } from './repl.js';
4
4
  start_repl();
5
5
  //# sourceMappingURL=xshell.js.map