xshell 1.3.52 → 1.3.54

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
@@ -109,27 +109,36 @@ export type FStats = fs.BigIntStats & {
109
109
  };
110
110
  export interface FListOptions {
111
111
  filter?: RegExp | ((fp: string) => any);
112
+ excludes?: string[] | Set<string>;
112
113
  deep?: boolean;
113
114
  absolute?: boolean;
114
115
  print?: boolean;
115
116
  stats?: boolean;
116
117
  best_effort?: boolean;
118
+ limit?: number;
119
+ }
120
+ interface FListContext {
121
+ count: number;
122
+ fpd: string;
123
+ excludes?: Set<string>;
117
124
  }
118
125
  /** 列出文件夹,默认返回文件名列表,也可以传入 stats 选项返回元数据
119
- - fpd: 文件夹完整路径 absolute path of directory
126
+ - fpd: 文件夹完整路径
120
127
  - options?:
121
- - deep?: `false` 递归遍历 recursively
122
- - absolute?: `false` 返回、打印完整路径而不是相对路径,为 false 时返回的 FStats.fp 也会被修改 Return, print full path instead of relative path, FStats.fp returned when false will also be modified
128
+ - deep?: `false` 递归遍历
129
+ - absolute?: `false` 返回、打印完整路径而不是相对路径,为 false 时返回的 FStats.fp 也会被修改
123
130
  - print?: `true`
124
131
  - filter?: `() => true` RegExp | (fp: string) => any
125
132
  注意当 deep = true 时被 filter 过滤掉的目录及目录中的文件不会包含在结果中
133
+ - excludes?: 如 ['.git/', 'node_modules/'], 指定忽略的文件、文件夹名称
126
134
  - stats?: `false` 启用后返回 FStats 列表
127
- - best_effort?: `false` 启用后,当遇到 stat 软链接文件不存在等错误时,会返回一个 fake_time FStats 文件,而不是抛出错误 */
135
+ - best_effort?: `false` 启用后,当遇到 stat 软链接文件不存在等错误时,会返回一个 fake_time FStats 文件,而不是抛出错误
136
+ - limit?: 递归模式下限制最大列出的文件数,达到限制后不再往更深层次递归读取文件夹 */
128
137
  export declare function flist(fpd: string): Promise<string[]>;
129
138
  export declare function flist(fpd: string, options?: FListOptions & {
130
139
  stats: true;
131
- }): Promise<FStats[]>;
132
- export declare function flist(fpd: string, options?: FListOptions): Promise<string[]>;
140
+ }, ctx?: FListContext): Promise<FStats[]>;
141
+ export declare function flist(fpd: string, options?: FListOptions, ctx?: FListContext): Promise<string[]>;
133
142
  export declare function fstat(fp: string | FileHandle): Promise<FStats>;
134
143
  export declare function flstat(fp: string): Promise<FStats>;
135
144
  export declare function ffstat(handle: fsp.FileHandle): Promise<fs.BigIntStats>;
package/file.js CHANGED
@@ -169,14 +169,13 @@ const fake_stats = {
169
169
  ctime: fake_time,
170
170
  birthtime: fake_time,
171
171
  };
