xshell 1.2.50 → 1.2.53

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/builder.js CHANGED
@@ -2,7 +2,7 @@ import { fileURLToPath } from 'url';
2
2
  import { not_empty } from "./prototype.js";
3
3
  import { noprint } from "./process.js";
4
4
  import { Lock, filter_values } from "./utils.js";
5
- import { fcopy, fmkdir, fwrite, fread } from "./file.js";
5
+ import { fcopy, fmkdir, fwrite, fread, print_info } from "./file.js";
6
6
  import { path } from "./path.js";
7
7
  const monaco_files = [
8
8
  'loader.js',
@@ -563,7 +563,7 @@ export class Bundler {
563
563
  await this.build(print);
564
564
  await this.close();
565
565
  }
566
- async build_htmls(print = { info: true, files: false }) {
566
+ async build_htmls(print = print_info) {
567
567
  await Promise.all(Object.entries(this.htmls).map(async ([fp_html, { entry = 'index.js', device_viewport: device_width = false, icon, manifest, dependencies: _dependencies = [], title, scripts, mscripts, notice = false, heads, }]) => {
568
568
  const html_template = '<!doctype html>\n' +
569
569
  '<html>\n' +
@@ -606,7 +606,7 @@ export class Bundler {
606
606
  if (print.files)
607
607
  console.log(`${this.name} 的所有 html 页面构建完成`);
608
608
  }
609
- async build_single_js(print = { info: true, files: false }) {
609
+ async build_single_js(print = print_info) {
610
610
  const { js, entry } = this.single_js;
611
611
  await fwrite(`${this.fpd_out}${js}`, [
612
612
  ...this.resolve_dependency_assets(this.dependencies, false, { production: this.production })
@@ -643,7 +643,7 @@ export class Bundler {
643
643
  ];
644
644
  }).flat();
645
645
  }
646
- async copy_files({ dependencies: _dependencies = this.dependencies, production = this.production, assets = this.assets, fpd_root = this.fpd_root, fpd_out = this.fpd_out, print = { info: true, files: false } } = {}) {
646
+ async copy_files({ dependencies: _dependencies = this.dependencies, production = this.production, assets = this.assets, fpd_root = this.fpd_root, fpd_out = this.fpd_out, print = print_info } = {}) {
647
647
  if (print.files)
648
648
  console.log(`复制 ${this.name} 的依赖文件到 ${this.fpd_out}`);
649
649
  if (_dependencies.length)
package/file.d.ts CHANGED
@@ -6,6 +6,20 @@ export type FileHandle = fsp.FileHandle & {
6
6
  fp: string;
7
7
  };
8
8
  export declare const encodings: readonly ["utf-8", "gb18030", "shift-jis", "utf-16le"];
9
+ export declare const print_files: {
10
+ readonly info: true;
11
+ readonly files: true;
12
+ };
13
+ export declare const print_info: {
14
+ readonly info: true;
15
+ readonly files: false;
16
+ };
17
+ export declare const print_info_options: {
18
+ readonly print: {
19
+ readonly info: true;
20
+ readonly files: false;
21
+ };
22
+ };
9
23
  export declare const ramdisk: boolean;
10
24
  /** fp 所指向的 文件/ 文件夹 是否存在
11
25
  Does the file/folder pointed to by fp exist? */
@@ -19,7 +33,8 @@ export declare function fexists(fp: string, { print }?: {
19
33
 
20
34
  - flags: `'r'` https://nodejs.org/docs/latest/api/fs.html#file-system-flags
21
35
  - options?:
22
- - mode?: `'0o666'` Sets the file mode (permission and sticky bits) if the file is created. */
36
+ - mode?: `'0o666'` Sets the file mode (permission and sticky bits) if the file is created.
37
+ - print?: `false` */
23
38
  export declare function fopen(fp: string, flags?: string | number, { mode, print }?: {
24
39
  mode?: fs.Mode;
25
40
  print?: boolean;
@@ -61,11 +76,11 @@ export interface FWriteOptions {
61
76
  print?: boolean;
62
77
  mtime?: number;
63
78
  }
64
- /** 写入 data 到 fp 路径所指的文件
79
+ /** 写入 data 到 fp 路径或句柄所指的文件
65
80
  会在因不存在父文件夹导致写入失败时,自动创建父文件夹,并再次尝试写入
66
81
  最好预先创建父文件夹,减少文件系统操作,提升性能
67
82
  返回写入的文件路径
68
- - fp: 目标文件完整路径
83
+ - fp: 目标文件完整路径或句柄
69
84
  - data: 支持下面几种类型
70
85
  - string: 写入文本
71
86
  - Uint8Array, Buffer: 写入二进制 buffer
@@ -268,4 +283,24 @@ export declare function ftail(fp: string, handler: (lines: string[]) => void | P
268
283
  export declare function freplace(fp: string, pattern: string | RegExp, replacement: string, { print }?: {
269
284
  print?: boolean;
270
285
  }): Promise<void>;
286
+ interface FSyncOptions {
287
+ print?: {
288
+ info: boolean;
289
+ files: boolean;
290
+ };
291
+ delete?: boolean;
292
+ }
293
+ /** 同步文件或文件夹 fp_src 到 fp_dst ,返回 fp_dst
294
+ - fp_src: 源文件或文件夹
295
+ - fp_dst: 目标文件或文件夹
296
+ - options?:
297
+ - print?: `print_info`
298
+ - delete?: `false` */
299
+ export declare function fsync(fp_src: string, fp_dst: string, { print, delete: _delete }?: FSyncOptions): Promise<string>;
300
+ /** 根据对象的 fp 属性进行字典序排序 */
301
+ export declare function fp_sorter(l: {
302
+ fp: string;
303
+ }, r: {
304
+ fp: string;
305
+ }): 0 | 1 | -1;
271
306
  export declare function log_action(action: string, fp_src: string, fp_dst: string, sep?: string): void;
package/file.js CHANGED
@@ -4,10 +4,13 @@ import { t } from "./i18n/instance.js";
4
4
  import { noop, to_json } from "./prototype.js";
5
5
  import { pack, parse } from "./io.js";
6
6
  import { path } from "./path.js";
7
- import { check, Lock, WritableMemoryStream, url_width, throttle, decode } from "./utils.js";
7
+ import { check, Lock, WritableMemoryStream, url_width, throttle, decode, strcmp } from "./utils.js";
8
8
  import { noprint } from "./process.js";
9
9
  export { fsp };
10
10
  export const encodings = ['utf-8', 'gb18030', 'shift-jis', 'utf-16le'];
11
+ export const print_files = { info: true, files: true };
12
+ export const print_info = { info: true, files: false };
13
+ export const print_info_options = { print: print_info };
11
14
  export const ramdisk = fexists('T:/TEMP/', noprint);
12
15
  /** fp 所指向的 文件/ 文件夹 是否存在
13
16
  Does the file/folder pointed to by fp exist? */
@@ -24,7 +27,8 @@ export function fexists(fp, { print = true } = {}) {
24
27
 
25
28
  - flags: `'r'` https://nodejs.org/docs/latest/api/fs.html#file-system-flags
26
29
  - options?:
27
- - mode?: `'0o666'` Sets the file mode (permission and sticky bits) if the file is created. */
30
+ - mode?: `'0o666'` Sets the file mode (permission and sticky bits) if the file is created.
31
+ - print?: `false` */
28
32
  export async function fopen(fp, flags = 'r', { mode, print } = {}) {
29
33
  if (print)
30
34
  console.log(t('打开文件'), fp);
@@ -824,6 +828,109 @@ export async function freplace(fp, pattern, replacement, { print = true } = {})
824
828
  await fwrite(fp, (await fread(fp, { print }))
825
829
  .replaceAll(pattern, replacement), { print });
826
830
  }
831
+ /** 同步文件或文件夹 fp_src 到 fp_dst ,返回 fp_dst
832
+ - fp_src: 源文件或文件夹
833
+ - fp_dst: 目标文件或文件夹
834
+ - options?:
835
+ - print?: `print_info`
836
+ - delete?: `false` */
837
+ export async function fsync(fp_src, fp_dst, { print = print_files, delete: _delete = false } = {}) {
838
+ if (print.info)
839
+ log_action('同步', fp_src, fp_dst);
840
+ if (fp_src.isdir) {
841
+ let dst_stats;
842
+ try {
843
+ dst_stats = (await flist(fp_dst, { stats: true, deep: true, print: false })).sort(fp_sorter);
844
+ }
845
+ catch (error) {
846
+ if (error.code === 'ENOENT')
847
+ return fcopy(fp_src, fp_dst, { print: print.info });
848
+ else
849
+ throw error;
850
+ }
851
+ const src_stats = (await flist(fp_src, { deep: true, print: false, stats: true }))
852
+ .sort(fp_sorter);
853
+ let p = 0, q = 0;
854
+ let tasks = [];
855
+ let fpd_changeds = [];
856
+ let fpd_deleteds = [];
857
+ const sync_change = ({ fp }, modify) => {
858
+ if (fpd_changeds.some(fpd_created => fp.startsWith(fpd_created)))
859
+ return;
860
+ if (fp.isdir)
861
+ fpd_changeds.push(fp);
862
+ if (print.files)
863
+ log_action(`同步${modify ? '修改' : '新增'}`, `${fp_src}${fp}`, `${fp_dst}${fp}`);
864
+ tasks.push(fcopy(`${fp_src}${fp}`, `${fp_dst}${fp}`, noprint));
865
+ };
866
+ const sync_delete = ({ fp }) => {
867
+ if (fpd_deleteds.some(fpd_deleted => fp.startsWith(fpd_deleted)))
868
+ return;
869
+ if (fp.isdir)
870
+ fpd_deleteds.push(fp);
871
+ if (print.files)
872
+ console.log('同步删除', `${fp_dst}${fp}`);
873
+ tasks.push(fdelete(`${fp_dst}${fp}`, noprint));
874
+ };
875
+ for (; p < src_stats.length && q < dst_stats.length;) {
876
+ const src = src_stats[p];
877
+ const dst = dst_stats[q];
878
+ if (src.fp === dst.fp) {
879
+ if (!src.fp.isdir && !compare_stat(src, dst))
880
+ sync_change(src, true);
881
+ ++p;
882
+ ++q;
883
+ }
884
+ else if (src.fp < dst.fp) { // dst 缺少了文件 / 文件夹
885
+ sync_change(src, false);
886
+ ++p;
887
+ }
888
+ else { // dst 多了文件 / 文件夹
889
+ if (_delete)
890
+ sync_delete(dst);
891
+ ++q;
892
+ }
893
+ }
894
+ for (; p < src_stats.length; ++p)
895
+ sync_change(src_stats[p], false);
896
+ if (_delete)
897
+ for (; q < dst_stats.length; ++q)
898
+ sync_delete(dst_stats[q]);
899
+ await Promise.all(tasks);
900
+ }
901
+ else {
902
+ let same = true;
903
+ const [src_stat, dst_stat] = await Promise.all([
904
+ fstat(fp_src),
905
+ (async () => {
906
+ try {
907
+ return await fstat(fp_dst);
908
+ }
909
+ catch (error) {
910
+ if (error.code === 'ENOENT')
911
+ same = false;
912
+ else
913
+ throw error;
914
+ }
915
+ })()
916
+ ]);
917
+ if (same)
918
+ same = compare_stat(src_stat, dst_stat);
919
+ if (same)
920
+ log_action('跳过相同', fp_src, fp_dst);
921
+ else
922
+ await fcopy(fp_src, fp_dst, { print: print.info });
923
+ }
924
+ return fp_dst;
925
+ }
926
+ /** 根据对象的 fp 属性进行字典序排序 */
927
+ export function fp_sorter(l, r) {
928
+ return strcmp(l.fp, r.fp);
929
+ }
930
+ /** 返回修改时间与大小是否相同 */
931
+ function compare_stat(src, dst) {
932
+ return src.size === dst.size && Math.abs(Number(src.mtimeMs) - Number(dst.mtimeMs)) <= 1;
933
+ }
827
934
  export function log_action(action, fp_src, fp_dst, sep = '->') {
828
935
  console.log(`${`${action} ${fp_src}`.pad(url_width)} ${sep} ${fp_dst}`);
829
936
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.2.50",
3
+ "version": "1.2.53",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -53,13 +53,13 @@
53
53
  "@babel/parser": "^7.27.7",
54
54
  "@babel/traverse": "^7.27.7",
55
55
  "@koa/cors": "^5.0.0",
56
- "@stylistic/eslint-plugin": "^5.0.0",
56
+ "@stylistic/eslint-plugin": "^5.1.0",
57
57
  "@svgr/webpack": "^8.1.0",
58
58
  "@types/sass-loader": "^8.0.9",
59
59
  "@types/ws": "^8.18.1",
60
- "@typescript-eslint/eslint-plugin": "^8.35.0",
61
- "@typescript-eslint/parser": "^8.35.0",
62
- "@typescript-eslint/utils": "^8.35.0",
60
+ "@typescript-eslint/eslint-plugin": "^8.35.1",
61
+ "@typescript-eslint/parser": "^8.35.1",
62
+ "@typescript-eslint/utils": "^8.35.1",
63
63
  "archiver": "^7.0.1",
64
64
  "chalk": "^5.4.1",
65
65
  "commander": "^14.0.0",
@@ -69,7 +69,7 @@
69
69
  "eslint-plugin-import": "^2.32.0",
70
70
  "eslint-plugin-react": "^7.37.5",
71
71
  "https-proxy-agent": "^7.0.6",
72
- "i18next": "^25.2.1",
72
+ "i18next": "^25.3.0",
73
73
  "i18next-scanner": "^4.6.0",
74
74
  "koa": "^3.0.0",
75
75
  "koa-compress": "^5.1.1",
@@ -92,7 +92,7 @@
92
92
  "undici": "^7.11.0",
93
93
  "webpack": "^5.99.9",
94
94
  "webpack-bundle-analyzer": "^4.10.2",
95
- "ws": "^8.18.2"
95
+ "ws": "^8.18.3"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@babel/types": "^7.27.7",
@@ -103,7 +103,7 @@
103
103
  "@types/koa": "^2.15.0",
104
104
  "@types/koa-compress": "^4.0.6",
105
105
  "@types/mime-types": "^3.0.1",
106
- "@types/node": "^24.0.6",
106
+ "@types/node": "^24.0.8",
107
107
  "@types/react": "^19.1.8",
108
108
  "@types/tough-cookie": "^4.0.5",
109
109
  "@types/ua-parser-js": "^0.7.39",
@@ -224,6 +224,7 @@ export declare const empty: (value: any) => boolean;
224
224
  export declare const is_key_type: IsKeyType;
225
225
  type IsKeyType = (key: any) => key is string | number | symbol;
226
226
  export declare function rethrow(error: Error): void;
227
+ export declare function to_snake_case(str: string): string;
227
228
  export declare function to_method_property_descriptors(methods: {
228
229
  [name: string]: Function;
229
230
  }): PropertyDescriptorMap;
@@ -13,6 +13,12 @@ export const is_key_type = ((key) => key_types.includes(typeof key));
13
13
  export function rethrow(error) {
14
14
  throw error;
15
15
  }
16
+ export function to_snake_case(str) {
17
+ return str.replace(/([A-Z])/g, '_$1')
18
+ .toLowerCase()
19
+ .replace('-', '_')
20
+ .strip_if_start('_');
21
+ }
16
22
  export function to_method_property_descriptors(methods) {
17
23
  return Object.fromEntries(Object.entries(methods)
18
24
  .map(([name, value]) => ([name, {
@@ -120,10 +126,7 @@ Object.defineProperties(String.prototype, {
120
126
  return new RegExp(this.replace(new RegExp(`[${replace_chars}]`, 'g'), '\\$&'), flags);
121
127
  },
122
128
  to_snake_case() {
123
- return this.replace(/([A-Z])/g, '_$1')
124
- .toLowerCase()
125
- .replace('-', '_')
126
- .replace(/^_+/, '');
129
+ return to_snake_case(this);
127
130
  },
128
131
  refmt(pattern, pattern_, preservations = '', flags = '', transformer = (name, value) => value || '', pattern_placeholder = /\{.*?\}/g) {
129
132
  // --- 转换 pattern 为 pattern_regx
package/prototype.d.ts CHANGED
@@ -254,6 +254,7 @@ export declare const empty: (value: any) => boolean;
254
254
  export declare const is_key_type: IsKeyType;
255
255
  type IsKeyType = (key: any) => key is string | number | symbol;
256
256
  export declare function rethrow(error: Error): void;
257
+ export declare function to_snake_case(str: string): string;
257
258
  export declare function to_method_property_descriptors(methods: {
258
259
  [name: string]: Function;
259
260
  }): PropertyDescriptorMap;
package/prototype.js CHANGED
@@ -14,6 +14,12 @@ export const is_key_type = ((key) => key_types.includes(typeof key));
14
14
  export function rethrow(error) {
15
15
  throw error;
16
16
  }
17
+ export function to_snake_case(str) {
18
+ return str.replace(/([A-Z])/g, '_$1')
19
+ .toLowerCase()
20
+ .replace('-', '_')
21
+ .strip_if_start('_');
22
+ }
17
23
  export function to_method_property_descriptors(methods) {
18
24
  return Object.fromEntries(Object.entries(methods)
19
25
  .map(([name, value]) => ([name, {
@@ -124,10 +130,7 @@ if (!globalThis.my_prototype_defined) {
124
130
  return new RegExp(this.replace(new RegExp(`[${replace_chars}]`, 'g'), '\\$&'), flags);
125
131
  },
126
132
  to_snake_case() {
127
- return this.replace(/([A-Z])/g, '_$1')
128
- .toLowerCase()
129
- .replace('-', '_')
130
- .replace(/^_+/, '');
133
+ return to_snake_case(this);
131
134
  },
132
135
  refmt(pattern, pattern_, preservations = '', flags = '', transformer = (name, value) => value || '', pattern_placeholder = /\{.*?\}/g) {
133
136
  // --- 转换 pattern 为 pattern_regx
@@ -16,6 +16,10 @@ export declare function unique<TObj>(iterable: TObj[] | Iterable<TObj>, mapper?:
16
16
  export declare function seq<T = number>(n: number, generator?: (index: number) => T): T[];
17
17
  /** 将 keys, values 数组按对应的顺序组合成一个对象 */
18
18
  export declare function zip_object<TValue>(keys: (string | number)[], values: TValue[]): Record<string, TValue>;
19
+ /** 映射对象中的 keys, 返回新对象
20
+ - obj: 对象
21
+ - mapper?: `to_snake_case` (key: string) => string 或者 Record<string, string> */
22
+ export declare function map_keys<TReturn>(obj: any, mapper?: ((key: string) => string) | Record<string, string>): TReturn;
19
23
  /** 过滤对象中的 values, 返回新对象
20
24
  - obj
21
25
  - filter?: `not_empty` */
package/utils.browser.js CHANGED
@@ -1,4 +1,4 @@
1
- import { is_key_type, select, not_empty, ident } from "./prototype.browser.js";
1
+ import { is_key_type, select, not_empty, ident, to_snake_case } from "./prototype.browser.js";
2
2
  import { t } from "./i18n/instance.js";
3
3
  export function assert(assertion, message) {
4
4
  if (!assertion) {
@@ -54,6 +54,16 @@ export function zip_object(keys, values) {
54
54
  return obj;
55
55
  }, {});
56
56
  }
57
+ /** 映射对象中的 keys, 返回新对象
58
+ - obj: 对象
59
+ - mapper?: `to_snake_case` (key: string) => string 或者 Record<string, string> */
60
+ export function map_keys(obj, mapper = to_snake_case) {
61
+ return Object.fromEntries(Object.entries(obj)
62
+ .map(typeof mapper === 'function' ?
63
+ ([key, value]) => [mapper(key), value]
64
+ :
65
+ ([key, value]) => [mapper[key] || key, value]));
66
+ }
57
67
  /** 过滤对象中的 values, 返回新对象
58
68
  - obj
59
69
  - filter?: `not_empty` */
package/utils.d.ts CHANGED
@@ -25,8 +25,10 @@ export declare function seq<T = number>(n: number, generator?: (index: number) =
25
25
  export declare function sort_keys<TObj>(obj: TObj): TObj;
26
26
  /** 将 keys, values 数组按对应的顺序组合成一个对象 */
27
27
  export declare function zip_object<TValue>(keys: (string | number)[], values: TValue[]): Record<string, TValue>;
28
- /** 映射对象中的 keys, 返回新对象 */
29
- export declare function map_keys<TObj>(obj: TObj, mapper: (key: string) => string): TObj;
28
+ /** 映射对象中的 keys, 返回新对象
29
+ - obj: 对象
30
+ - mapper?: `to_snake_case` (key: string) => string 或者 Record<string, string> */
31
+ export declare function map_keys<TReturn>(obj: any, mapper?: ((key: string) => string) | Record<string, string>): TReturn;
30
32
  /** 映射对象中的 values, 返回新对象 */
31
33
  export declare function map_values<TValue, TNewValue>(obj: {
32
34
  [key: string]: TValue;
package/utils.js CHANGED
@@ -2,7 +2,7 @@ import { Stream, Writable, Transform } from 'stream';
2
2
  import util from 'util';
3
3
  import timers from 'timers/promises';
4
4
  import { t } from "./i18n/instance.js";
5
- import { select, not_empty, is_key_type, noop, ident } from "./prototype.js";
5
+ import { select, not_empty, is_key_type, noop, ident, to_snake_case } from "./prototype.js";
6
6
  /** `180` 输出字符宽度 */
7
7
  export const output_width = 180;
8
8
  export const url_width = 52;
@@ -81,10 +81,15 @@ export function zip_object(keys, values) {
81
81
  return obj;
82
82
  }, {});
83
83
  }
84
- /** 映射对象中的 keys, 返回新对象 */
85
- export function map_keys(obj, mapper) {
84
+ /** 映射对象中的 keys, 返回新对象
85
+ - obj: 对象
86
+ - mapper?: `to_snake_case` (key: string) => string 或者 Record<string, string> */
87
+ export function map_keys(obj, mapper = to_snake_case) {
86
88
  return Object.fromEntries(Object.entries(obj)
87
- .map(([key, value]) => [mapper(key), value]));
89
+ .map(typeof mapper === 'function' ?
90
+ ([key, value]) => [mapper(key), value]
91
+ :
92
+ ([key, value]) => [mapper[key] || key, value]));
88
93
  }
89
94
  /** 映射对象中的 values, 返回新对象 */
90
95
  export function map_values(obj, mapper) {