xshell 1.0.38 → 1.0.40

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
@@ -79,12 +79,12 @@ export interface FListOptions {
79
79
  - fpd: 文件夹完整路径 absolute path of directory
80
80
  - options?:
81
81
  - deep?: `false` 递归遍历 recursively
82
- - absolute?: `false` 返回、打印完整路径而不是相对路径 Return, print full path instead of relative path
82
+ - absolute?: `false` 返回、打印完整路径而不是相对路径,为 false 时返回的 FStats.fp 也会被修改 Return, print full path instead of relative path, FStats.fp returned when false will also be modified
83
83
  - print?: `true`
84
84
  - filter?: `true` RegExp | (fp: string) => any 注意当 deep = true 时被 filter 过滤掉的目录及目录中的文件不会包含在结果中
85
85
  Note that when deep = true, directories and files in directories that are filtered out by the filter will not be included in the results
86
- - stats?: `false` 启用后返回 FStats 列表, 不能和 deep 一起使用
87
- Returns the FStats list when enabled, cannot be used with deep */
86
+ - stats?: `false` 启用后返回 FStats 列表
87
+ Returns the FStats list when enabled */
88
88
  export declare function flist(fpd: string): Promise<string[]>;
89
89
  export declare function flist(fpd: string, options?: FListOptions & {
90
90
  stats: true;
@@ -109,7 +109,7 @@ export declare function fdelete(fp: string, { print }?: {
109
109
  - overwrite?: `true`
110
110
 
111
111
  @example
112
- fcopy('d:/temp/camera/', 'd:/camera/') */
112
+ fcopy('D:/temp/camera/', 'D:/camera/') */
113
113
  export declare function fcopy(fp_src: string, fp_dst: string, { print, overwrite, }?: {
114
114
  print?: boolean;
115
115
  overwrite?: boolean;
@@ -118,7 +118,7 @@ export declare function fcopy(fp_src: string, fp_dst: string, { print, overwrite
118
118
  - src: 源 文件/文件夹 完整路径 src file/directory absolute path
119
119
  - dst: 目标 文件/文件夹 完整路径 dst file/directory absolute path
120
120
  @example
121
- fmove('d:/temp/camera/', 'd:/camera/') */
121
+ fmove('D:/temp/camera/', 'D:/camera/') */
122
122
  export declare function fmove(src: string, dst: string, { overwrite, print }?: {
123
123
  overwrite?: boolean;
124
124
  print?: boolean;
package/file.js CHANGED
@@ -1,7 +1,7 @@
1
- import { promises as fsp, default as fs, } from 'fs';
1
+ import { promises as fsp, default as fs } from 'fs';
2
2
  import { isUint8Array } from 'util/types';
3
- import path from 'upath';
4
3
  import fse from 'fs-extra';
4
+ import { path } from './path.js';
5
5
  import { t } from './i18n/instance.js';
6
6
  import { to_json } from './prototype.js';
7
7
  import { assert } from './utils.js';
@@ -30,10 +30,10 @@ export async function fopen(fp, flags, { mode, print } = {}) {
30
30
  }
31
31
  export async function fread(fp, { dir, encoding = 'utf-8', print = true } = {}) {
32
32
  if (dir) {
33
- assert(dir.endsWith('/'), t('dir 必须以 / 结尾'));
33
+ assert(dir.isdir, t('dir 必须以 / 结尾'));
34
34
  fp = dir + fp;
35
35
  }
36
- assert(!fp.endsWith('/'), t('fp 必须是文件,不能以 / 结尾'));
36
+ assert(!fp.isdir, t('fp 必须是文件,不能以 / 结尾'));
37
37
  assert(path.isAbsolute(fp), `${t('fp 必须是绝对路径:')} ${fp}`);
38
38
  assert(encoding !== 'auto');
39
39
  if (print)
@@ -69,7 +69,7 @@ export async function fwrite(fp, data, { dir, print = true, mkdir = false, } = {
69
69
  }
70
70
  else {
71
71
  if (dir) {
72
- assert(dir.endsWith('/'), t('dir 必须以 / 结尾'));
72
+ assert(dir.isdir, t('dir 必须以 / 结尾'));
73
73
  fp = dir + fp;
74
74
  }
75
75
  assert(path.isAbsolute(fp), `${t('fp 必须是绝对路径,当前为:')} ${fp}`);
@@ -90,7 +90,7 @@ export async function fwrite(fp, data, { dir, print = true, mkdir = false, } = {
90
90
  }
91
91
  export async function fappend(fp, data, { dir, print = true } = {}) {
92
92
  if (dir) {
93
- assert(dir.endsWith('/'), t('dir 必须以 / 结尾'));
93
+ assert(dir.isdir, t('dir 必须以 / 结尾'));
94
94
  fp = dir + fp;
95
95
  }
96
96
  assert(path.isAbsolute(fp), `${t('fp 必须是绝对路径,当前为:')} ${fp}`);
@@ -101,9 +101,11 @@ export async function fappend(fp, data, { dir, print = true } = {}) {
101
101
  }
102
102
  export async function flist(fpd, options = {}) {
103
103
  const { filter, deep = false, absolute = false, print = true, stats = false } = options;
104
+ assert(path.isAbsolute(fpd), t("flist: 参数 fpd: '{{fpd}}' 必须是绝对路径", { fpd }));
105
+ assert(fpd.isdir, t("flist: 参数 fpd: '{{fpd}}' 必须以 / 结尾", { fpd }));
104
106
  if (!path.isAbsolute(fpd))
105
107
  throw new Error(t('参数 fpd: ') + fpd + t(' 必须是绝对路径'));
106
- if (!fpd.endsWith('/'))
108
+ if (!fpd.isdir)
107
109
  throw new Error(t('参数 fpd: ') + fpd + t(' 必须以 / 结尾'));
108
110
  // readdir withFileTypes 参数在底层有什么区别,速度上有什么差异
109
111
  // 都调用了 uv_fs_scandir, 且调用参数相同,仅仅是 Node.js 侧的回调不同 AfterScanDir / AfterScanDirWithTypes
@@ -125,22 +127,39 @@ export async function flist(fpd, options = {}) {
125
127
  console.log(fp);
126
128
  fps.push(fp);
127
129
  }
130
+ async function _fstat(fp) {
131
+ let _stats = await fstat(absolute ? fp : fpd + fp);
132
+ if (!absolute)
133
+ _stats.fp = fp;
134
+ return _stats;
135
+ }
128
136
  if (deep)
129
- return (await Promise.all(fps.map(async (fp) => fp.endsWith('/') ?
130
- [
131
- fp,
132
- ...(await flist(absolute ? fp : fpd + fp, options)).map(fp_ => absolute ? fp_ : fp + fp_)
133
- ]
134
- :
135
- fp))).flat();
137
+ return (await Promise.all(fps.map(async (fp) => {
138
+ let fpd_stats;
139
+ return fp.isdir ?
140
+ [
141
+ stats ? (fpd_stats = await _fstat(fp)) : fp,
142
+ ...(await flist(absolute ? fp : fpd + fp, options)).map((fp_or_stats) => {
143
+ if (stats) {
144
+ if (!absolute)
145
+ fp_or_stats.fp = fp + fp_or_stats.fp;
146
+ fpd_stats.size += fp_or_stats.size;
147
+ return fp_or_stats;
148
+ }
149
+ else
150
+ return absolute ? fp_or_stats : fp + fp_or_stats;
151
+ })
152
+ ]
153
+ :
154
+ stats ? _fstat(fp) : fp;
155
+ }))).flat();
136
156
  else if (stats)
137
- return Promise.all(fps.map(async (fp) => fstat(absolute ? fp : fpd + fp)));
157
+ return Promise.all(fps.map(_fstat));
138
158
  else
139
159
  return fps;
140
160
  }
141
161
  export async function fstat(fp) {
142
- if (!path.isAbsolute(fp))
143
- throw new Error('fp: ' + fp + t(' 必须是绝对路径'));
162
+ assert(path.isAbsolute(fp), t("fstat: 参数 fp: '{{fp}}' 必须是绝对路径", { fp }));
144
163
  let stat = await fsp.stat(fp, { bigint: true });
145
164
  stat.fp = fp;
146
165
  return stat;
@@ -158,7 +177,7 @@ export async function fdelete(fp, { print = true } = {}) {
158
177
  try {
159
178
  await fsp.rm(fp, { recursive: true });
160
179
  if (print)
161
- if (fp.endsWith('/'))
180
+ if (fp.isdir)
162
181
  console.log((t('删除了文件夹: ') + fp).red);
163
182
  else
164
183
  console.log((t('删除了文件: ') + fp).red);
@@ -167,7 +186,7 @@ export async function fdelete(fp, { print = true } = {}) {
167
186
  catch (error) {
168
187
  if (error.code === 'ENOENT') {
169
188
  if (print)
170
- if (fp.endsWith('/'))
189
+ if (fp.isdir)
171
190
  console.log(t('文件夹已不存在: ') + fp);
172
191
  else
173
192
  console.log(t('文件已不存在: ') + fp);
@@ -184,9 +203,9 @@ export async function fdelete(fp, { print = true } = {}) {
184
203
  - overwrite?: `true`
185
204
 
186
205
  @example
187
- fcopy('d:/temp/camera/', 'd:/camera/') */
206
+ fcopy('D:/temp/camera/', 'D:/camera/') */
188
207
  export async function fcopy(fp_src, fp_dst, { print = true, overwrite = true, } = {}) {
189
- assert(fp_src.endsWith('/') === fp_dst.endsWith('/'), t('fp_src 和 fp_dst 必须同为文件路径或文件夹路径'));
208
+ assert(fp_src.isdir === fp_dst.isdir, t('fp_src 和 fp_dst 必须同为文件路径或文件夹路径'));
190
209
  assert(path.isAbsolute(fp_src) && path.isAbsolute(fp_dst), t('fp_src 和 fp_dst 必须为完整路径'));
191
210
  if (print)
192
211
  console.log(t('复制'), fp_src, '→', fp_dst);
@@ -196,9 +215,9 @@ export async function fcopy(fp_src, fp_dst, { print = true, overwrite = true, }
196
215
  - src: 源 文件/文件夹 完整路径 src file/directory absolute path
197
216
  - dst: 目标 文件/文件夹 完整路径 dst file/directory absolute path
198
217
  @example
199
- fmove('d:/temp/camera/', 'd:/camera/') */
218
+ fmove('D:/temp/camera/', 'D:/camera/') */
200
219
  export async function fmove(src, dst, { overwrite = false, print = true } = {}) {
201
- if (src.endsWith('/') !== dst.endsWith('/'))
220
+ if (src.isdir !== dst.isdir)
202
221
  throw new Error(t('src 和 dst 必须同为文件路径或文件夹路径'));
203
222
  if (!path.isAbsolute(src) || !path.isAbsolute(dst))
204
223
  throw new Error(t('src 和 dst 必须为完整路径'));
@@ -236,7 +255,7 @@ export async function frename(fp, fp_, { fpd, print = true, overwrite = true } =
236
255
  - mode?: `'0o777'` */
237
256
  export async function fmkdir(fpd, { print = true, mode, } = {}) {
238
257
  assert(path.isAbsolute(fpd), t('fpd 必须是绝对路径: ') + fpd);
239
- assert(fpd.endsWith('/'), t('fpd 必须以 / 结尾: ') + fpd);
258
+ assert(fpd.isdir, t('fpd 必须以 / 结尾: ') + fpd);
240
259
  // CallingfsPromises.mkdir() when path is a directory that exists results in a rejection only when recursive is false.
241
260
  const fpd_ = (await fsp.mkdir(fpd, { recursive: true, mode }))?.replaceAll('\\', '/');
242
261
  if (fpd_) {
@@ -252,8 +271,8 @@ export async function fmkdir(fpd, { print = true, mode, } = {}) {
252
271
  - fp_link: 目标链接文件/文件夹的路径 target file/directory path */
253
272
  export async function flink(fp_real, fp_link, { junction = false, print = true } = {}) {
254
273
  assert(path.isAbsolute(fp_real) && path.isAbsolute(fp_link), t('fp 必须是绝对路径'));
255
- const is_fpd_real = fp_real.endsWith('/');
256
- const is_fpd_link = fp_link.endsWith('/');
274
+ const is_fpd_real = fp_real.isdir;
275
+ const is_fpd_link = fp_link.isdir;
257
276
  assert(is_fpd_real === is_fpd_link, t('fp_real 和 fp_link 必须同为文件路径或文件夹路径'));
258
277
  if (fexists(fp_link))
259
278
  throw new Error(t('存在同名') + (is_fpd_link ? t('文件夹') : t('文件')) + ': ' + fp_link + t(',无法创建链接'));
package/i18n/i18n-scan.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import path from 'upath';
3
2
  import { program } from 'commander';
3
+ import { path } from '../path.js';
4
4
  import { scanner } from './scanner/index.js';
5
5
  import { try_load_dict } from './utils.js';
6
6
  (async function main() {
@@ -1,5 +1,4 @@
1
1
  import i18n_scanner from 'i18next-scanner';
2
- import path from 'upath';
3
2
  import vfs from 'vinyl-fs';
4
3
  import sort from 'gulp-sort';
5
4
  import ora from 'ora';
@@ -8,6 +7,7 @@ import Vinyl from 'vinyl';
8
7
  import through2 from 'through2';
9
8
  import CliTable from 'cli-table3';
10
9
  import '../../prototype.js';
10
+ import { path } from '../../path.js';
11
11
  import { map_stream } from '../../utils.js';
12
12
  import { LANGUAGES, } from '../index.js';
13
13
  import { RWDict } from '../rwdict.js';
package/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './path.js';
1
2
  export * from './prototype.js';
2
3
  export * from './utils.js';
3
4
  export * from './file.js';
package/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './path.js';
1
2
  export * from './prototype.js';
2
3
  export * from './utils.js';
3
4
  export * from './file.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
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.3.0",
19
+ "node": ">=20.5.0",
20
20
  "vscode": ">=1.79.0"
21
21
  },
22
22
  "author": "ShenHongFei <shen.hongfei@outlook.com> (https://github.com/ShenHongFei)",
@@ -45,7 +45,7 @@
45
45
  ]
46
46
  },
47
47
  "dependencies": {
48
- "@babel/core": "^7.22.8",
48
+ "@babel/core": "^7.22.9",
49
49
  "@babel/parser": "^7.22.7",
50
50
  "@babel/traverse": "^7.22.8",
51
51
  "@koa/cors": "^4.0.0",
@@ -62,7 +62,7 @@
62
62
  "fs-extra": "^11.1.1",
63
63
  "gulp-sort": "^2.0.0",
64
64
  "hash-string": "^1.0.0",
65
- "i18next": "^23.2.10",
65
+ "i18next": "^23.2.11",
66
66
  "i18next-scanner": "^4.3.0",
67
67
  "js-cookie": "^3.0.5",
68
68
  "koa": "^2.14.2",
@@ -70,6 +70,7 @@
70
70
  "koa-useragent": "^4.1.0",
71
71
  "lodash": "^4.17.21",
72
72
  "map-stream": "0.0.7",
73
+ "mime-types": "^2.1.35",
73
74
  "ora": "^6.3.1",
74
75
  "qs": "^6.11.2",
75
76
  "react": "^18.2.0",
@@ -81,7 +82,6 @@
81
82
  "tslib": "^2.6.0",
82
83
  "typescript": "^5.1.6",
83
84
  "undici": "^5.22.1",
84
- "upath": "^2.0.1",
85
85
  "vinyl": "^3.0.0",
86
86
  "vinyl-fs": "^4.0.0",
87
87
  "ws": "^8.13.0"
@@ -97,20 +97,19 @@
97
97
  "@types/koa": "^2.13.6",
98
98
  "@types/koa-compress": "^4.0.3",
99
99
  "@types/lodash": "^4.14.195",
100
- "@types/node": "^20.4.1",
100
+ "@types/mime-types": "^2.1.1",
101
+ "@types/node": "^20.4.4",
101
102
  "@types/qs": "^6.9.7",
102
- "@types/react": "^18.2.14",
103
+ "@types/react": "^18.2.15",
103
104
  "@types/through2": "^2.0.38",
104
105
  "@types/tough-cookie": "^4.0.2",
105
106
  "@types/vinyl-fs": "^3.0.2",
106
107
  "@types/vscode": "^1.80.0",
107
- "@typescript-eslint/eslint-plugin": "^6.0.0",
108
- "@typescript-eslint/parser": "^6.0.0",
109
- "eslint": "^8.44.0",
110
- "eslint-plugin-react": "^7.32.2",
111
- "eslint-plugin-xlint": "^1.0.6",
112
- "source-map-loader": "^4.0.1",
113
- "ts-loader": "^9.4.4"
108
+ "@typescript-eslint/eslint-plugin": "^6.1.0",
109
+ "@typescript-eslint/parser": "^6.1.0",
110
+ "eslint": "^8.45.0",
111
+ "eslint-plugin-react": "^7.33.0",
112
+ "eslint-plugin-xlint": "^1.0.6"
114
113
  },
115
114
  "scripts": {
116
115
  "start": "node --title=xshell --inspect=0.0.0.0:8420 ./xshell.js",
package/path.d.ts ADDED
@@ -0,0 +1,88 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { default as npath, type FormatInputPathObject } from 'path';
3
+ export declare function to_fp(str: string): string;
4
+ /** Normalize a string path, reducing '..' and '.' parts.
5
+ When multiple slashes are found, they're replaced by a single one; when the path contains a trailing slash, it is preserved.
6
+ @param path string path to normalize.
7
+ @throws {TypeError} if `path` is not a string. */
8
+ export declare function normalize(path: string): string;
9
+ /** Join all arguments together and normalize the resulting path.
10
+ @param paths paths to join.
11
+ @throws {TypeError} if any of the path segments is not a string. */
12
+ export declare function join(...paths: string[]): string;
13
+ /** The right-most parameter is considered {to}. Other parameters are considered an array of {from}.
14
+
15
+ Starting from leftmost {from} parameter, resolves {to} to an absolute path.
16
+
17
+ If {to} isn't already absolute, {from} arguments are prepended in right to left order,
18
+ until an absolute path is found. If after using all {from} paths still no absolute path is found,
19
+ the current working directory is used as well. The resulting path is normalized,
20
+ and trailing slashes are removed unless the path gets resolved to the root directory.
21
+
22
+ @param paths A sequence of paths or path segments.
23
+ @throws {TypeError} if any of the arguments is not a string. */
24
+ export declare function resolve(...paths: string[]): string;
25
+ /** Determines whether {path} is an absolute path.
26
+ An absolute path will always resolve to the same location, regardless of the working directory.
27
+
28
+ If the given {path} is a zero-length string, `false` will be returned.
29
+
30
+ @param path path to test.
31
+ @throws {TypeError} if `path` is not a string. */
32
+ export declare function isAbsolute(path: string): boolean;
33
+ /** Solve the relative path from {from} to {to} based on the current working directory.
34
+ At times we have two absolute paths, and we need to derive the relative path from one to the other. This is actually the reverse transform of path.resolve.
35
+
36
+ @throws {TypeError} if either `from` or `to` is not a string. */
37
+ export declare function relative(from: string, to: string): string;
38
+ /** Return the directory name of a path. Similar to the Unix dirname command.
39
+ @param path the path to evaluate.
40
+ @throws {TypeError} if `path` is not a string. */
41
+ export declare function dirname(path: string): string;
42
+ /** Return the last portion of a path. Similar to the Unix basename command.
43
+ Often used to extract the file name from a fully qualified path.
44
+ @param path the path to evaluate.
45
+ @param suffix optionally, an extension to remove from the result.
46
+ @throws {TypeError} if `path` is not a string or if `ext` is given and is not a string. */
47
+ export declare function basename(path: string, suffix?: string): string;
48
+ /** Return the extension of the path, from the last '.' to end of string in the last portion of the path.
49
+ If there is no '.' in the last portion of the path or the first character of it is '.', then it returns an empty string.
50
+
51
+ @param path the path to evaluate.
52
+ @throws {TypeError} if `path` is not a string. */
53
+ export declare function extname(path: string): string;
54
+ /** `/` */
55
+ export declare const sep = "/";
56
+ /** The platform-specific file delimiter. ';' or ':'. */
57
+ export declare const delimiter: ":" | ";";
58
+ /** Returns an object from a path string - the opposite of format().
59
+ @param path path to evaluate.
60
+ @throws {TypeError} if `path` is not a string. */
61
+ export declare function parse(path: string): npath.ParsedPath;
62
+ /** Returns a path string from an object - the opposite of parse().
63
+ @param pathObject path to evaluate. */
64
+ export declare function format(pathObject: FormatInputPathObject): string;
65
+ /** On Windows systems only, returns an equivalent namespace-prefixed path for the given path.
66
+ If path is not a string, path will be returned without modifications.
67
+ This method is meaningful only on Windows system.
68
+ On POSIX systems, the method is non-operational and always returns path without modifications. */
69
+ export declare function toNamespacedPath(path: string): any;
70
+ export declare const posix: npath.PlatformPath;
71
+ export declare const win32: npath.PlatformPath;
72
+ /** 统一使用 / 作为分隔符的 path 模块 */
73
+ export declare let path: {
74
+ to_fp: typeof to_fp;
75
+ normalize: typeof normalize;
76
+ join: typeof join;
77
+ resolve: typeof resolve;
78
+ isAbsolute: typeof isAbsolute;
79
+ relative: typeof relative;
80
+ dirname: typeof dirname;
81
+ basename: typeof basename;
82
+ extname: typeof extname;
83
+ sep: string;
84
+ delimiter: ":" | ";";
85
+ parse: typeof parse;
86
+ format: typeof format;
87
+ toNamespacedPath: typeof toNamespacedPath;
88
+ };
package/path.js ADDED
@@ -0,0 +1,119 @@
1
+ import { default as npath } from 'path';
2
+ export function to_fp(str) {
3
+ if (!str)
4
+ return str;
5
+ const fp = str.replaceAll('\\', '/');
6
+ // 转换小写盘符开头的路径
7
+ if (fp[1] === ':' && 'a' <= fp[0] && fp[0] <= 'z')
8
+ return fp[0].toUpperCase() + fp.slice(1);
9
+ else
10
+ return fp;
11
+ }
12
+ /** Normalize a string path, reducing '..' and '.' parts.
13
+ When multiple slashes are found, they're replaced by a single one; when the path contains a trailing slash, it is preserved.
14
+ @param path string path to normalize.
15
+ @throws {TypeError} if `path` is not a string. */
16
+ export function normalize(path) {
17
+ return to_fp(npath.normalize(to_fp(path)));
18
+ }
19
+ /** Join all arguments together and normalize the resulting path.
20
+ @param paths paths to join.
21
+ @throws {TypeError} if any of the path segments is not a string. */
22
+ export function join(...paths) {
23
+ return to_fp(npath.join(...paths.map(p => to_fp(p))));
24
+ }
25
+ /** The right-most parameter is considered {to}. Other parameters are considered an array of {from}.
26
+
27
+ Starting from leftmost {from} parameter, resolves {to} to an absolute path.
28
+
29
+ If {to} isn't already absolute, {from} arguments are prepended in right to left order,
30
+ until an absolute path is found. If after using all {from} paths still no absolute path is found,
31
+ the current working directory is used as well. The resulting path is normalized,
32
+ and trailing slashes are removed unless the path gets resolved to the root directory.
33
+
34
+ @param paths A sequence of paths or path segments.
35
+ @throws {TypeError} if any of the arguments is not a string. */
36
+ export function resolve(...paths) {
37
+ return to_fp(npath.resolve(...paths.map(p => to_fp(p))));
38
+ }
39
+ /** Determines whether {path} is an absolute path.
40
+ An absolute path will always resolve to the same location, regardless of the working directory.
41
+
42
+ If the given {path} is a zero-length string, `false` will be returned.
43
+
44
+ @param path path to test.
45
+ @throws {TypeError} if `path` is not a string. */
46
+ export function isAbsolute(path) {
47
+ return npath.isAbsolute(to_fp(path));
48
+ }
49
+ /** Solve the relative path from {from} to {to} based on the current working directory.
50
+ At times we have two absolute paths, and we need to derive the relative path from one to the other. This is actually the reverse transform of path.resolve.
51
+
52
+ @throws {TypeError} if either `from` or `to` is not a string. */
53
+ export function relative(from, to) {
54
+ return to_fp(npath.relative(to_fp(from), to_fp(to)));
55
+ }
56
+ /** Return the directory name of a path. Similar to the Unix dirname command.
57
+ @param path the path to evaluate.
58
+ @throws {TypeError} if `path` is not a string. */
59
+ export function dirname(path) {
60
+ return to_fp(npath.dirname(path));
61
+ }
62
+ /** Return the last portion of a path. Similar to the Unix basename command.
63
+ Often used to extract the file name from a fully qualified path.
64
+ @param path the path to evaluate.
65
+ @param suffix optionally, an extension to remove from the result.
66
+ @throws {TypeError} if `path` is not a string or if `ext` is given and is not a string. */
67
+ export function basename(path, suffix) {
68
+ return npath.basename(to_fp(path), suffix);
69
+ }
70
+ /** Return the extension of the path, from the last '.' to end of string in the last portion of the path.
71
+ If there is no '.' in the last portion of the path or the first character of it is '.', then it returns an empty string.
72
+
73
+ @param path the path to evaluate.
74
+ @throws {TypeError} if `path` is not a string. */
75
+ export function extname(path) {
76
+ return npath.extname(to_fp(path));
77
+ }
78
+ /** `/` */
79
+ export const sep = '/';
80
+ /** The platform-specific file delimiter. ';' or ':'. */
81
+ export const delimiter = npath.delimiter;
82
+ /** Returns an object from a path string - the opposite of format().
83
+ @param path path to evaluate.
84
+ @throws {TypeError} if `path` is not a string. */
85
+ export function parse(path) {
86
+ return npath.parse(to_fp(path));
87
+ }
88
+ /** Returns a path string from an object - the opposite of parse().
89
+ @param pathObject path to evaluate. */
90
+ export function format(pathObject) {
91
+ return to_fp(npath.format(pathObject));
92
+ }
93
+ /** On Windows systems only, returns an equivalent namespace-prefixed path for the given path.
94
+ If path is not a string, path will be returned without modifications.
95
+ This method is meaningful only on Windows system.
96
+ On POSIX systems, the method is non-operational and always returns path without modifications. */
97
+ export function toNamespacedPath(path) {
98
+ return to_fp(toNamespacedPath(to_fp(path)));
99
+ }
100
+ export const posix = npath.posix;
101
+ export const win32 = npath.win32;
102
+ /** 统一使用 / 作为分隔符的 path 模块 */
103
+ export let path = {
104
+ to_fp,
105
+ normalize,
106
+ join,
107
+ resolve,
108
+ isAbsolute,
109
+ relative,
110
+ dirname,
111
+ basename,
112
+ extname,
113
+ sep,
114
+ delimiter,
115
+ parse,
116
+ format,
117
+ toNamespacedPath
118
+ };
119
+ //# sourceMappingURL=path.js.map
package/process.d.ts CHANGED
@@ -26,15 +26,14 @@ interface StartOptions {
26
26
  detached?: boolean;
27
27
  /** 为 true 时会设置 UV_PROCESS_WINDOWS_HIDE
28
28
  然后设置启动进程的参数 CREATE_NO_WINDOW, 和 SW_HIDE
29
- d:/0/libuv/src/win/process.c
29
+ D:/0/libuv/src/win/process.c
30
30
  CREATE_NO_WINDOW:
31
31
  The process is a console application that is being run without a console window.
32
32
  Therefore, the console handle for the application is not set.
33
33
  This flag is ignored if the application is not a console application, or
34
34
  if it is used with either CREATE_NEW_CONSOLE or DETACHED_PROCESS.
35
35
 
36
- 具体有什么用还不清楚
37
- */
36
+ 具体有什么用还不清楚 */
38
37
  window?: boolean;
39
38
  }
40
39
  /** start process
@@ -66,7 +65,7 @@ export interface CallResult<TOutput extends string | Buffer = string> {
66
65
  - exe: .exe 路径或文件名 (建议使用路径,跳过 path 搜索,性能更高) path or filename (full path is recommanded to skip path searching for better perf)
67
66
  - args: `[]` 参数列表 arguments list
68
67
  - options?:
69
- - cwd?: `'d:/'`
68
+ - cwd?: `process.cwd()`
70
69
  - env?: `process.env` 覆盖/添加到 process.env 的环境变量 overwrite/add to process.env
71
70
  - encoding?: `'utf-8'` 子进程输出编码, 设置为 binary 时返回 stdout, stderr 类型为 Buffer; 否则是 string
72
71
  child output encoding. When set to binary, return stdout, stderr is of type Buffer; otherwise it is string
@@ -84,7 +83,7 @@ export declare function call(exe: string, args?: string[], options?: CallOptions
84
83
  - js: .js 路径 (相对路径根据 cwd 解析) path (relative path will resolve based on cwd)
85
84
  - args: `[]` 参数列表 arguments list
86
85
  - options
87
- - cwd?: `'d:/'`
86
+ - cwd?: `process.cwd()`
88
87
  - env?: `process.env` 覆盖/添加到 process.env 的环境变量 overwrite/add to process.env
89
88
  - encoding?: `'utf-8'` 子进程输出编码 child process output encoding
90
89
  - print?: `true` print 选项,支持设置细项 print option (with details)
@@ -93,7 +92,7 @@ export declare function call(exe: string, args?: string[], options?: CallOptions
93
92
  - throw_code?: `true` code 不为 0 时是否抛出异常 whether to throw Error when code is not 0 */
94
93
  export declare function call_nodejs(js: string, args?: string[], options?: CallOptions): Promise<CallResult<string>>;
95
94
  export interface TermOptions {
96
- /** `d:/t/` */
95
+ /** `process.cwd()` */
97
96
  cwd?: string;
98
97
  /** `true` 打印参数 */
99
98
  print?: boolean;
package/process.js CHANGED
@@ -3,7 +3,7 @@ import { userInfo } from 'os';
3
3
  import { t } from './i18n/instance.js';
4
4
  import './prototype.js';
5
5
  import { inspect, DecoderStream } from './utils.js';
6
- export const exe_nodejs = process.execPath.to_slash();
6
+ export const exe_nodejs = process.execPath.fp;
7
7
  /** start process
8
8
  - exe: .exe 路径或文件名 (建议使用完整路径,跳过 path 搜索,性能更高) path or filename (full path is recommanded to skip path searching for better perf)
9
9
  - args: `[]` 参数列表 arguments list
@@ -165,7 +165,7 @@ export async function call(exe, args = [], options = {}) {
165
165
  - js: .js 路径 (相对路径根据 cwd 解析) path (relative path will resolve based on cwd)
166
166
  - args: `[]` 参数列表 arguments list
167
167
  - options
168
- - cwd?: `'d:/'`
168
+ - cwd?: `process.cwd()`
169
169
  - env?: `process.env` 覆盖/添加到 process.env 的环境变量 overwrite/add to process.env
170
170
  - encoding?: `'utf-8'` 子进程输出编码 child process output encoding
171
171
  - print?: `true` print 选项,支持设置细项 print option (with details)
@@ -181,7 +181,7 @@ export const exe_winterm = `C:/Users/${userInfo().username}/AppData/Local/Micros
181
181
  - args: 调用参数 call parameter
182
182
  - options?: WinTermOptions
183
183
  */
184
- export async function term(exe, args = [], { cwd = 'd:/t/', print = true, title,
184
+ export async function term(exe, args = [], { cwd = process.cwd().fp, print = true, title,
185
185
  // env
186
186
  } = {}) {
187
187
  return start(exe_winterm, [
@@ -21,7 +21,6 @@ declare global {
21
21
  position?: 'left' | 'right';
22
22
  }): string;
23
23
  to_regexp(this: string, preservations?: string, flags?: string): RegExp;
24
- to_bool(this: string): boolean;
25
24
  /** 字符串模式替换
26
25
  - pattern: 匹配部分的格式
27
26
  - pattern_: 替换后的格式
@@ -67,21 +66,26 @@ declare global {
67
66
  surround(this: string, left: string, right?: string): string;
68
67
  surround_tag(this: string, tag_name: string): string;
69
68
  to_lf(this: string): string;
70
- to_crlf(this: string): string;
71
69
  /** 'xxx'.replace(/pattern/g, '')
72
- 如果 pattern 是 string 则在创建 RegExp 时自动加上 flags (默认 'g'), 否则忽略 flags
73
- */
70
+ 如果 pattern 是 string 则在创建 RegExp 时自动加上 flags (默认 'g'), 否则忽略 flags */
74
71
  rm(this: string, pattern: string | RegExp, flags?: string): string;
75
72
  /** 将 string 划分为行,并去掉最后一个 \n 之后的 '' */
76
73
  split_lines(this: string): string[];
77
- trim_doc_comment(this: string): string;
78
74
  split_indent(this: string): {
79
75
  indent: number;
80
76
  text: string;
81
77
  };
82
78
  space(this: string): string;
83
- to_slash(this: string): string;
84
- to_backslash(this: string): string;
79
+ /** 等价于 .endsWith('/') */
80
+ isdir: boolean;
81
+ /** 以 `/` 分割的路径 */
82
+ fp: string;
83
+ /** 文件名 (path.basename 的结果), 保留结尾的 /,如:
84
+ - D:/0/aaa.txt -> aaa.txt
85
+ - D:/aaa/ -> aaa/ */
86
+ fname: string;
87
+ /** .txt */
88
+ fext: string;
85
89
  }
86
90
  interface Date {
87
91
  /** - ms?: `false` 显示到 ms */
@@ -109,9 +109,6 @@ Object.defineProperties(String.prototype, {
109
109
  .map((c) => c === ']' ? '\\]' : c).join('');
110
110
  return new RegExp(this.replace(new RegExp(`[${replace_chars}]`, 'g'), '\\$&'), flags);
111
111
  },
112
- to_bool() {
113
- return this.length && this !== '0' && this.toLowerCase() !== 'false';
114
- },
115
112
  refmt(pattern, pattern_, preservations = '', flags = '', transformer = (name, value) => value || '', pattern_placeholder = /\{.*?\}/g) {
116
113
  // --- 转换 pattern 为 pattern_regx
117
114
  let last_end = 0;
@@ -226,9 +223,6 @@ Object.defineProperties(String.prototype, {
226
223
  to_lf() {
227
224
  return this.replace(/\r\n/g, '\n');
228
225
  },
229
- to_crlf() {
230
- return this.replace(/\n/g, '\r\n');
231
- },
232
226
  rm(pattern, flags = 'g') {
233
227
  if (typeof pattern === 'string')
234
228
  pattern = new RegExp(pattern, flags);
@@ -277,13 +271,38 @@ Object.defineProperties(String.prototype, {
277
271
  .replace(new RegExp(cjk + '([A-Za-z0-9`\\$%\\^&\\*\\-=\\+\\\\\\|\\/@\u00a1-\u00ff\u2022\u2027\u2150-\u218f])', 'g'), '$1 $2')
278
272
  .replace(new RegExp('([A-Za-z0-9`\\$%\\^&\\*\\-=\\+\\\\\\|\\/@\u00a1-\u00ff\u2022\u2027\u2150-\u218f])' + cjk, 'g'), '$1 $2');
279
273
  },
280
- to_slash() {
281
- return this.replaceAll('\\', '/');
274
+ }),
275
+ // ------------ 文件路径操作
276
+ ...to_getter_property_descriptors({
277
+ isdir() {
278
+ return this.endsWith('/');
279
+ },
280
+ fp() {
281
+ if (!this)
282
+ return this;
283
+ const fp = this.replaceAll('\\', '/');
284
+ // 转换小写盘符开头的路径
285
+ if (fp[1] === ':' && 'a' <= fp[0] && fp[0] <= 'z')
286
+ return fp[0].toUpperCase() + fp.slice(1);
287
+ else
288
+ return fp;
289
+ },
290
+ fdir() {
291
+ const fname = (this.endsWith('/') ? this.slice(0, -1) : this).split('/').at(-1);
292
+ return this.slice(0, this.length - (fname.length + (this.endsWith('/') ? 1 : 0)));
293
+ },
294
+ fname() {
295
+ const fname = (this.endsWith('/') ? this.slice(0, -1) : this).split('/').at(-1);
296
+ if (fname)
297
+ return `${fname}${this.endsWith('/') ? '/' : ''}`;
298
+ else
299
+ return '';
282
300
  },
283
- to_backslash() {
284
- return this.replaceAll('/', '\\');
301
+ fext() {
302
+ const index = this.lastIndexOf('.');
303
+ return index === -1 ? '' : this.slice(index + 1);
285
304
  },
286
- })
305
+ }),
287
306
  });
288
307
  // ------------------------------------ Date.prototype
289
308
  Object.defineProperties(Date.prototype, to_method_property_descriptors({
package/prototype.d.ts CHANGED
@@ -11,8 +11,7 @@ declare global {
11
11
  truncate(this: string, width: number): string;
12
12
  /** pad string to `<width>`
13
13
  - character?: `' '`
14
- - position?: `'right'`
15
- */
14
+ - position?: `'right'` */
16
15
  pad(this: string, width: number, { character, position }?: {
17
16
  character?: string;
18
17
  position?: 'left' | 'right';
@@ -22,7 +21,6 @@ declare global {
22
21
  position?: 'left' | 'right';
23
22
  }): string;
24
23
  to_regexp(this: string, preservations?: string, flags?: string): RegExp;
25
- to_bool(this: string): boolean;
26
24
  /** 字符串模式替换
27
25
  - pattern: 匹配部分的格式
28
26
  - pattern_: 替换后的格式
@@ -68,7 +66,6 @@ declare global {
68
66
  surround(this: string, left: string, right?: string): string;
69
67
  surround_tag(this: string, tag_name: string): string;
70
68
  to_lf(this: string): string;
71
- to_crlf(this: string): string;
72
69
  /** 'xxx'.replace(/pattern/g, '')
73
70
  如果 pattern 是 string 则在创建 RegExp 时自动加上 flags (默认 'g'), 否则忽略 flags
74
71
  */
@@ -90,7 +87,6 @@ declare global {
90
87
  strip_ansi(this: string): string;
91
88
  /** 将 string 划分为行,并去掉最后一个 \n 之后的 '' */
92
89
  split_lines(this: string): string[];
93
- trim_doc_comment(this: string): string;
94
90
  split_indent(this: string): {
95
91
  indent: number;
96
92
  text: string;
@@ -101,14 +97,18 @@ declare global {
101
97
  decode_base64(this: string, buffer: true): Buffer;
102
98
  decode_base64(this: string, buffer?: boolean): string | Buffer;
103
99
  space(this: string): string;
100
+ /** 等价于 .endsWith('/') */
101
+ isdir: boolean;
102
+ /** 以 `/` 分割的路径 */
103
+ fp: string;
104
+ /** 父文件夹路径 (path.dirname),并保证以 `/` 结尾 */
104
105
  fdir: string;
105
- /** path.basename, 如:
106
- - d:/0/aaa.txt -> aaa.txt
107
- - d:/aaa/ -> aaa/ */
106
+ /** 文件名 (path.basename 的结果), 保留结尾的 /,如:
107
+ - D:/0/aaa.txt -> aaa.txt
108
+ - D:/aaa/ -> aaa/ */
108
109
  fname: string;
109
110
  /** .txt */
110
111
  fext: string;
111
- to_slash(this: string): string;
112
112
  to_backslash(this: string): string;
113
113
  }
114
114
  interface Date {
package/prototype.js CHANGED
@@ -1,10 +1,10 @@
1
- import path from 'upath';
2
1
  import byte_size from 'byte-size';
3
2
  import EmojiRegex from 'emoji-regex';
4
3
  import strip_ansi from 'strip-ansi';
5
4
  import chalk from 'chalk';
6
- chalk.level = 2;
5
+ import { to_fp, dirname, basename, extname } from './path.js';
7
6
  import { t } from './i18n/instance.js';
7
+ chalk.level = 2;
8
8
  export const emoji_regex = EmojiRegex();
9
9
  export { chalk };
10
10
  export function to_method_property_descriptors(methods) {
@@ -40,7 +40,7 @@ export const brackets = {
40
40
  tortoise_shell: ['〔', '〕'],
41
41
  };
42
42
  const color_map = Object.fromEntries(['red_', 'green_', 'yellow_', 'blue_', 'magenta_', 'cyan_'].map(color => [color, `${color.slice(0, -1)}Bright`]));
43
- if (!global.my_prototype_defined) {
43
+ if (!globalThis.my_prototype_defined) {
44
44
  // ------------------------------------ String.prototype
45
45
  Object.defineProperties(String.prototype, {
46
46
  ...to_getter_property_descriptors({
@@ -116,9 +116,6 @@ if (!global.my_prototype_defined) {
116
116
  .map((c) => c === ']' ? '\\]' : c).join('');
117
117
  return new RegExp(this.replace(new RegExp(`[${replace_chars}]`, 'g'), '\\$&'), flags);
118
118
  },
119
- to_bool() {
120
- return this.length && this !== '0' && this.toLowerCase() !== 'false';
121
- },
122
119
  refmt(pattern, pattern_, preservations = '', flags = '', transformer = (name, value) => value || '', pattern_placeholder = /\{.*?\}/g) {
123
120
  // --- 转换 pattern 为 pattern_regx
124
121
  let last_end = 0;
@@ -313,23 +310,25 @@ if (!global.my_prototype_defined) {
313
310
  }]))),
314
311
  // ------------ 文件路径操作
315
312
  ...to_getter_property_descriptors({
313
+ isdir() {
314
+ return this.endsWith('/');
315
+ },
316
+ fp() {
317
+ return to_fp(this);
318
+ },
316
319
  fdir() {
317
- const dir = path.dirname(this);
318
- return dir.endsWith('/') ? dir : `${dir}/`;
320
+ const fpd = dirname(this);
321
+ // 有可能 fpd 是 '/'
322
+ return fpd.endsWith('/') ? fpd : `${fpd}/`;
319
323
  },
320
324
  fname() {
321
- return `${path.basename(this)}${this.endsWith('/') ? '/' : ''}`;
325
+ return `${basename(this)}${this.endsWith('/') ? '/' : ''}`;
322
326
  },
323
327
  fext() {
324
- return path.extname(this);
328
+ return extname(this);
325
329
  },
326
330
  }),
327
331
  ...to_method_property_descriptors({
328
- to_slash() {
329
- if (!this)
330
- return this;
331
- return path.normalizeSafe(this);
332
- },
333
332
  to_backslash() {
334
333
  return this.replaceAll('/', '\\');
335
334
  },
package/repl.js CHANGED
@@ -2,9 +2,9 @@ import nvm from 'vm';
2
2
  import repl from 'repl';
3
3
  import process from 'process';
4
4
  import { fileURLToPath } from 'url';
5
- import path from 'upath';
6
5
  import ts from 'typescript';
7
6
  const { factory, createPrinter, SyntaxKind, NodeFlags, ModifierFlags, isIdentifier, isNamedImports, isImportDeclaration, isAwaitExpression, isExpressionStatement, isVariableStatement, isNamespaceImport, isClassDeclaration, isCallExpression, isReturnStatement, isBinaryExpression, isFunctionDeclaration, } = ts;
7
+ import { path } from './path.js';
8
8
  import { t } from './i18n/instance.js';
9
9
  import './prototype.js';
10
10
  import { log_line, delay, inspect, set_inspect_options, Timer, delta2str } from './utils.js';
@@ -323,11 +323,11 @@ export async function pollute_global() {
323
323
  exports: global
324
324
  });
325
325
  await Promise.all([
326
- pollute_module_default_export('upath', 'path'),
327
326
  pollute_module_default_export('lodash/omit.js', 'omit'),
328
327
  pollute_module_default_export('lodash/sortBy.js', 'sort_by'),
329
328
  pollute_module_default_export('lodash/groupBy.js', 'group_by'),
330
329
  pollute_module_default_export('qs', 'qs'),
330
+ pollute_module_exports('./path.js'),
331
331
  pollute_module_exports('./prototype.js'),
332
332
  pollute_module_exports('./utils.js'),
333
333
  pollute_module_exports('./process.js'),
package/server.d.ts CHANGED
@@ -29,6 +29,7 @@ declare module 'http' {
29
29
  export declare class Server {
30
30
  /** proxy 时需要丢弃的 resposne headers */
31
31
  static drop_response_headers: Set<string>;
32
+ js_exts: Set<string>;
32
33
  app: Koa;
33
34
  handler: ReturnType<Koa['callback']>;
34
35
  port: number;
@@ -50,6 +51,7 @@ export declare class Server {
50
51
  */
51
52
  parse(ctx: Context): Promise<void>;
52
53
  _router(ctx: Context, next: Next): Promise<void>;
54
+ /** 被子类重写以自定义处理逻辑 */
53
55
  router(ctx: Context): Promise<boolean>;
54
56
  logger(ctx: Context): void;
55
57
  proxy(ctx: Context, url: string | URL, headers_?: Record<string, string>): Promise<void>;
@@ -58,10 +60,17 @@ export declare class Server {
58
60
  root: string;
59
61
  log_404?: boolean;
60
62
  }): Promise<boolean>;
61
- /** send file at `path` with the given `options` to the koa `ctx`. */
62
- fsend(ctx: Context, path: string, { root, absolute }?: {
63
+ /** body 设置为 fp 所指文件的 ReadStream
64
+ 检查 fp 是否合法,设置对应的 content-type,支持缓存,支持分块下载
65
+ - ctx: Koa.Context
66
+ - fp: 文件路径,有 root 时为相对路径或以 root 开头的绝对路径,无 root 时必须是绝对路径
67
+ - options?:
68
+ - root?: 只能访问 root 下面的文件
69
+ - absolute?: `false` 使用绝对路径指定发送的文件
70
+ - download?: `undefined` 在 response.headers 中加上 content-disposition: attachment 指示浏览器下载文件 */
71
+ fsend(ctx: Context, fp: string, { root, absolute, download, }?: {
63
72
  root?: string;
64
- /** `false` */
65
73
  absolute?: boolean;
74
+ download?: boolean;
66
75
  }): Promise<string>;
67
76
  }
package/server.js CHANGED
@@ -4,10 +4,10 @@ import { createReadStream } from 'fs';
4
4
  import { Readable } from 'stream';
5
5
  import util from 'util';
6
6
  // --- 3rd party
7
- import upath from 'upath';
8
7
  import qs from 'qs';
9
8
  import resolve_safely from 'resolve-path';
10
9
  import { WebSocketServer } from 'ws';
10
+ import { contentType } from 'mime-types';
11
11
  // --- koa & koa middleware
12
12
  import { default as Koa } from 'koa';
13
13
  import KoaCors from '@koa/cors';
@@ -28,6 +28,7 @@ export class Server {
28
28
  'transfer-encoding',
29
29
  'x-powered-by'
30
30
  ]);
31
+ js_exts = new Set(['.js', '.mjs', '.cjs']);
31
32
  app;
32
33
  handler;
33
34
  port;
@@ -160,6 +161,7 @@ export class Server {
160
161
  return;
161
162
  await next?.();
162
163
  }
164
+ /** 被子类重写以自定义处理逻辑 */
163
165
  async router(ctx) {
164
166
  return false;
165
167
  }
@@ -295,31 +297,37 @@ export class Server {
295
297
  return false;
296
298
  }
297
299
  }
298
- /** send file at `path` with the given `options` to the koa `ctx`. */
299
- async fsend(ctx, path, { root, absolute } = {}) {
300
+ /** body 设置为 fp 所指文件的 ReadStream
301
+ 检查 fp 是否合法,设置对应的 content-type,支持缓存,支持分块下载
302
+ - ctx: Koa.Context
303
+ - fp: 文件路径,有 root 时为相对路径或以 root 开头的绝对路径,无 root 时必须是绝对路径
304
+ - options?:
305
+ - root?: 只能访问 root 下面的文件
306
+ - absolute?: `false` 使用绝对路径指定发送的文件
307
+ - download?: `undefined` 在 response.headers 中加上 content-disposition: attachment 指示浏览器下载文件 */
308
+ async fsend(ctx, fp, { root, absolute, download, } = {}) {
309
+ assert(root.isdir);
300
310
  const { request } = ctx;
301
311
  let { response } = ctx;
302
312
  if (!absolute && !root)
303
313
  throw new Error('fsend with `!absolute && !root`');
304
- if (absolute)
305
- path = upath.resolve(path);
306
- else {
307
- if (path.startsWith(root))
308
- path = path.slice(root.length);
309
- if (path.startsWith('/'))
310
- path = path.slice(1);
314
+ if (!absolute) {
315
+ if (fp.startsWith(root))
316
+ fp = fp.slice(root.length);
317
+ if (fp.startsWith('/'))
318
+ fp = fp.slice(1);
311
319
  try {
312
- path = upath.normalize(resolve_safely(root, path));
320
+ fp = resolve_safely(root, fp).fp;
313
321
  }
314
322
  catch (error) {
315
- error.message += `, path: ${path}`;
323
+ error.message += `, fp: ${fp}`;
316
324
  throw error;
317
325
  }
318
326
  }
319
327
  // stat
320
328
  let stats;
321
329
  try {
322
- stats = await fstat(path);
330
+ stats = await fstat(fp);
323
331
  }
324
332
  catch (error) {
325
333
  if (['ENOENT', 'ENAMETOOLONG', 'ENOTDIR'].includes(error.code)) {
@@ -330,12 +338,6 @@ export class Server {
330
338
  error.message = `fs.stat 出错: ${error.message}`;
331
339
  throw error;
332
340
  }
333
- // size limit
334
- // if (stats.size >= 100 * 2**20) {
335
- // let error = new Error('body.length >= 100 mb')
336
- // ;(error as any).status = 500
337
- // throw error
338
- // }
339
341
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges
340
342
  // advertise server support of partial requests
341
343
  response.set('accept-ranges', 'bytes');
@@ -343,15 +345,17 @@ export class Server {
343
345
  response.set('cache-control', 'max-age=0, must-revalidate');
344
346
  if (!response.get('last-modified'))
345
347
  response.set('last-modified', stats.mtime.toUTCString());
346
- const fext = path.fext;
347
- if (!response.type)
348
- response.type = fext;
349
- if (fext === '.pdf')
350
- response.set('content-disposition', `attachment; filename="${encodeURIComponent(path.fname)}"`);
348
+ if (download)
349
+ response.set('content-disposition', `attachment; filename="${encodeURIComponent(fp.fname)}"`);
350
+ if (!response.get('content-type')) {
351
+ const fext = fp.fext;
352
+ response.set('content-type', (this.js_exts.has(fext) ? 'text/javascript; chatset=utf-8' : contentType(fp.fext))
353
+ || 'application/octet-stream');
354
+ }
351
355
  if (request.fresh) {
352
356
  response.status = 304;
353
357
  // 以上会自动设置 response.body = null
354
- return path;
358
+ return fp;
355
359
  }
356
360
  if (request.headers.range)
357
361
  try {
@@ -369,19 +373,19 @@ export class Server {
369
373
  response.status = 206;
370
374
  response.set('content-length', String(chunksize));
371
375
  response.set('content-range', `bytes ${start}-${end}/${stats.size}`);
372
- response.body = createReadStream(path, { start, end });
376
+ response.body = createReadStream(fp, { start, end });
373
377
  }
374
- catch (err) {
378
+ catch {
375
379
  response.status = 416;
376
380
  response.set('content-length', String(stats.size));
377
381
  response.set('content-range', `bytes */${stats.size}`);
378
- response.body = createReadStream(path);
382
+ response.body = createReadStream(fp);
379
383
  }
380
384
  else {
381
385
  response.set('content-length', String(stats.size));
382
- response.body = createReadStream(path);
386
+ response.body = createReadStream(fp);
383
387
  }
384
- return path;
388
+ return fp;
385
389
  }
386
390
  }
387
391
  //# sourceMappingURL=server.js.map