xshell 1.0.45 → 1.0.47

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.d.ts CHANGED
@@ -155,6 +155,8 @@ export declare function flink(fp_real: string, fp_link: string, { junction, prin
155
155
  junction?: boolean;
156
156
  print?: boolean;
157
157
  }): Promise<void>;
158
- /** 打开一个文件并搜索替换某个 pattern open a file and replace certain pattern */
159
- export declare function freplace(fp: string, pattern: string | RegExp, replacement: string): Promise<void>;
158
+ /** 打开一个文件并搜索替换某个 pattern */
159
+ export declare function freplace(fp: string, pattern: string | RegExp, replacement: string, { print }?: {
160
+ print?: boolean;
161
+ }): Promise<void>;
160
162
  export {};
package/file.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { promises as fsp, default as fs } from 'fs';
2
2
  import { isUint8Array } from 'util/types';
3
- import fse from 'fs-extra';
4
3
  import { path } from './path.js';
5
4
  import { t } from './i18n/instance.js';
6
5
  import { to_json } from './prototype.js';
@@ -209,7 +208,8 @@ export async function fcopy(fp_src, fp_dst, { print = true, overwrite = true, }
209
208
  assert(path.isAbsolute(fp_src) && path.isAbsolute(fp_dst), t('fp_src 和 fp_dst 必须为完整路径'));
210
209
  if (print)
211
210
  console.log(t('复制'), fp_src, '→', fp_dst);
212
- await fse.copy(fp_src, fp_dst, { overwrite, errorOnExist: true });
211
+ const { copy } = await import('fs-extra');
212
+ await copy(fp_src, fp_dst, { overwrite, errorOnExist: true });
213
213
  }
214
214
  /** 移动文件或文件夹 move file or direcotry
215
215
  - src: 源 文件/文件夹 完整路径 src file/directory absolute path
@@ -223,7 +223,8 @@ export async function fmove(src, dst, { overwrite = false, print = true } = {})
223
223
  throw new Error(t('src 和 dst 必须为完整路径'));
224
224
  if (print)
225
225
  console.log(t('移动'), src, '→', dst);
226
- await fse.move(src, dst, { overwrite });
226
+ const { move } = await import('fs-extra');
227
+ await move(src, dst, { overwrite });
227
228
  }
228
229
  /** 重命名文件 rename file
229
230
  - fp: 当前文件名/路径 current filename/path
@@ -293,9 +294,9 @@ export async function flink(fp_real, fp_link, { junction = false, print = true }
293
294
  else
294
295
  fsp.symlink(fp_real, fp_link, is_fpd_real ? 'dir' : 'file');
295
296
  }
296
- /** 打开一个文件并搜索替换某个 pattern open a file and replace certain pattern */
297
- export async function freplace(fp, pattern, replacement) {
298
- await fwrite(fp, (await fread(fp))
299
- .replaceAll(pattern, replacement));
297
+ /** 打开一个文件并搜索替换某个 pattern */
298
+ export async function freplace(fp, pattern, replacement, { print = true } = {}) {
299
+ await fwrite(fp, (await fread(fp, { print }))
300
+ .replaceAll(pattern, replacement), { print });
300
301
  }
301
302
  //# sourceMappingURL=file.js.map
package/i18n/dict.json CHANGED
@@ -319,5 +319,23 @@
319
319
  },
320
320
  "正在监听: http://localhost:8421\n": {
321
321
  "en": "listening: http://localhost:8421\n"
322
+ },
323
+ "flist: 参数 fpd: '{{fpd}}' 必须是绝对路径": {
324
+ "en": "flist: parameter fpd: '{{fpd}}' must be an absolute path"
325
+ },
326
+ "flist: 参数 fpd: '{{fpd}}' 必须以 / 结尾": {
327
+ "en": "flist: argument fpd: '{{fpd}}' must end with /"
328
+ },
329
+ "fstat: 参数 fp: '{{fp}}' 必须是绝对路径": {
330
+ "en": "fstat: parameter fp: '{{fp}}' must be an absolute path"
331
+ },
332
+ "不存在且无法创建文件夹 {{fpd}}": {
333
+ "en": "Folder {{fpd}} does not exist and cannot be created"
334
+ },
335
+ "message.data 必须是数组": {
336
+ "en": "message.data must be an array"
337
+ },
338
+ "fsend 必须传 absolute 选项或 root 文件夹": {
339
+ "en": "fsend must pass absolute option or root folder"
322
340
  }
323
341
  }
