xshell 1.0.49 → 1.0.51
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 +8 -1
- package/file.js +66 -2
- package/i18n/dict.json +3 -0
- package/net.js +4 -2
- package/package.json +8 -8
- package/server.d.ts +1 -4
- package/server.js +9 -11
- package/utils.browser.d.ts +2 -0
- package/utils.browser.js +13 -0
- package/utils.d.ts +2 -0
- package/utils.js +15 -2
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
|
|
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/
|
|
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.
|
|
3
|
+
"version": "1.0.51",
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
108
|
-
"@typescript-eslint/parser": "^6.
|
|
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.
|
|
111
|
-
"eslint-plugin-xlint": "^1.0.
|
|
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/server.d.ts
CHANGED
|
@@ -68,10 +68,7 @@ export declare class Server {
|
|
|
68
68
|
format_ua(headers: IncomingHttpHeaders | IncomingHttp2Headers): string;
|
|
69
69
|
proxy(ctx: Context, url: string | URL, headers_?: Record<string, string>): Promise<void>;
|
|
70
70
|
static filter_response_headers(headers: Headers): {};
|
|
71
|
-
try_send(ctx: Context,
|
|
72
|
-
root: string;
|
|
73
|
-
log_404?: boolean;
|
|
74
|
-
}): Promise<boolean>;
|
|
71
|
+
try_send(ctx: Context, fpd_root: string, fp: string, log_404: boolean): Promise<boolean>;
|
|
75
72
|
/** 将 body 设置为 fp 所指文件的 ReadStream
|
|
76
73
|
检查 fp 是否合法,设置对应的 content-type,支持缓存,支持分块下载
|
|
77
74
|
- ctx: Koa.Context
|
package/server.js
CHANGED
|
@@ -308,27 +308,25 @@ export class Server {
|
|
|
308
308
|
headers_[key] = value;
|
|
309
309
|
return headers_;
|
|
310
310
|
}
|
|
311
|
-
async try_send(ctx,
|
|
311
|
+
async try_send(ctx, fpd_root, fp, log_404) {
|
|
312
312
|
const { request: { _path, path, method }, response, } = ctx;
|
|
313
|
-
if (
|
|
313
|
+
if (response.body !== undefined || response.status !== 404)
|
|
314
314
|
return true;
|
|
315
315
|
if (method !== 'HEAD' && method !== 'GET')
|
|
316
316
|
return false;
|
|
317
|
-
function _log_404() {
|
|
318
|
-
let s = `${' '.repeat(11)} ${method.toLowerCase()} 404: ${path}`;
|
|
319
|
-
if (_path !== path)
|
|
320
|
-
s += ` ${_path.bracket()}`;
|
|
321
|
-
console.log(s.red);
|
|
322
|
-
}
|
|
323
317
|
try {
|
|
324
|
-
await this.fsend(ctx, fp, { root });
|
|
318
|
+
await this.fsend(ctx, fp, { root: fpd_root });
|
|
325
319
|
return true;
|
|
326
320
|
}
|
|
327
321
|
catch (error) {
|
|
328
322
|
if (error.status !== 404)
|
|
329
323
|
throw error;
|
|
330
|
-
if (log_404)
|
|
331
|
-
|
|
324
|
+
if (log_404) {
|
|
325
|
+
let s = `${' '.repeat(11)} ${method.toLowerCase()} 404: ${path}`;
|
|
326
|
+
if (_path !== path)
|
|
327
|
+
s += ` ${_path.bracket()}`;
|
|
328
|
+
console.log(s.red);
|
|
329
|
+
}
|
|
332
330
|
return false;
|
|
333
331
|
}
|
|
334
332
|
}
|
package/utils.browser.d.ts
CHANGED
|
@@ -51,6 +51,8 @@ export declare class Lock<TResource = void> {
|
|
|
51
51
|
export declare function pause(milliseconds?: number): Promise<void>;
|
|
52
52
|
/** 字符串字典序比较 */
|
|
53
53
|
export declare function strcmp(l: string, r: string): 0 | 1 | -1;
|
|
54
|
+
export declare function get<TReturn = any>(obj: any, keypath: string): TReturn;
|
|
55
|
+
export declare function invoke<TReturn = any>(obj: any, funcpath: string, args: any[]): TReturn;
|
|
54
56
|
/** 拼接 TypedArrays 生成一个完整的 Uint8Array */
|
|
55
57
|
export declare function concat(arrays: ArrayBufferView[]): Uint8Array;
|
|
56
58
|
/** 时间间隔 (milliseconds) 格式化 */
|
package/utils.browser.js
CHANGED
|
@@ -126,6 +126,19 @@ export function strcmp(l, r) {
|
|
|
126
126
|
return -1;
|
|
127
127
|
return 1;
|
|
128
128
|
}
|
|
129
|
+
export function get(obj, keypath) {
|
|
130
|
+
let obj_ = obj;
|
|
131
|
+
for (const key of keypath.split('.'))
|
|
132
|
+
obj_ = obj_[key];
|
|
133
|
+
return obj_;
|
|
134
|
+
}
|
|
135
|
+
export function invoke(obj, funcpath, args) {
|
|
136
|
+
const paths = funcpath.split('.');
|
|
137
|
+
let obj_ = obj;
|
|
138
|
+
for (let i = 0; i < paths.length - 1; i++)
|
|
139
|
+
obj_ = obj_[paths[i]];
|
|
140
|
+
return obj_[paths.at(-1)].call(obj_, ...args);
|
|
141
|
+
}
|
|
129
142
|
/** 拼接 TypedArrays 生成一个完整的 Uint8Array */
|
|
130
143
|
export function concat(arrays) {
|
|
131
144
|
let length = 0;
|
package/utils.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export declare function unique<T>(iterable: T[] | Iterable<T>, selector?: string
|
|
|
22
22
|
export declare function sort_keys<T>(obj: T): T;
|
|
23
23
|
/** 字符串字典序比较 */
|
|
24
24
|
export declare function strcmp(l: string, r: string): 0 | 1 | -1;
|
|
25
|
+
export declare function get<TReturn = any>(obj: any, keypath: string): TReturn;
|
|
26
|
+
export declare function invoke<TReturn = any>(obj: any, funcpath: string, args: any[]): TReturn;
|
|
25
27
|
/** 拼接 TypedArrays 生成一个完整的 Uint8Array */
|
|
26
28
|
export declare function concat(arrays: ArrayBufferView[]): Uint8Array;
|
|
27
29
|
export declare function typed_array_to_buffer(view: ArrayBufferView): Buffer;
|
package/utils.js
CHANGED
|
@@ -86,6 +86,19 @@ export function strcmp(l, r) {
|
|
|
86
86
|
return -1;
|
|
87
87
|
return 1;
|
|
88
88
|
}
|
|
89
|
+
export function get(obj, keypath) {
|
|
90
|
+
let obj_ = obj;
|
|
91
|
+
for (const key of keypath.split('.'))
|
|
92
|
+
obj_ = obj_[key];
|
|
93
|
+
return obj_;
|
|
94
|
+
}
|
|
95
|
+
export function invoke(obj, funcpath, args) {
|
|
96
|
+
const paths = funcpath.split('.');
|
|
97
|
+
let obj_ = obj;
|
|
98
|
+
for (let i = 0; i < paths.length - 1; i++)
|
|
99
|
+
obj_ = obj_[paths[i]];
|
|
100
|
+
return obj_[paths.at(-1)].call(obj_, ...args);
|
|
101
|
+
}
|
|
89
102
|
/** 拼接 TypedArrays 生成一个完整的 Uint8Array */
|
|
90
103
|
export function concat(arrays) {
|
|
91
104
|
let length = 0;
|
|
@@ -405,8 +418,8 @@ export async function stream_to_buffer(stream) {
|
|
|
405
418
|
export async function* stream_to_lines(stream) {
|
|
406
419
|
let buf = '';
|
|
407
420
|
for await (const chunk of stream) {
|
|
408
|
-
let
|
|
409
|
-
for (; (i = chunk.indexOf('\n', j)) >= 0;) {
|
|
421
|
+
let j = 0;
|
|
422
|
+
for (let i = 0; (i = chunk.indexOf('\n', j)) >= 0;) {
|
|
410
423
|
let line = chunk.slice(j, i);
|
|
411
424
|
if (buf) {
|
|
412
425
|
line = buf + line;
|