xshell 1.0.49 → 1.0.50

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
@@ -21,7 +21,7 @@ export declare function fexists(fp: string, { print }?: {
21
21
  - flags: `'r'`
22
22
  - options?:
23
23
  - mode?: `'0o666'` Sets the file mode (permission and sticky bits) if the file is created. */
24
- export declare function fopen(fp: string, flags: string | number, { mode, print }?: {
24
+ export declare function fopen(fp: string, flags?: string | number, { mode, print }?: {
25
25
  mode?: fs.Mode;
26
26
  print?: boolean;
27
27
  }): Promise<fsp.FileHandle & {
@@ -91,6 +91,8 @@ export declare function flist(fpd: string, options?: FListOptions & {
91
91
  }): Promise<FStats[]>;
92
92
  export declare function flist(fpd: string, options?: FListOptions): Promise<string[]>;
93
93
  export declare function fstat(fp: string): Promise<FStats>;
94
+ export declare function flstat(fp: string): Promise<FStats>;
95
+ export declare function ffstat(handle: fsp.FileHandle): Promise<fs.BigIntStats>;
94
96
  /** 删除文件或文件夹 delete files or folders
95
97
  - fp: 文件或文件夹的完整路径 The full path to the file or folder
96
98
  - options?:
@@ -155,6 +157,11 @@ export declare function flink(fp_real: string, fp_link: string, { junction, prin
155
157
  junction?: boolean;
156
158
  print?: boolean;
157
159
  }): Promise<void>;
160
+ export declare let fwatchers: Record<string, fs.FSWatcher>;
161
+ /** 跟踪文本文件追加的内容,类似 tail -f */
162
+ export declare function ftail(fp: string, handler: (lines: string[]) => void | Promise<void>, { print }?: {
163
+ print?: boolean;
164
+ }): Promise<fs.FSWatcher>;
158
165
  /** 打开一个文件并搜索替换某个 pattern */
159
166
  export declare function freplace(fp: string, pattern: string | RegExp, replacement: string, { print }?: {
160
167
  print?: boolean;
package/file.js CHANGED
@@ -3,7 +3,7 @@ import { isUint8Array } from 'util/types';
3
3
  import { path } from './path.js';
4
4
  import { t } from './i18n/instance.js';
5
5
  import { to_json } from './prototype.js';
6
- import { assert } from './utils.js';
6
+ import { assert, Lock } from './utils.js';
7
7
  export const encodings = ['utf-8', 'gb18030', 'shift-jis', 'utf-16le'];
8
8
  /** fp 所指向的 文件/ 文件夹 是否存在
9
9
  Does the file/folder pointed to by fp exist? */
@@ -22,7 +22,7 @@ export function fexists(fp, { print = true } = {}) {
22
22
  - flags: `'r'`
23
23
  - options?:
24
24
  - mode?: `'0o666'` Sets the file mode (permission and sticky bits) if the file is created. */
25
- export async function fopen(fp, flags, { mode, print } = {}) {
25
+ export async function fopen(fp, flags = 'r', { mode, print } = {}) {
26
26
  if (print)
27
27
  console.log(t('打开文件'), fp);
28
28
  return Object.assign(await fsp.open(fp, flags, mode), { fp, flags, mode });
@@ -163,6 +163,22 @@ export async function fstat(fp) {
163
163
  stat.fp = fp;
164
164
  return stat;
165
165
  }
166
+ export async function flstat(fp) {
167
+ assert(path.isAbsolute(fp), t("flstat: 参数 fp: '{{fp}}' 必须是绝对路径", { fp }));
168
+ let stat = await fsp.lstat(fp, { bigint: true });
169
+ stat.fp = fp;
170
+ return stat;
171
+ }
172
+ export async function ffstat(handle) {
173
+ return new Promise((resolve, reject) => {
174
+ fs.fstat(handle.fd, { bigint: true }, (error, stats) => {
175
+ if (error)
176
+ reject(error);
177
+ else
178
+ resolve(stats);
179
+ });
180
+ });
181
+ }
166
182
  /** 删除文件或文件夹 delete files or folders
167
183
  - fp: 文件或文件夹的完整路径 The full path to the file or folder
168
184
  - options?:
@@ -294,6 +310,54 @@ export async function flink(fp_real, fp_link, { junction = false, print = true }
294
310
  else
295
311
  fsp.symlink(fp_real, fp_link, is_fpd_real ? 'dir' : 'file');
296
312
  }
313
+ export let fwatchers = {};
314
+ /** 跟踪文本文件追加的内容,类似 tail -f */
315
+ export async function ftail(fp, handler, { print = true } = {}) {
316
+ fwatchers[fp]?.close();
317
+ const { size } = await fstat(fp);
318
+ let pointer = Number(size);
319
+ let lock = new Lock(await fopen(fp));
320
+ let fbuf = new Uint8Array(2 ** 20);
321
+ let strbuf = '';
322
+ let decoder = new TextDecoder();
323
+ if (print)
324
+ console.log('开始跟踪追加内容', fp);
325
+ const { default: throttle } = await import('lodash/throttle.js');
326
+ const onchange_throttled = throttle(async () => {
327
+ if (lock.locked)
328
+ return;
329
+ await lock.request(async (handle) => {
330
+ const { bytesRead } = await handle.read(fbuf, 0, fbuf.length, pointer);
331
+ pointer += bytesRead;
332
+ const chunk = decoder.decode(fbuf.subarray(0, bytesRead), { stream: true });
333
+ let lines = [];
334
+ let j = 0;
335
+ for (let i = 0; (i = chunk.indexOf('\n', j)) >= 0;) {
336
+ let line = chunk.slice(j, i);
337
+ if (strbuf) {
338
+ line = strbuf + line;
339
+ strbuf = '';
340
+ }
341
+ j = i + 1;
342
+ lines.push(line);
343
+ }
344
+ strbuf = chunk.slice(j);
345
+ await handler(lines);
346
+ });
347
+ }, 250);
348
+ let watcher = fs.watch(fp, event => {
349
+ if (event === 'change')
350
+ onchange_throttled();
351
+ else {
352
+ console.error(`被监听的文件 ${fp.quote()} 出现了 rename 事件,结束 ftail`);
353
+ watcher.close();
354
+ }
355
+ });
356
+ watcher.on('error', error => {
357
+ console.error(error);
358
+ });
359
+ return fwatchers[fp] = watcher;
360
+ }
297
361
  /** 打开一个文件并搜索替换某个 pattern */
298
362
  export async function freplace(fp, pattern, replacement, { print = true } = {}) {
299
363
  await fwrite(fp, (await fread(fp, { print }))
package/i18n/dict.json CHANGED
@@ -343,5 +343,8 @@
343
343
  },
344
344
  "xshell 启动成功,正在监听: http://localhost:8421": {
345
345
  "en": "xshell started successfully and is listening: http://localhost:8421"
346
+ },
347
+ "flstat: 参数 fp: '{{fp}}' 必须是绝对路径": {
348
+ "en": "flstat: parameter fp: '{{fp}}' must be an absolute path"
346
349
  }
347
350
  }
package/net.js CHANGED
@@ -87,7 +87,9 @@ export async function request(url, { method, queries, headers: _headers, body, t
87
87
  // --- headers, http/2 开始都用小写的 headers
88
88
  let headers = new Headers({
89
89
  'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja-JP;q=0.6,ja;q=0.5',
90
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
90
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
91
+ 'sec-ch-ua-platform': '"Windows"',
92
+ 'sec-ch-ua-platform-version': '"15.0.0"',
91
93
  });
92
94
  if (body !== undefined)
93
95
  headers.set('content-type', type);
@@ -541,7 +543,7 @@ export class Remote {
541
543
  作为 websocket 连接接收方,必传 websocket 参数
542
544
  发送或连接出错时自动清理 message.id 对应的 handler */
543
545
  async send(message, websocket) {
544
- assert(!message.data || message.data.every(arg => arg !== undefined), 'message.data 数组中不能有 undefined 的项, 因为 json 序列化后会变为 null');
546
+ assert(!message.data || message.data.every(arg => arg !== undefined), t('message.data 数组中不能有 undefined 的项, 因为 json 序列化后会变为 null'));
545
547
  if (this.print)
546
548
  console.log('remote.send:', message);
547
549
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.0.49",
3
+ "version": "1.0.50",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "interactive programming"
17
17
  ],
18
18
  "engines": {
19
- "node": ">=20.5.0",
19
+ "node": ">=20.5.1",
20
20
  "vscode": ">=1.79.0"
21
21
  },
22
22
  "author": "ShenHongFei <shen.hongfei@outlook.com> (https://github.com/ShenHongFei)",
@@ -77,7 +77,7 @@
77
77
  "strip-ansi": "^7.1.0",
78
78
  "through2": "^4.0.2",
79
79
  "tough-cookie": "^4.1.3",
80
- "tslib": "^2.6.1",
80
+ "tslib": "^2.6.2",
81
81
  "typescript": "^5.1.6",
82
82
  "ua-parser-js": "2.0.0-alpha.2",
83
83
  "undici": "^5.23.0",
@@ -97,18 +97,18 @@
97
97
  "@types/koa-compress": "^4.0.3",
98
98
  "@types/lodash": "^4.14.197",
99
99
  "@types/mime-types": "^2.1.1",
100
- "@types/node": "^20.4.10",
100
+ "@types/node": "^20.5.1",
101
101
  "@types/react": "^18.2.20",
102
102
  "@types/through2": "^2.0.38",
103
103
  "@types/tough-cookie": "^4.0.2",
104
104
  "@types/ua-parser-js": "^0.7.36",
105
105
  "@types/vinyl-fs": "^3.0.2",
106
106
  "@types/vscode": "^1.81.0",
107
- "@typescript-eslint/eslint-plugin": "^6.3.0",
108
- "@typescript-eslint/parser": "^6.3.0",
107
+ "@typescript-eslint/eslint-plugin": "^6.4.0",
108
+ "@typescript-eslint/parser": "^6.4.0",
109
109
  "eslint": "^8.47.0",
110
- "eslint-plugin-react": "^7.33.1",
111
- "eslint-plugin-xlint": "^1.0.6"
110
+ "eslint-plugin-react": "^7.33.2",
111
+ "eslint-plugin-xlint": "^1.0.7"
112
112
  },
113
113
  "scripts": {
114
114
  "start": "node --title=xshell --inspect=0.0.0.0:8420 ./xshell.js",
package/utils.js CHANGED
@@ -405,8 +405,8 @@ export async function stream_to_buffer(stream) {
405
405
  export async function* stream_to_lines(stream) {
406
406
  let buf = '';
407
407
  for await (const chunk of stream) {
408
- let i = 0, j = 0;
409
- for (; (i = chunk.indexOf('\n', j)) >= 0;) {
408
+ let j = 0;
409
+ for (let i = 0; (i = chunk.indexOf('\n', j)) >= 0;) {
410
410
  let line = chunk.slice(j, i);
411
411
  if (buf) {
412
412
  line = buf + line;