package/net.browser.d.ts CHANGED
@@ -163,6 +163,7 @@ export declare class Remote {
163
163
  handlers: Map<number, MessageHandler>;
164
164
  /** `false` 打印所有交互的 rpc messages */
165
165
  print: boolean;
166
+ reconnecting: boolean;
166
167
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
167
168
  static parse<TData extends any[] = any[]>(buffer: Uint8Array): Message<TData>;
168
169
  static pack({ id, func, data, done, error }: Message): Uint8Array;
@@ -198,4 +199,17 @@ export declare class Remote {
198
199
  作为 websocket 连接发起方,不需要传入 websocket
199
200
  作为 websocket 连接接收方,必传 websocket 参数 */
200
201
  call<TReturn extends any[] = any[]>(func: string, args?: any[], websocket?: WebSocket): Promise<TReturn>;
202
+ /** 是幂等的,在未连接,或 (on_error) 检测到连接断开后以固定间隔尝试 remote.call(func) 重连
203
+ 通常在 on_error 中和首次启动时调用
204
+ - func?: 连接后调用的函数,通常为某个 register 函数,将自身注册到 server
205
+ - duration?: `1000 * 30` 首次重连失败后的后续尝试间隔 (单位: ms)
206
+ - first_delay?: `0` 调用函数后是否立即连接,还是在 first_delay 后重连 (单位: ms)
207
+ - on_error?: 接收每次尝试连接的错误 */
208
+ start_reconnecting({ func, interval, first_delay, on_error, }?: RemoteReconnectingOptions): Promise<void>;
209
+ }
210
+ export interface RemoteReconnectingOptions {
211
+ func?: string;
212
+ interval?: number;
213
+ first_delay?: number;
214
+ on_error?(error: Error): void;
201
215
  }
package/net.browser.js CHANGED
@@ -273,6 +273,7 @@ export class Remote {
273
273
  handlers = new Map();
274
274
  /** `false` 打印所有交互的 rpc messages */
275
275
  print = false;
276
+ reconnecting = false;
276
277
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
277
278
  static parse(buffer) {
278
279
  const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -452,5 +453,38 @@ export class Remote {
452
453
  }
453
454
  });
454
455
  }
456
+ /** 是幂等的,在未连接,或 (on_error) 检测到连接断开后以固定间隔尝试 remote.call(func) 重连
457
+ 通常在 on_error 中和首次启动时调用
458
+ - func?: 连接后调用的函数,通常为某个 register 函数,将自身注册到 server
459
+ - duration?: `1000 * 30` 首次重连失败后的后续尝试间隔 (单位: ms)
460
+ - first_delay?: `0` 调用函数后是否立即连接,还是在 first_delay 后重连 (单位: ms)
461
+ - on_error?: 接收每次尝试连接的错误 */
462
+ async start_reconnecting({ func, interval = 1000 * 10, first_delay = 0, on_error, } = {}) {
463
+ assert(this.url);
464
+ if (this.reconnecting)
465
+ return;
466
+ this.reconnecting = true;
467
+ if (first_delay)
468
+ await delay(first_delay);
469
+ for (;;) {
470
+ if (this.lwebsocket.resource?.readyState !== WebSocket.OPEN)
471
+ try {
472
+ if (func)
473
+ await this.call(func);
474
+ else
475
+ await this.connect();
476
+ this.reconnecting = false;
477
+ break;
478
+ }
479
+ catch (error) {
480
+ on_error?.(error);
481
+ }
482
+ else {
483
+ this.reconnecting = false;
484
+ break;
485
+ }
486
+ await delay(interval);
487
+ }
488
+ }
455
489
  }
456
490
  //# sourceMappingURL=net.browser.js.map
