xshell 1.2.29 → 1.2.31

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.
Files changed (5) hide show
  1. package/fflate.d.ts +359 -0
  2. package/fflate.js +1175 -0
  3. package/file.d.ts +42 -1
  4. package/file.js +222 -4
  5. package/package.json +7 -7
package/file.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { promises as fsp, default as fs } from 'fs';
2
+ import type { UnzipFileFilter } from './fflate.ts';
2
3
  export { fsp };
3
4
  export type Encoding = 'utf-8' | 'gb18030' | 'shift-jis' | 'utf-16le';
4
5
  export type FileHandle = fsp.FileHandle & {
@@ -71,7 +72,7 @@ export interface FWriteOptions {
71
72
  - any: 通过 JSON.stringify 转为文本后写入文件
72
73
  - options?:
73
74
  - print?: `true`
74
- - mtime?: 在写入后设置修改时间 */
75
+ - mtime?: 在写入后设置修改时间 (utc 毫秒数) */
75
76
  export declare function fwrite(fp: string, data: string | Uint8Array | any, options?: FWriteOptions): Promise<string>;
76
77
  export declare function fwrite(fp: FileHandle, data: string | Uint8Array | any, options?: FWriteOptions): Promise<FileHandle>;
77
78
  export declare function fwrite(fp: string | FileHandle, data: string | Uint8Array | any, options?: FWriteOptions): Promise<string | FileHandle>;
@@ -220,6 +221,46 @@ export declare function fzip(data: string | Record<string, string | Uint8Array>,
220
221
  - info?: `true` 开始压缩、压缩完成
221
222
  - files?: `true` 打印压缩文件列表 */
222
223
  export declare function zip(data: string | Record<string, string | Uint8Array>, options?: ZipOptions): Promise<Buffer>;
224
+ export interface UnzipOptions {
225
+ encoding?: Encoding;
226
+ dryrun?: boolean;
227
+ print?: {
228
+ info: boolean;
229
+ files: boolean;
230
+ };
231
+ strip?: boolean;
232
+ clean?: boolean;
233
+ filter?: UnzipFileFilter;
234
+ }
235
+ /** 解压 zip 包至文件夹,返回 fpd_out
236
+ - zip:
237
+ - 包路径 (string) 或
238
+ - 包数据 (Uint8Array | ArrayBuffer)
239
+ - fpd_out: 解压到文件夹的完整路径 (string)
240
+ - options?:
241
+ - encoding?: `utf-8` 压缩包文件名编码
242
+ - dryrun?: 只是打印一下文件名列表,看看编码是否正确,不实际解压
243
+ - print?:
244
+ - info?: `true` 开始解压、解压完成
245
+ - files?: `true` 打印解压缩的文件列表
246
+ - strip?: `true` 压缩包内只有一个顶层文件夹时自动去除该层级
247
+ - clean?: `true` 忽略解压 ftrashes 这些垃圾文件
248
+ - filter?: 用于过滤要解压的文件, 返回 falsy 值时忽略这个文件,要注意 file.name 为完整的相对路径(没有 strip) */
249
+ export declare function funzip(zip: string | Uint8Array | ArrayBuffer, fpd_out: string, options?: UnzipOptions): Promise<string>;
250
+ /** 解压 zip 包至内存,返回解压后的数据 (Record<文件或文件夹 (以 / 结尾) 的相对路径, Uint8Array>)
251
+ - zip:
252
+ - 压缩文件路径 (string) 或
253
+ - 压缩文件内容 (buffer)
254
+ - options?:
255
+ - encoding?: `utf-8` 压缩包文件名编码
256
+ - dryrun?: 只是打印一下文件名列表,看看编码是否正确,不实际解压
257
+ - print?:
258
+ - info?: `true` 开始解压、解压完成
259
+ - files?: `true` 打印解压缩的文件列表
260
+ - strip?: `true` 压缩包内只有一个顶层文件夹时自动去除该层级
261
+ - clean?: `true` 忽略解压 ftrashes 这些垃圾文件
262
+ - filter?: 用于过滤要解压的文件, 返回 falsy 值时忽略这个文件,要注意 file.name 为完整的相对路径(没有 strip) */
263
+ export declare function unzip(zip: string | Uint8Array | ArrayBuffer, options?: UnzipOptions): Promise<Record<string, Uint8Array>>;
223
264
  export declare let fwatchers: Record<string, fs.FSWatcher>;
224
265
  /** 跟踪文本文件追加的内容,类似 tail -f */
225
266
  export declare function ftail(fp: string, handler: (lines: string[]) => void | Promise<void>, { print }?: {
package/file.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { promises as fsp, default as fs } from 'fs';
2
- import { isUint8Array } from 'util/types';
2
+ import { isArrayBuffer, isUint8Array } from 'util/types';
3
3
  import { t } from "./i18n/instance.js";
4
- import { to_json } from "./prototype.js";
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 } from "./utils.js";
7
+ import { check, Lock, WritableMemoryStream, url_width, throttle, decode } 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'];
@@ -100,7 +100,8 @@ export async function fwrite(fp, data, { print = true, mtime } = {}) {
100
100
  let handle = await fopen(fp, 'w', noprint);
101
101
  try {
102
102
  await handle.write(data, 0);
103
- await handle.utimes(mtime, mtime);
103
+ const mseconds = mtime / 1000;
104
+ await handle.utimes(mseconds, mseconds);
104
105
  }
105
106
  finally {
106
107
  await handle.close();
@@ -544,6 +545,223 @@ async function _zip(data, fp_zip, { dirname, print = { files: true, info: true }
544
545
  console.log(`压缩完成,总大小 ${size.to_fsize_str()}`);
545
546
  return fp_zip || ostream.pbuffer;
546
547
  }
548
+ /** 解压 zip 包至文件夹,返回 fpd_out
549
+ - zip:
550
+ - 包路径 (string) 或
551
+ - 包数据 (Uint8Array | ArrayBuffer)
552
+ - fpd_out: 解压到文件夹的完整路径 (string)
553
+ - options?:
554
+ - encoding?: `utf-8` 压缩包文件名编码
555
+ - dryrun?: 只是打印一下文件名列表,看看编码是否正确,不实际解压
556
+ - print?:
557
+ - info?: `true` 开始解压、解压完成
558
+ - files?: `true` 打印解压缩的文件列表
559
+ - strip?: `true` 压缩包内只有一个顶层文件夹时自动去除该层级
560
+ - clean?: `true` 忽略解压 ftrashes 这些垃圾文件
561
+ - filter?: 用于过滤要解压的文件, 返回 falsy 值时忽略这个文件,要注意 file.name 为完整的相对路径(没有 strip) */
562
+ export async function funzip(zip, fpd_out, options) {
563
+ await _unzip(zip, fpd_out, options);
564
+ return fpd_out;
565
+ }
566
+ /** 解压 zip 包至内存,返回解压后的数据 (Record<文件或文件夹 (以 / 结尾) 的相对路径, Uint8Array>)
567
+ - zip:
568
+ - 压缩文件路径 (string) 或
569
+ - 压缩文件内容 (buffer)
570
+ - options?:
571
+ - encoding?: `utf-8` 压缩包文件名编码
572
+ - dryrun?: 只是打印一下文件名列表,看看编码是否正确,不实际解压
573
+ - print?:
574
+ - info?: `true` 开始解压、解压完成
575
+ - files?: `true` 打印解压缩的文件列表
576
+ - strip?: `true` 压缩包内只有一个顶层文件夹时自动去除该层级
577
+ - clean?: `true` 忽略解压 ftrashes 这些垃圾文件
578
+ - filter?: 用于过滤要解压的文件, 返回 falsy 值时忽略这个文件,要注意 file.name 为完整的相对路径(没有 strip) */
579
+ export async function unzip(zip, options) {
580
+ return _unzip(zip, undefined, options);
581
+ }
582
+ async function _unzip(zip, fpd_out, { encoding = 'utf-8', dryrun = false, print = {
583
+ files: true,
584
+ info: true
585
+ }, strip = true, clean = true, filter: _filter } = {}) {
586
+ const str_out = typeof fpd_out === 'string' ? fpd_out : '内存';
587
+ let fp_zip = 'buffer';
588
+ let nfiles = 0;
589
+ let size_uncompressed = 0;
590
+ if (isArrayBuffer(zip))
591
+ zip = new Uint8Array(zip);
592
+ else if (typeof zip === 'string') {
593
+ check(['zip', 'apk', 'crx', 'vsix'].includes(zip.fext), 'fp_src 应该以 zip 等后缀结尾');
594
+ fp_zip = zip;
595
+ zip = await fread(zip, { encoding: 'binary', print: false });
596
+ }
597
+ if (print.info)
598
+ log_action(`开始${dryrun ? '测试' : ''}解压`, `${fp_zip} (${zip.length.to_fsize_str()})`, str_out);
599
+ if (!dryrun && fpd_out)
600
+ await fmkdir(fpd_out, { print: print.info });
601
+ let dryrun_unzipped = {};
602
+ const { unzipSync } = await import("./fflate.js");
603
+ let unzipped = unzipSync(zip, {
604
+ decoder: encoding === 'utf-8'
605
+ ? decode
606
+ : (() => {
607
+ let text_decoder = new TextDecoder(encoding);
608
+ return buf => text_decoder.decode(buf);
609
+ })(),
610
+ filter(file) {
611
+ const { name, originalSize } = file;
612
+ // 需要递归的清理 LICENSE, .husky/, .husky/pre-commit 等文件
613
+ if (clean) {
614
+ const lname = name.toLowerCase();
615
+ let to_clean = false;
616
+ // xxx/xxx/xxx/a.txt
617
+ // xxx/xxx/
618
+ // a.txt
619
+ // xxx/
620
+ // 处理每个文件夹部分
621
+ let ileft = 0, iright = 0;
622
+ for (; (iright = lname.indexOf('/', ileft) + 1) !== 0; ileft = iright)
623
+ if (ftrashes.has(lname.slice(ileft, iright))) {
624
+ to_clean = true;
625
+ break;
626
+ }
627
+ // 处理最后剩下的文件名
628
+ if (!to_clean && ileft < lname.length && ftrashes.has(lname.slice(ileft)))
629
+ to_clean = true;
630
+ if (to_clean) {
631
+ if (print.files)
632
+ console.log(`清理了 ${name}`);
633
+ return false;
634
+ }
635
+ }
636
+ if (!clean && name.startsWith('__MACOSX/') ||
637
+ _filter && !_filter(file))
638
+ return false;
639
+ else {
640
+ nfiles++;
641
+ size_uncompressed += originalSize;
642
+ if (dryrun) {
643
+ dryrun_unzipped[name] = null;
644
+ return false;
645
+ }
646
+ else
647
+ return true;
648
+ }
649
+ }
650
+ });
651
+ if (dryrun)
652
+ unzipped = dryrun_unzipped;
653
+ if (strip) {
654
+ let top_common_dir = '';
655
+ for (const key in unzipped) {
656
+ const islash = key.indexOf('/');
657
+ if (islash === -1) { // 顶层有文件
658
+ top_common_dir = '';
659
+ break;
660
+ }
661
+ else {
662
+ const top_dir = key.slice(0, islash + 1);
663
+ if (top_common_dir) {
664
+ if (top_common_dir !== top_dir) {
665
+ top_common_dir = '';
666
+ break;
667
+ }
668
+ }
669
+ else
670
+ top_common_dir = top_dir;
671
+ }
672
+ }
673
+ if (top_common_dir) {
674
+ let unzipped_ = {};
675
+ for (const key in unzipped) {
676
+ const key_ = key.slice(top_common_dir.length);
677
+ if (key_)
678
+ unzipped_[key_] = unzipped[key];
679
+ }
680
+ unzipped = unzipped_;
681
+ }
682
+ }
683
+ let pwrites = [];
684
+ for (const key in unzipped) {
685
+ const value = unzipped[key];
686
+ if (fpd_out && !dryrun) {
687
+ const fp = fpd_out + key;
688
+ if (fp.isdir)
689
+ await fmkdir(fp, noprint);
690
+ else {
691
+ let pwrite = fwrite(fp, value, noprint);
692
+ pwrite.catch(noop); // suppress PromiseRejectionHandledWarning: Promise rejection was handled asynchronously
693
+ pwrites.push(pwrite);
694
+ }
695
+ }
696
+ if (print.files)
697
+ console.log(`解压出 ${key}`);
698
+ }
699
+ if (fpd_out && !dryrun)
700
+ await Promise.all(pwrites);
701
+ if (print.info)
702
+ console.log(`解压了 ${nfiles} 个文件,解压后文件总大小为 ${size_uncompressed.to_fsize_str()}`);
703
+ if (!fpd_out || dryrun)
704
+ return unzipped;
705
+ }
706
+ const ftrashes = new Set([
707
+ '__MACOSX/',
708
+ '.babelrc.js',
709
+ '.babelrc',
710
+ '.ci',
711
+ '.circleci',
712
+ '.dockerignore',
713
+ '.DS_Store',
714
+ '.editorconfig',
715
+ '.eslintignore',
716
+ '.eslintrc.js',
717
+ '.eslintrc.json',
718
+ '.eslintrc.yaml',
719
+ '.eslintrc.yml',
720
+ '.eslintrc',
721
+ '.gitattributes',
722
+ '.github',
723
+ '.gitignore',
724
+ '.husky/',
725
+ '.jsbeautifyrc',
726
+ '.jshintrc',
727
+ '.mailmap',
728
+ '.markdownlint.js',
729
+ '.node-version',
730
+ '.npmignore',
731
+ '.prettierignore',
732
+ '.prettierrc.js',
733
+ '.prettierrc.json',
734
+ '.prettierrc.yaml',
735
+ '.prettierrc',
736
+ '.project',
737
+ '.releaserc.json',
738
+ '.stylelintrc.json',
739
+ '.travis.yml',
740
+ '.vscode/',
741
+ 'AUTHORS.txt',
742
+ 'AUTHORS',
743
+ 'bower',
744
+ 'CODE_OF_CONDUCT.md',
745
+ 'codecov.yml',
746
+ 'commitlint.config.js',
747
+ 'CONTRIBUTING.md',
748
+ 'COPING.md',
749
+ 'husky.config.js',
750
+ 'LICENCE.md',
751
+ 'LICENCE.txt',
752
+ 'LICENCE',
753
+ 'LICENSE.md',
754
+ 'LICENSE.txt',
755
+ 'LICENSE',
756
+ 'lint-staged.config.js',
757
+ 'open-bot.yaml',
758
+ 'package-lock.json',
759
+ 'pnpm-lock.yaml',
760
+ 'prettier.config.js',
761
+ 'stylelint.config.js',
762
+ 'tslint.json',
763
+ 'yarn.lock',
764
+ ].map(f => f.toLowerCase()));
547
765
  export let fwatchers = {};
548
766
  /** 跟踪文本文件追加的内容,类似 tail -f */
549
767
  export async function ftail(fp, handler, { print = true } = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.2.29",
3
+ "version": "1.2.31",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -67,14 +67,14 @@
67
67
  "commander": "^13.1.0",
68
68
  "css-loader": "^7.1.2",
69
69
  "emoji-regex": "^10.4.0",
70
- "eslint": "^9.23.0",
70
+ "eslint": "^9.24.0",
71
71
  "eslint-plugin-import": "^2.31.0",
72
- "eslint-plugin-react": "^7.37.4",
72
+ "eslint-plugin-react": "^7.37.5",
73
73
  "gulp-sort": "^2.0.0",
74
74
  "https-proxy-agent": "^7.0.6",
75
75
  "i18next": "^24.2.3",
76
76
  "i18next-scanner": "^4.6.0",
77
- "koa": "^2.16.0",
77
+ "koa": "^2.16.1",
78
78
  "koa-compress": "^5.1.1",
79
79
  "license-webpack-plugin": "^4.0.2",
80
80
  "map-stream": "^0.0.7",
@@ -84,7 +84,7 @@
84
84
  "react-i18next": "^15.4.1",
85
85
  "react-object-model": "^1.2.23",
86
86
  "resolve-path": "^1.4.0",
87
- "sass": "^1.86.2",
87
+ "sass": "^1.86.3",
88
88
  "sass-loader": "^16.0.5",
89
89
  "source-map-loader": "^5.0.0",
90
90
  "strip-ansi": "^7.1.0",
@@ -93,7 +93,7 @@
93
93
  "tough-cookie": "^5.1.2",
94
94
  "ts-loader": "^9.5.2",
95
95
  "tslib": "^2.8.1",
96
- "typescript": "^5.8.2",
96
+ "typescript": "^5.8.3",
97
97
  "ua-parser-js": "^2.0.3",
98
98
  "undici": "^7.7.0",
99
99
  "vinyl": "^3.0.0",
@@ -118,7 +118,7 @@
118
118
  "@types/tough-cookie": "^4.0.5",
119
119
  "@types/ua-parser-js": "^0.7.39",
120
120
  "@types/vinyl-fs": "^3.0.5",
121
- "@types/vscode": "^1.98.0",
121
+ "@types/vscode": "^1.99.0",
122
122
  "@types/webpack-bundle-analyzer": "^4.7.0"
123
123
  },
124
124
  "pnpm": {