172
- export async function flist(fpd, options = {}) {
173
- const { filter, deep = false, absolute = false, print = true, stats = false, best_effort = false } = options;
174
- check(path.isAbsolute(fpd), t("flist: 参数 fpd: '{{fpd}}' 必须是绝对路径", { fpd }));
175
- check(fpd.isdir, t("flist: 参数 fpd: '{{fpd}}' 必须以 / 结尾", { fpd }));
176
- if (!path.isAbsolute(fpd))
177
- throw new Error(t('参数 fpd: ') + fpd + t(' 必须是绝对路径'));
178
- if (!fpd.isdir)
179
- throw new Error(t('参数 fpd: ') + fpd + t(' 必须以 / 结尾'));
172
+ export async function flist(fpd, options = {}, ctx = { count: 0, fpd }) {
173
+ const { filter, deep = false, absolute = false, print = true, stats = false, best_effort = false, limit, excludes } = options;
174
+ check(path.isAbsolute(fpd), `flist: 参数 fpd: '${fpd}' 必须是绝对路径`);
175
+ check(fpd.isdir, `flist: 参数 fpd: '${fpd}' 必须以 / 结尾`);
176
+ // 初始化 ctx.excludes
177
+ if (excludes)
178
+ ctx.excludes ??= Array.isArray(excludes) ? new Set(excludes) : excludes;
180
179
  // readdir withFileTypes 参数在底层有什么区别,速度上有什么差异
181
180
  // 都调用了 uv_fs_scandir, 且调用参数相同,仅仅是 Node.js 侧的回调不同 AfterScanDir / AfterScanDirWithTypes
182
181
  // 回调中通过 uv_fs_scandir_next 获取到每个条目的信息,而 uv_fs_scandir_next 中都会读取 type
@@ -184,63 +183,55 @@ export async function flist(fpd, options = {}) {
184
183
  const files = await fsp.readdir(fpd, { withFileTypes: true, encoding: 'utf-8' });
185
184
  const filter_regexp = filter instanceof RegExp;
186
185
  const filter_fn = Boolean(filter && !filter_regexp);
187
- let fps = [];
186
+ let rfps = [];
188
187
  for (const file of files) {
189
- const fp = (absolute ? fpd : '') +
190
- file.name +
191
- (file.isDirectory() ? '/' : '');
192
- if (filter_regexp && !filter.test(fp))
193
- continue;
194
- if (filter_fn && !filter(fp))
188
+ const fname = file.name + (file.isDirectory() ? '/' : '');
189
+ const rfp = deep ? (fpd + fname).strip_start(ctx.fpd) : fname;
190
+ if (ctx.excludes?.has(rfp) ||
191
+ filter_regexp && !filter.test(rfp) ||
192
+ filter_fn && !filter(rfp))
195
193
  continue;
196
194
  if (print)
197
- console.log(fp);
198
- fps.push(fp);
195
+ console.log(absolute ? ctx.fpd + rfp : rfp);
196
+ rfps.push(rfp);
197
+ if (limit)
198
+ ++ctx.count;
199
199
  }
200
- async function _fstat(fp) {
200
+ async function _fstat(rfp) {
201
+ const fp = ctx.fpd + rfp;
201
202
  let _stats;
202
203
  try {
203
- _stats = await fstat(absolute ? fp : fpd + fp);
204
+ _stats = await fstat(fp);
204
205
  }
205
206
  catch (error) {
206
- if (best_effort)
207
- _stats = { ...fake_stats };
208
- else
207
+ if (!best_effort)
209
208
  throw error;
209
+ _stats = { ...fake_stats };
210
210
  }
211
- if (!absolute)
212
- _stats.fp = fp;
211
+ _stats.fp = absolute ? fp : rfp;
213
212
  return _stats;
214
213
  }
215
- if (deep)
216
- return (await Promise.all(
214
+ let results = !deep ?
215
+ stats ? await Promise.all(rfps.map(_fstat)) : rfps
216
+ : (await Promise.all(
217
217
  // 顶层文件/文件夹
218
- fps.map(async (fp) => {
218
+ rfps.map(async (rfp) => {
219
+ if (!rfp.isdir || limit && ctx.count >= limit)
220
+ return stats ? _fstat(rfp) : rfp;
219
221
  /** 顶层文件夹的大小 */
220
222
  let fpd_stats;
221
- return fp.isdir ?
222
- [
223
- stats ? (fpd_stats = await _fstat(fp)) : fp,
224
- ...(await flist(absolute ? fp : fpd + fp, options)).map((fp_or_stats) => {
225
- if (stats) {
226
- if (!absolute)
227
- fp_or_stats.fp = fp + fp_or_stats.fp;
228
- // 所有顶层文件夹中的文件(不包括文件夹)大小总和加起来作为文件夹大小
229
- if (!fp_or_stats.fp.isdir)
230
- fpd_stats.size += fp_or_stats.size;
231
- return fp_or_stats;
232
- }
233
- else
234
- return absolute ? fp_or_stats : fp + fp_or_stats;
235
- })
236
- ]
237
- :
238
- stats ? _fstat(fp) : fp;
223
+ const self = stats ? (fpd_stats = await _fstat(rfp)) : rfp;
224
+ const children = await flist(ctx.fpd + rfp, options, ctx);
225
+ if (stats)
226
+ children.forEach(s => {
227
+ fpd_stats.size += s.size;
228
+ });
229
+ return [self, ...children];
239
230
  }))).flat();
240
- else if (stats)
241
- return Promise.all(fps.map(_fstat));
242
- else
243
- return fps;
231
+ return absolute && fpd === ctx.fpd && !stats ?
232
+ results.map(rfp => ctx.fpd + rfp)
233
+ :
234
+ results;
244
235
  }
245
236
  export async function fstat(fp) {
246
237
  let stat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.3.52",
3
+ "version": "1.3.54",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -23,7 +23,6 @@
23
23
  "publish:npm": "npm publish --access=public",
24
24
  "publish:extension": "vsce publish"
25
25
  },
26
- "author": "ShenHongFei <shen.hongfei@outlook.com> (https://github.com/ShenHongFei)",
27
26
  "publisher": "ShenHongFei",
28
27
  "homepage": "https://www.npmjs.com/package/xshell",
29
28
  "icon": "xshell.png",
@@ -56,15 +55,15 @@
56
55
  "@stylistic/eslint-plugin": "^5.10.0",
57
56
  "@svgr/webpack": "^8.1.0",
58
57
  "@types/sass-loader": "^8.0.10",
59
- "@typescript-eslint/eslint-plugin": "^8.58.0",
60
- "@typescript-eslint/parser": "^8.58.0",
61
- "@typescript-eslint/utils": "^8.58.0",
58
+ "@typescript-eslint/eslint-plugin": "^8.58.1",
59
+ "@typescript-eslint/parser": "^8.58.1",
60
+ "@typescript-eslint/utils": "^8.58.1",
62
61
  "archiver": "^7.0.1",
63
62
  "chalk": "^5.6.2",
64
63
  "commander": "^14.0.3",
65
64
  "css-loader": "^7.1.4",
66
65
  "emoji-regex": "^10.6.0",
67
- "eslint": "^10.1.0",
66
+ "eslint": "^10.2.0",
68
67
  "eslint-plugin-react": "^7.37.5",
69
68
  "https-proxy-agent": "^9.0.0",
70
69
  "i18next": "25.8.1",
@@ -77,16 +76,16 @@
77
76
  "react": "^19.2.4",
78
77
  "react-i18next": "^17.0.2",
79
78
  "resolve-path": "^1.4.0",
80
- "sass": "^1.98.0",
79
+ "sass": "^1.99.0",
81
80
  "sass-loader": "^16.0.7",
82
81
  "source-map-loader": "^5.0.0",
83
82
  "strip-ansi": "^7.2.0",
84
83
  "style-loader": "^4.0.0",
85
84
  "tough-cookie": "^6.0.1",
86
- "ts-loader": "^9.5.4",
85
+ "ts-loader": "^9.5.7",
87
86
  "tslib": "^2.8.1",
88
87
  "typescript": "^6.0.2",
89
- "undici": "^7.24.7",
88
+ "undici": "^8.0.2",
90
89
  "webpack": "^5.105.4",
91
90
  "webpack-bundle-analyzer": "^5.3.0",
92
91
  "ws": "^8.20.0"
@@ -100,7 +99,7 @@
100
99
  "@types/koa": "^3.0.2",
101
100
  "@types/koa-compress": "^4.0.7",
102
101
  "@types/mime-types": "^3.0.1",
103
- "@types/node": "^25.5.0",
102
+ "@types/node": "^25.5.2",
104
103
  "@types/react": "^19.2.14",
105
104
  "@types/tough-cookie": "^4.0.5",
106
105
  "@types/vscode": "^1.110.0",
@@ -76,6 +76,7 @@ declare global {
76
76
  rm(this: string, pattern: string | RegExp, flags?: string): string;
77
77
  /** 将 string 划分为行,并去掉最后一个 \n 之后的 '' */
78
78
  split_lines(this: string): string[];
79
+ /** 将一行 string 解析为缩进量和内容 */
79
80
  split_indent(this: string): {
80
81
  indent: number;
81
82
  text: string;
package/xlint.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import { builtinModules } from 'node:module';
3
- import { AST_TOKEN_TYPES, ASTUtils, TSESTree } from '@typescript-eslint/utils';
3
+ import { AST_TOKEN_TYPES, AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';
4
4
  import TSParser from '@typescript-eslint/parser';
5
5
  import ts_plugin from '@typescript-eslint/eslint-plugin';
6
6
  import react_plugin from 'eslint-plugin-react';
@@ -327,7 +327,7 @@ export const xlint_plugin = {
327
327
  function checkForTypeAnnotationSpace(typeAnnotation) {
328
328
  const types = typeAnnotation.types;
329
329
  types.forEach(type => {
330
- const skipFunctionParenthesis = type.type === TSESTree.AST_NODE_TYPES.TSFunctionType ? ASTUtils.isNotOpeningBraceToken : 0;
330
+ const skipFunctionParenthesis = type.type === AST_NODE_TYPES.TSFunctionType ? ASTUtils.isNotOpeningBraceToken : 0;
331
331
  const operator = sourceCode.getTokenBefore(type, skipFunctionParenthesis);
332
332
  if (operator && unions.includes(operator.value)) {
333
333
  const prev = sourceCode.getTokenBefore(operator);
@@ -735,7 +735,7 @@ export const xlint_plugin = {
735
735
  let type;
736
736
  // 收集所有排序后的 import 文本
737
737
  const texts = imports_.map((im, i) => {
738
- const [start, end] = get_range_with_comments(source, im.node);
738
+ const [start, end] = get_node_range_with_comments(source, im.node, true);
739
739
  if (start < _start)
740
740
  _start = start;
741
741
  if (end > _end)
@@ -927,26 +927,26 @@ function all_space(str, len = str.length) {
927
927
  return false;
928
928
  return true;
929
929
  }
930
- /** 获取节点前面的注释,并计算包含注释的范围 */
931
- function get_range_with_comments(source, node) {
930
+ /** 获取节点及其注释的范围(排除前一条语句的行尾注释)
931
+ 返回 `[start, end]`,其中 start 是第一个独立注释的位置(若无则为节点起始位置)
932
+ - source
933
+ - node
934
+ - include_after_comment?: `true` 是否包含节点后面的行尾注释 */
935
+ function get_node_range_with_comments(source, node, include_after_comment) {
932
936
  let [start, end] = node.range;
933
937
  // 获取节点前的所有注释
934
- let comments = source.getCommentsBefore(node);
938
+ const comments = source.getCommentsBefore(node);
935
939
  if (comments.length !== 0) {
936
- // 从最后一个注释往前找,遇到属于上一条语句的行尾注释或非注释时退出
940
+ // 从最后一个注释往前找,遇到前一条语句行尾注释时退出
937
941
  // 判断标准:独立注释以多个空格 + // 或者 /* 开头
938
942
  // 行尾注释:// 前面含有非空格
939
943
  let valid_comment;
940
944
  for (let i = comments.length - 1; i >= 0; --i) {
941
- let comment = comments[i];
945
+ const comment = comments[i];
942
946
  // 获取 comment 所在行的内容
943
- let line = source.lines[comment.loc.start.line - 1];
947
+ const line = source.lines[comment.loc.start.line - 1];
944
948
  // 找到注释开始标记 // 或 /* 在行中的位置
945
- let istart;
946
- if (comment.type === 'Line')
947
- istart = line.indexOf('//');
948
- else // Block
949
- istart = line.indexOf('/*');
949
+ const istart = comment.type === 'Line' ? line.indexOf('//') : line.indexOf('/*');
950
950
  // 注释标记之前是否全是空格
951
951
  if (!all_space(line.slice(0, istart)))
952
952
  break;
@@ -956,10 +956,12 @@ function get_range_with_comments(source, node) {
956
956
  if (valid_comment)
957
957
  start = valid_comment.range[0];
958
958
  }
959
- let comments_ = source.getCommentsAfter(node);
960
- // 和导入在同一行,在尾部的注释
961
- if (comments_[0]?.loc.start.line === node.loc.end.line)
962
- end = comments_[0].range[1];
959
+ if (include_after_comment) {
960
+ const comments_after = source.getCommentsAfter(node);
961
+ // 和节点在同一行,在尾部的注释
962
+ if (comments_after[0]?.loc.start.line === node.loc.end.line)
963
+ end = comments_after[0].range[1];
964
+ }
963
965
  return [start, end];
964
966
  }
965
967
  let cache_alias_cwd;