package/net.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
- import { FormData, Headers, type Response, type RequestInit } from 'undici';
3
- import { WebSocket, type CloseEvent, type ErrorEvent } from 'ws';
4
- import { Cookie, CookieJar, MemoryCookieStore } from 'tough-cookie';
2
+ import type { FormData, Headers, Response, RequestInit } from 'undici';
3
+ import type { WebSocket, CloseEvent, ErrorEvent } from 'ws';
4
+ import type { Cookie, CookieJar, MemoryCookieStore } from 'tough-cookie';
5
5
  declare module 'tough-cookie' {
6
6
  interface MemoryCookieStore {
7
7
  idx: Record<string, any>;
@@ -10,14 +10,16 @@ declare module 'tough-cookie' {
10
10
  import './prototype.js';
11
11
  import type { Encoding } from './file.js';
12
12
  import { inspect, Lock } from './utils.js';
13
+ export declare const WebSocketOpen = 1;
13
14
  export declare enum MyProxy {
14
15
  socks5 = "http://localhost:10080",
15
16
  whistle = "http://localhost:8899"
16
17
  }
17
- export { Headers };
18
+ export type { Headers };
18
19
  export declare const cookies: {
19
20
  store: MemoryCookieStore;
20
21
  jar: CookieJar;
22
+ init(): Promise<void>;
21
23
  get(domain_or_url: string, str?: boolean): Cookie[] | Promise<Cookie[] | string>;
22
24
  };
23
25
  export { Cookie };
@@ -100,7 +102,7 @@ export declare function rpc(func: string, args?: any[], { url, async: _async, ig
100
102
  async?: boolean;
101
103
  ignore?: boolean;
102
104
  }): Promise<any>;
103
- export { WebSocket };
105
+ export type { WebSocket };
104
106
  export declare class WebSocketConnectionError extends Error {
105
107
  name: string;
106
108
  websocket: WebSocket;
@@ -211,6 +213,7 @@ export declare class Remote {
211
213
  handlers: Map<number, MessageHandler>;
212
214
  /** `false` 打印所有交互的 rpc messages */
213
215
  print: boolean;
216
+ reconnecting: boolean;
214
217
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
215
218
  static parse<TData extends any[] = any[]>(buffer: Uint8Array): Message<TData>;
216
219
  static pack({ id, func, data, done, error }: Message): Uint8Array;
@@ -246,9 +249,28 @@ export declare class Remote {
246
249
  作为 websocket 连接发起方,不需要传入 websocket
247
250
  作为 websocket 连接接收方,必传 websocket 参数 */
248
251
  call<TReturn extends any[] = any[]>(func: string, args?: any[], websocket?: WebSocket): Promise<TReturn>;
252
+ /** 是幂等的,在未连接,或 (on_error) 检测到连接断开后以固定间隔尝试 remote.call(func) 重连
253
+ 通常在 on_error 中和首次启动时调用
254
+ - func?: 连接后调用的函数,通常为某个 register 函数,将自身注册到 server
255
+ - duration?: `1000 * 30` 首次重连失败后的后续尝试间隔 (单位: ms)
256
+ - first_delay?: `0` 调用函数后是否立即连接,还是在 first_delay 后重连 (单位: ms)
257
+ - on_error?: 接收每次尝试连接的错误 */
258
+ start_reconnecting({ func, interval, first_delay, on_error, }?: RemoteReconnectingOptions): Promise<void>;
249
259
  }
250
- /** 通常将连接到 server 的 client 抽象为下面的结构 */
251
- export interface RemoteClient {
260
+ /** 为连接到 server 的 client 创建 RemoteClient,以便后续调用 client 中方法 */
261
+ export declare class RemoteClient {
252
262
  remote: Remote;
253
263
  websocket: WebSocket;
264
+ constructor(remote: Remote, websocket: WebSocket);
265
+ /** 调用 client 中的 func, 只适用于最简单的一元 rpc (请求, 响应) */
266
+ call<TReturn extends any[] = any[]>(func: string, args?: any[]): Promise<TReturn>;
267
+ /** 发送 message 到 client
268
+ 发送或连接出错时自动清理 message.id 对应的 handler */
269
+ send(message: Message): Promise<void>;
270
+ }
271
+ export interface RemoteReconnectingOptions {
272
+ func?: string;
273
+ interval?: number;
274
+ first_delay?: number;
275
+ on_error?(error: Error): void;
254
276
  }
package/net.js CHANGED
@@ -1,21 +1,22 @@
1
- import { fetch, ProxyAgent, FormData, Headers } from 'undici';
2
- import { WebSocket } from 'ws';
3
- import { Cookie, CookieJar, MemoryCookieStore } from 'tough-cookie';
4
- import make_fetch_cookie from 'fetch-cookie';
5
1
  import { t } from './i18n/instance.js';
6
2
  import './prototype.js';
7
3
  import { inspect, concat, assert, genid, delay, Lock } from './utils.js';
4
+ export const WebSocketOpen = 1;
8
5
  export var MyProxy;
9
6
  (function (MyProxy) {
10
7
  MyProxy["socks5"] = "http://localhost:10080";
11
8
  MyProxy["whistle"] = "http://localhost:8899";
12
9
  })(MyProxy || (MyProxy = {}));
13
- export { Headers };
14
10
  // ------------------------------------ fetch, request
15
- let cookie_store = new MemoryCookieStore();
16
11
  export const cookies = {
17
- store: cookie_store,
18
- jar: new CookieJar(cookie_store),
12
+ store: null,
13
+ jar: null,
14
+ async init() {
15
+ if (this.jar)
16
+ return;
17
+ const { MemoryCookieStore, CookieJar } = await import('tough-cookie');
18
+ this.jar = new CookieJar(this.store = new MemoryCookieStore());
19
+ },
19
20
  get(domain_or_url, str = false) {
20
21
  if (domain_or_url.startsWith('http'))
21
22
  if (str)
@@ -31,7 +32,6 @@ export const cookies = {
31
32
  return cookies;
32
33
  },
33
34
  };
34
- export { Cookie };
35
35
  /** 对于 request() 函数来说无意义的 headers,会自动过滤掉 */
36
36
  const drop_request_headers = new Set([
37
37
  // : 开头的 key
@@ -45,8 +45,12 @@ const drop_request_headers = new Set([
45
45
  'upgrade',
46
46
  ]);
47
47
  let proxy_agents = {};
48
- const fetch_cookie = make_fetch_cookie(fetch, cookies.jar, false);
48
+ let fetch_cookie;
49
49
  async function fetch_retry(url, options, timeout, retries = 0, count = 0) {
50
+ const { fetch } = await import('undici');
51
+ const { default: make_fetch_cookie } = await import('fetch-cookie');
52
+ await cookies.init();
53
+ fetch_cookie ||= make_fetch_cookie(fetch, cookies.jar, false);
50
54
  try {
51
55
  options.signal = AbortSignal.timeout(timeout);
52
56
  return await fetch_cookie(url, options);
@@ -64,6 +68,7 @@ async function fetch_retry(url, options, timeout, retries = 0, count = 0) {
64
68
  }
65
69
  }
66
70
  export async function request(url, { method, queries, headers: _headers, body, type = 'application/json', proxy, encoding, retries, timeout = 5 * 1000, auth, cookies: _cookies, raw = false, } = {}) {
71
+ const { Headers, ProxyAgent, FormData } = await import('undici');
67
72
  url = new URL(url);
68
73
  if (queries)
69
74
  for (const key in queries) {
@@ -278,7 +283,6 @@ export async function rpc(func, args, { url = 'http://localhost:8421/api/rpc', a
278
283
  }
279
284
  let decoder = new TextDecoder();
280
285
  let encoder = new TextEncoder();
281
- export { WebSocket };
282
286
  export class WebSocketConnectionError extends Error {
283
287
  name = 'WebSocketConnectionError';
284
288
  websocket;
@@ -331,6 +335,7 @@ export class WebSocketConnectionError extends Error {
331
335
  https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes */
332
336
  export async function connect_websocket(url, { protocols, max_payload = 2 ** 33, // 8 GB
333
337
  on_message, on_error, on_close }) {
338
+ const { WebSocket } = await import('ws');
334
339
  let websocket = new WebSocket(url, protocols, {
335
340
  maxPayload: max_payload,
336
341
  skipUTF8Validation: true
@@ -426,6 +431,7 @@ export class Remote {
426
431
  handlers = new Map();
427
432
  /** `false` 打印所有交互的 rpc messages */
428
433
  print = false;
434
+ reconnecting = false;
429
435
  /** 使用 Uint8Array 作为参数更灵活 https://stackoverflow.com/a/74505197/7609214 */
430
436
  static parse(buffer) {
431
437
  const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -500,7 +506,7 @@ export class Remote {
500
506
  作为 websocket 连接接收方,需要传入使用的 websocket 连接,确保这个这个连接的状态 */
501
507
  async connect(websocket) {
502
508
  if (this.initiator)
503
- if (this.lwebsocket.resource?.readyState === WebSocket.OPEN)
509
+ if (this.lwebsocket.resource?.readyState === WebSocketOpen)
504
510
  return;
505
511
  else if (!this.url)
506
512
  throw new Error(t('创建 Remote 时传入的 websocket 连接已断开'));
@@ -510,7 +516,7 @@ export class Remote {
510
516
  return this.lwebsocket.request(async (websocket) => {
511
517
  // 保存的 rpc 状态在 this.handlers, 与 websocket 无关,因此即使断开重连也不影响 rpc 的运行,即
512
518
  // 底层连接断开后自动重连对上层应该是无感知的,除非再次连接时失败
513
- if (websocket?.readyState === WebSocket.OPEN)
519
+ if (websocket?.readyState === WebSocketOpen)
514
520
  return;
515
521
  else // 重连
516
522
  this.lwebsocket.resource = await connect_websocket(this.url, {
@@ -520,7 +526,7 @@ export class Remote {
520
526
  on_error: this.on_error.bind(this)
521
527
  });
522
528
  });
523
- else if (websocket.readyState !== WebSocket.OPEN)
529
+ else if (websocket.readyState !== WebSocketOpen)
524
530
  throw new Error(t('传入的 websocket 连接已断开'));
525
531
  }
526
532
  /** 作为 websocket 连接发起方手动关闭到对端的 websocket 连接 */
@@ -576,7 +582,7 @@ export class Remote {
576
582
  }
577
583
  catch (error) {
578
584
  // handler 出错并不意味着 rpc 一定会结束,可能 error 是运行中的正常数据,所以不能清理 handler
579
- if (websocket.readyState === WebSocket.OPEN &&
585
+ if (websocket.readyState === WebSocketOpen &&
580
586
  !message.error // 防止无限循环往对方发送 error, 只有在对方无错误时才可以发送
581
587
  )
582
588
  await this.send({ id, error, /* 不能设置 done 清理对面 handler, 理由同上 */ }, websocket);
@@ -605,5 +611,56 @@ export class Remote {
605
611
  }
606
612
  });
607
613
  }
614
+ /** 是幂等的,在未连接,或 (on_error) 检测到连接断开后以固定间隔尝试 remote.call(func) 重连
615
+ 通常在 on_error 中和首次启动时调用
616
+ - func?: 连接后调用的函数,通常为某个 register 函数,将自身注册到 server
617
+ - duration?: `1000 * 30` 首次重连失败后的后续尝试间隔 (单位: ms)
618
+ - first_delay?: `0` 调用函数后是否立即连接,还是在 first_delay 后重连 (单位: ms)
619
+ - on_error?: 接收每次尝试连接的错误 */
620
+ async start_reconnecting({ func, interval = 1000 * 10, first_delay = 0, on_error, } = {}) {
621
+ assert(this.url);
622
+ if (this.reconnecting)
623
+ return;
624
+ this.reconnecting = true;
625
+ if (first_delay)
626
+ await delay(first_delay);
627
+ for (;;) {
628
+ if (this.lwebsocket.resource?.readyState !== WebSocketOpen)
629
+ try {
630
+ if (func)
631
+ await this.call(func);
632
+ else
633
+ await this.connect();
634
+ this.reconnecting = false;
635
+ break;
636
+ }
637
+ catch (error) {
638
+ on_error?.(error);
639
+ }
640
+ else {
641
+ this.reconnecting = false;
642
+ break;
643
+ }
644
+ await delay(interval);
645
+ }
646
+ }
647
+ }
648
+ /** 为连接到 server 的 client 创建 RemoteClient,以便后续调用 client 中方法 */
649
+ export class RemoteClient {
650
+ remote;
651
+ websocket;
652
+ constructor(remote, websocket) {
653
+ this.remote = remote;
654
+ this.websocket = websocket;
655
+ }
656
+ /** 调用 client 中的 func, 只适用于最简单的一元 rpc (请求, 响应) */
657
+ async call(func, args) {
658
+ return this.remote.call(func, args, this.websocket);
659
+ }
660
+ /** 发送 message 到 client
661
+ 发送或连接出错时自动清理 message.id 对应的 handler */
662
+ async send(message) {
663
+ return this.remote.send(message, this.websocket);
664
+ }
608
665
  }
609
666
  //# sourceMappingURL=net.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -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.4.2",
65
+ "i18next": "^23.4.4",
66
66
  "i18next-scanner": "^4.3.0",
67
67
  "js-cookie": "^3.0.5",
68
68
  "koa": "^2.14.2",
@@ -72,9 +72,8 @@
72
72
  "map-stream": "0.0.7",
73
73
  "mime-types": "^2.1.35",
74
74
  "ora": "^7.0.1",
75
- "qs": "^6.11.2",
76
75
  "react": "^18.2.0",
77
- "react-i18next": "^13.0.3",
76
+ "react-i18next": "^13.1.1",
78
77
  "resolve-path": "^1.4.0",
79
78
  "strip-ansi": "^7.1.0",
80
79
  "through2": "^4.0.2",
@@ -96,11 +95,10 @@
96
95
  "@types/js-cookie": "^3.0.3",
97
96
  "@types/koa": "^2.13.8",
98
97
  "@types/koa-compress": "^4.0.3",
99
- "@types/lodash": "^4.14.196",
98
+ "@types/lodash": "^4.14.197",
100
99
  "@types/mime-types": "^2.1.1",
101
- "@types/node": "^20.4.8",
102
- "@types/qs": "^6.9.7",
103
- "@types/react": "^18.2.18",
100
+ "@types/node": "^20.4.9",
101
+ "@types/react": "^18.2.20",
104
102
  "@types/through2": "^2.0.38",
105
103
  "@types/tough-cookie": "^4.0.2",
106
104
  "@types/vinyl-fs": "^3.0.2",
package/repl.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- import ts from 'typescript';
2
- import type { Statement } from 'typescript';
3
1
  import type { Context } from 'koa';
4
2
  import './prototype.js';
5
3
  declare global {
@@ -9,23 +7,6 @@ declare global {
9
7
  }
10
8
  /** 谨慎使用,webpack 打包后可能会变成 /d:/1/mod/node_modules/xshell/ 这样的编译期路径 */
11
9
  export declare const fpd_root: string;
12
- export declare function set_inspection_limit(limit?: number): void;
13
- export declare function set_printing_compiled_js(enabled: boolean): void;
14
- export declare function parse_code(code: string): ts.SourceFile;
15
- export declare function generate_code(stmts: Statement[]): string;
16
- export declare function compile_ts({ fp, code, print, save, }?: {
17
- fp?: string;
18
- code?: string;
19
- print?: boolean;
20
- save?: boolean;
21
- }): Promise<string>;
22
- /** tsconfig.compilerOptions */
23
- export declare let ts_options: any;
24
- export declare let ts_options_commonjs: any;
25
- export declare let ts_options_commonjs_repl: any;
26
- export declare function load_tsconfig(): Promise<void>;
27
- export declare function eval_ts(code: string): Promise<any>;
28
- export declare function repl_ts(code: string): Promise<any>;
29
10
  export declare function start_repl(): Promise<void>;
30
11
  export declare function stop(): Promise<void>;
31
12
  export declare function exit(): Promise<void>;
package/repl.js CHANGED
@@ -1,255 +1,15 @@
1
- import nvm from 'vm';
2
1
  import repl from 'repl';
3
2
  import process from 'process';
4
3
  import { fileURLToPath } from 'url';
5
- import ts from 'typescript';
6
- const { factory, createPrinter, SyntaxKind, NodeFlags, ModifierFlags, isIdentifier, isNamedImports, isImportDeclaration, isAwaitExpression, isExpressionStatement, isVariableStatement, isNamespaceImport, isClassDeclaration, isCallExpression, isReturnStatement, isBinaryExpression, isFunctionDeclaration, } = ts;
7
4
  import { path } from './path.js';
8
5
  import { t } from './i18n/instance.js';
9
6
  import './prototype.js';
10
- import { log_line, delay, inspect, set_inspect_options, Timer, delta2str } from './utils.js';
11
- import { fread, fwrite } from './file.js';
7
+ import { delay, set_inspect_options, delta2str } from './utils.js';
12
8
  import { Remote } from './net.js';
13
9
  set_inspect_options();
14
10
  let server;
15
- let inspection_limit = 10000;
16
- let printing_compiled_js = false;
17
11
  /** 谨慎使用,webpack 打包后可能会变成 /d:/1/mod/node_modules/xshell/ 这样的编译期路径 */
18
12
  export const fpd_root = path.dirname(fileURLToPath(import.meta.url)) + '/';
19
- export function set_inspection_limit(limit = 10000) {
20
- if (limit === -1)
21
- limit = 50 * 10 ** 4;
22
- inspection_limit = limit;
23
- }
24
- export function set_printing_compiled_js(enabled) {
25
- printing_compiled_js = enabled;
26
- }
27
- // ------------------------------------ Code Compilers, Transformers
28
- function resolve_kind(kind) {
29
- return kind === SyntaxKind.VariableStatement ? 'VariableStatement' : SyntaxKind[kind];
30
- }
31
- function print_ast(nodes, sourceFile) {
32
- function print_node(node) {
33
- console.log(`${resolve_kind(node.kind)}: ${node.getText(sourceFile)}`);
34
- }
35
- if (Array.isArray(nodes))
36
- nodes.forEach(node => { print_node(node); });
37
- else
38
- print_node(nodes);
39
- }
40
- export function parse_code(code) {
41
- return ts.createSourceFile('repl.ts', code, ts.ScriptTarget.ESNext);
42
- }
43
- export function generate_code(stmts) {
44
- return createPrinter({ omitTrailingSemicolon: true, removeComments: false, newLine: ts.NewLineKind.LineFeed })
45
- .printFile(factory.updateSourceFile(ts.createSourceFile('output.ts', '', ts.ScriptTarget.ESNext), stmts));
46
- }
47
- function trans_import_2_require(import_decl) {
48
- if (!isImportDeclaration(import_decl))
49
- return import_decl;
50
- const { importClause: import_clause, moduleSpecifier: module_specifier } = import_decl;
51
- // require('module_specifier')
52
- const require_module_stmt = factory.createCallExpression(factory.createIdentifier('require'), [], [module_specifier]);
53
- // aaa, bbb as ccc, default as ddd, ...
54
- // (import_clause.namedBindings as NamedImports).elements
55
- return [
56
- // import mod from 'mod'
57
- // globalThis.mod = __importDefault(require("mod")).default
58
- ...import_clause.name ? [
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
- ] : [],
61
- // import * as mod from 'mod'
62
- // globalThis.mod = __importStar(require("mod"))
63
- ...import_clause.namedBindings && isNamespaceImport(import_clause.namedBindings) ? [
64
- factory.createExpressionStatement(factory.createAssignment(factory.createPropertyAccessExpression(factory.createIdentifier('globalThis'), import_clause.namedBindings.name), factory.createCallExpression(factory.createIdentifier('__importStar'), [], [require_module_stmt])))
65
- ] : [],
66
- // import { element2 as element2_, element3 as element3_, other, default as mod2 } from 'mod2'
67
- ...import_clause.namedBindings && isNamedImports(import_clause.namedBindings) ? [
68
- // var { element2: element2_, element3 } = __importDefault(require("mod"))
69
- factory.createVariableStatement([], [factory.createVariableDeclaration(factory.createObjectBindingPattern(
70
- // ImportSpecifier[] -> ObjectBindingPattern.BindingElement[]
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(globalThis, { element2_, element3 })
73
- factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Object'), 'assign'), [],
74
- // ImportSpecifier[] -> ObjectBindingPattern.BindingElement[]
75
- [factory.createIdentifier('globalThis'), factory.createObjectLiteralExpression(import_clause.namedBindings.elements.map(import_specifier => factory.createShorthandPropertyAssignment(import_specifier.name)), false)])),
76
- factory.createExpressionStatement(import_clause.namedBindings.elements.length === 1 ?
77
- // (a)
78
- factory.createParenthesizedExpression(import_clause.namedBindings.elements[0].name)
79
- : // ({ a: xxx, b: xxx })
80
- factory.createObjectLiteralExpression(import_clause.namedBindings.elements.map(import_specifier => factory.createShorthandPropertyAssignment(import_specifier.name)), false))
81
- ] : [],
82
- ];
83
- }
84
- /** export function foo () { } */
85
- function trans_export_stmt(stmt) {
86
- if (ts.isExportAssignment(stmt))
87
- return factory.createExpressionStatement(stmt.expression);
88
- function is_export_modifier(modifier) {
89
- return modifier.flags & ModifierFlags.Export || modifier.flags & ModifierFlags.ExportDefault || modifier.kind === SyntaxKind.ExportKeyword;
90
- }
91
- // @ts-ignore
92
- if (stmt.modifiers?.some(modifier => is_export_modifier(modifier))) {
93
- // @ts-ignore
94
- stmt.modifiers = factory.createNodeArray(
95
- // @ts-ignore
96
- stmt.modifiers.filter(modifier => !is_export_modifier(modifier)));
97
- return stmt;
98
- }
99
- return stmt;
100
- }
101
- /** const a = 123, b = 234, { c, d: e } = obj */
102
- function trans_variable_decl_2_var(var_decl) {
103
- if (!isVariableStatement(var_decl))
104
- return var_decl;
105
- // @ts-ignore
106
- var_decl.declarationList.flags = NodeFlags.None;
107
- return var_decl;
108
- }
109
- function trans_class_decl_2_expr(class_decl) {
110
- if (!isClassDeclaration(class_decl))
111
- return class_decl;
112
- // export class C { } 这样的语句已经在 trans_export_stmt 中被去掉了 export modifier
113
- return factory.createVariableStatement([], factory.createVariableDeclarationList([
114
- factory.createVariableDeclaration(class_decl.name, undefined, undefined, factory.createClassExpression(undefined, class_decl.name, undefined, class_decl.heritageClauses, class_decl.members))
115
- ]));
116
- }
117
- function get_expr_of_stmt(statement) {
118
- if (isExpressionStatement(statement))
119
- return statement;
120
- if (isVariableStatement(statement)) {
121
- const { declarations } = statement.declarationList;
122
- return factory.createExpressionStatement(declarations.length === 1 ?
123
- (() => {
124
- const { name } = declarations[0];
125
- if (isIdentifier(name)) // const a = c
126
- return name;
127
- else // const { a, b } = c
128
- return factory.createObjectLiteralExpression(name.elements
129
- .filter(({ name }) => name && isIdentifier(name))
130
- .map(({ name }) => factory.createShorthandPropertyAssignment(name)));
131
- })()
132
- :
133
- factory.createObjectLiteralExpression(declarations.map(var_decl => factory.createShorthandPropertyAssignment(var_decl.name)), true));
134
- }
135
- if (isFunctionDeclaration(statement))
136
- return factory.createExpressionStatement(statement.name);
137
- return statement;
138
- }
139
- function trans_return_2_expr(stmt) {
140
- if (!isReturnStatement(stmt))
141
- return stmt;
142
- return factory.createExpressionStatement(stmt.expression);
143
- }
144
- function assign_var_decl(var_decl, destination = 'globalThis') {
145
- if (!isVariableStatement(var_decl))
146
- return [var_decl];
147
- // VariableDeclaration[] -> ObjectLiteral[]
148
- // const a = 123, b = 234, { c, d: e } = obj
149
- const obj_literal = factory.createObjectLiteralExpression(var_decl.declarationList.declarations.map(declaration => isIdentifier(declaration.name) ?
150
- [factory.createShorthandPropertyAssignment(declaration.name)]
151
- : // ObjectBindingPattern
152
- declaration.name.elements.map((element) => factory.createShorthandPropertyAssignment(element.name))).flat(), true);
153
- return [
154
- var_decl,
155
- factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Object'), 'assign'), [], [factory.createIdentifier(destination), obj_literal])),
156
- factory.createExpressionStatement(obj_literal)
157
- ];
158
- }
159
- function return_last_expr(statements) {
160
- if (!statements.length)
161
- return statements;
162
- const last_expr = get_expr_of_stmt(statements.last);
163
- if (isExpressionStatement(last_expr))
164
- return [...statements.slice(0, -1), factory.createReturnStatement(last_expr.expression)];
165
- else
166
- return statements;
167
- }
168
- function wrap_await_stmt(statements, code) {
169
- function wrap(stmts) {
170
- return [
171
- factory.createExpressionStatement(factory.createCallExpression(factory.createFunctionExpression([factory.createModifier(SyntaxKind.AsyncKeyword)], undefined, 'async_wrapper', [], [], undefined, factory.createBlock(return_last_expr(stmts.map(statement => assign_var_decl(statement)).flat()), true)), [], [])),
172
- ];
173
- }
174
- if (!code.includes('await'))
175
- return statements;
176
- if (code.includes('await') && !code.includes('async '))
177
- return wrap(statements);
178
- if (statements.some(stmt => (isExpressionStatement(stmt) && isAwaitExpression(stmt.expression) ||
179
- isVariableStatement(stmt) && stmt.declarationList.declarations.some(var_decl => var_decl.initializer && isAwaitExpression(var_decl.initializer)) ||
180
- isExpressionStatement(stmt) && isCallExpression(stmt.expression) && stmt.expression.arguments.some(expr => isAwaitExpression(expr)) ||
181
- isExpressionStatement(stmt) && isBinaryExpression(stmt.expression) && isAwaitExpression(stmt.expression.right))))
182
- return wrap(statements);
183
- return statements;
184
- }
185
- export async function compile_ts({ fp, code, print = printing_compiled_js, save = false, } = {}) {
186
- if (!code && fp)
187
- code = await fread(fp);
188
- const source_file = parse_code(code);
189
- let statements = [...source_file.statements];
190
- statements = statements.map(trans_import_2_require).flat();
191
- statements = statements.map(trans_export_stmt);
192
- statements = statements.map(trans_class_decl_2_expr);
193
- statements = wrap_await_stmt(statements, code);
194
- statements = statements.map(trans_variable_decl_2_var);
195
- statements = statements.map(trans_return_2_expr);
196
- if (statements.length) {
197
- const last_stmt = statements[statements.length - 1];
198
- const last_expr = get_expr_of_stmt(last_stmt);
199
- if (last_expr !== last_stmt)
200
- statements = [...statements, last_expr];
201
- }
202
- code = generate_code(statements);
203
- let { diagnostics, outputText: output_text } = ts.transpileModule(code, { compilerOptions: ts_options_commonjs_repl });
204
- if (diagnostics.length) {
205
- console.log(diagnostics.join('\n\n\n'));
206
- log_line();
207
- }
208
- if (print) {
209
- console.log(output_text);
210
- log_line();
211
- }
212
- if (fp && save)
213
- await fwrite(fp.replace(/\.ts(x?)$/, '.js$1'), output_text);
214
- return output_text;
215
- }
216
- /** tsconfig.compilerOptions */
217
- export let ts_options;
218
- export let ts_options_commonjs;
219
- export let ts_options_commonjs_repl;
220
- export async function load_tsconfig() {
221
- const fp = `${fpd_root}tsconfig.json`;
222
- ({ config: { compilerOptions: ts_options } } = ts.parseConfigFileTextToJson(fp, await fread(fp, { print: false })));
223
- ts_options_commonjs = {
224
- ...ts_options,
225
- module: 'CommonJS',
226
- incremental: false,
227
- // determine CommonJS module require method
228
- esModuleInterop: true,
229
- };
230
- ts_options_commonjs_repl = {
231
- ...ts_options,
232
- module: 'CommonJS',
233
- esModuleInterop: true,
234
- // nvm.runInThisContext doesn't support inline source map
235
- sourceMap: false,
236
- };
237
- }
238
- let eval_id = 0;
239
- export async function eval_ts(code) {
240
- return nvm.runInThisContext(await compile_ts({ code }), `repl/${eval_id++}.ts`);
241
- }
242
- // ------------------------------------ repl
243
- export async function repl_ts(code) {
244
- console.log(); // 离之前的输出有一个空行
245
- const timer = new Timer();
246
- const __ = globalThis.__ = await eval_ts(code);
247
- timer.stop();
248
- console.log(inspect(__, { limit: inspection_limit }));
249
- if (timer.get() >= 500)
250
- timer.print();
251
- return __;
252
- }
253
13
  export async function start_repl() {
254
14
  // ------------ 加载库
255
15
  console.log(t('xshell 开始启动').yellow);
@@ -269,7 +29,6 @@ export async function start_repl() {
269
29
  process.title = 'xshell';
270
30
  await Promise.all([
271
31
  pollute_global(),
272
- load_tsconfig(),
273
32
  (async () => {
274
33
  // --- http server
275
34
  let { Server } = await import('./server.js');
@@ -281,9 +40,6 @@ export async function start_repl() {
281
40
  console.log('echo:', data);
282
41
  return [data];
283
42
  },
284
- async repl_ts({ data: [code] }) {
285
- return [await repl_ts(code)];
286
- }
287
43
  }
288
44
  })
289
45
  });
@@ -323,10 +79,6 @@ export async function pollute_global() {
323
79
  exports: globalThis
324
80
  });
325
81
  await Promise.all([
326
- pollute_module_default_export('lodash/omit.js', 'omit'),
327
- pollute_module_default_export('lodash/sortBy.js', 'sort_by'),
328
- pollute_module_default_export('lodash/groupBy.js', 'group_by'),
329
- pollute_module_default_export('qs', 'qs'),
330
82
  pollute_module_exports('./path.js'),
331
83
  pollute_module_exports('./prototype.js'),
332
84
  pollute_module_exports('./utils.js'),
package/server.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  /// <reference types="node" resolution-mode="require"/>
3
3
  import { type Server as HttpServer } from 'http';
4
- import { WebSocketServer } from 'ws';
5
- import { default as Koa, type Context, type Next } from 'koa';
6
- import { type UserAgentContext } from 'koa-useragent';
4
+ import type { WebSocketServer } from 'ws';
5
+ import type { default as Koa, Context, Next } from 'koa';
6
+ import type { UserAgentContext } from 'koa-useragent';
7
7
  declare module 'koa' {
8
8
  interface Request {
9
9
  /** 经过 decodeURIComponent 后,在路径重写之前的路径 */
package/server.js CHANGED
@@ -3,16 +3,7 @@ import zlib from 'zlib';
3
3
  import { createReadStream } from 'fs';
4
4
  import { Readable } from 'stream';
5
5
  import util from 'util';
6
- // --- 3rd party
7
- import qs from 'qs';
8
- import resolve_safely from 'resolve-path';
9
- import { WebSocketServer } from 'ws';
10
- import { contentType } from 'mime-types';
11
- // --- koa & koa middleware
12
- import { default as Koa } from 'koa';
13
- import KoaCors from '@koa/cors';
14
- import KoaCompress from 'koa-compress';
15
- import { userAgent as KoaUserAgent } from 'koa-useragent';
6
+ import querystring from 'querystring';
16
7
  // --- my libs
17
8
  import { t } from './i18n/instance.js';
18
9
  import { request as _request } from './net.js';
@@ -45,6 +36,11 @@ export class Server {
45
36
  }
46
37
  /** start http server and listen */
47
38
  async start() {
39
+ const { default: Koa } = await import('koa');
40
+ const { default: KoaCors } = await import('@koa/cors');
41
+ const { default: KoaCompress } = await import('koa-compress');
42
+ const { userAgent: KoaUserAgent } = await import('koa-useragent');
43
+ const { WebSocketServer } = await import('ws');
48
44
  // --- init koa app
49
45
  let app = new Koa();
50
46
  app.on('error', (error, ctx) => {
@@ -138,7 +134,7 @@ export class Server {
138
134
  if (ctx.is('application/json') || ctx.is('text/plain'))
139
135
  request.body = JSON.parse(req.body.toString());
140
136
  else if (ctx.is('application/x-www-form-urlencoded'))
141
- request.body = qs.parse(req.body.toString());
137
+ request.body = querystring.parse(req.body.toString());
142
138
  else if (ctx.is('multipart/form-data'))
143
139
  throw new Error('multipart/form-data is not supported');
144
140
  else
@@ -315,6 +311,7 @@ export class Server {
315
311
  fp = fp.slice(root.length);
316
312
  if (fp.startsWith('/'))
317
313
  fp = fp.slice(1);
314
+ const { default: resolve_safely } = await import('resolve-path');
318
315
  try {
319
316
  fp = resolve_safely(root, fp).fp;
320
317
  }
@@ -347,8 +344,9 @@ export class Server {
347
344
  if (download)
348
345
  response.set('content-disposition', `attachment; filename="${encodeURIComponent(fp.fname)}"`);
349
346
  if (!response.get('content-type')) {
347
+ const { contentType: get_content_type } = await import('mime-types');
350
348
  const fext = fp.fext;
351
- response.set('content-type', (this.js_exts.has(fext) ? 'text/javascript; chatset=utf-8' : contentType(fp.fext))
349
+ response.set('content-type', (this.js_exts.has(fext) ? 'text/javascript; chatset=utf-8' : get_content_type(fp.fext))
352
350
  || 'application/octet-stream');
353
351
  }
354
352
  if (request.fresh) {
package/utils.browser.js CHANGED
@@ -138,7 +138,7 @@ export function delta2str(delta) {
138
138
  return '0 ms';
139
139
  // [1, 1000) ms
140
140
  if (delta < 1000)
141
- return `${delta} ms`;
141
+ return `${delta.toFixed(0)} ms`;
142
142
  // 1.123 s
143
143
  if (1000 <= delta && delta < 1000 * 60)
144
144
  return `${(delta / 1000).toFixed(1)} s`;
package/utils.js CHANGED
@@ -101,7 +101,7 @@ export function delta2str(delta) {
101
101
  return '0 ms';
102
102
  // [1, 1000) ms
103
103
  if (delta < 1000)
104
- return `${delta} ms`;
104
+ return `${delta.toFixed(0)} ms`;
105
105
  // 1.123 s
106
106
  if (1000 <= delta && delta < 1000 * 60)
107
107
  return `${(delta / 1000).toFixed(1)} s`;