@maiyunnet/kebab 2.0.3 → 2.0.4

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/lib/fs.ts DELETED
@@ -1,527 +0,0 @@
1
- /**
2
- * Project: Kebab, User: JianSuoQiYue
3
- * Date: 2019-3-29 23:03:07
4
- * Last: 2020-3-11 22:21:51, 2022-12-29 01:18:25, 2023-12-13 20:50:09
5
- */
6
- import * as fs from 'fs';
7
- import * as http from 'http';
8
- import * as http2 from 'http2';
9
- import * as mime from '@litert/mime';
10
- import * as text from './text';
11
- import * as core from './core';
12
- import * as zlib from './zlib';
13
-
14
- export function getContent(path: string, options?: {
15
- 'start'?: number;
16
- 'end'?: number;
17
- }): Promise<Buffer | null>;
18
- export function getContent(path: string, options: BufferEncoding | {
19
- 'encoding': BufferEncoding;
20
- 'start'?: number;
21
- 'end'?: number;
22
- }): Promise<string | null>;
23
- /**
24
- * --- 读取完整文件或一段 ---
25
- * @param path 文件路径
26
- * @param options 编码或选项
27
- */
28
- export async function getContent(path: string, options?: BufferEncoding | {
29
- 'encoding'?: BufferEncoding;
30
- 'start'?: number;
31
- 'end'?: number;
32
- }): Promise<Buffer | string | null> {
33
- if (typeof options === 'string') {
34
- options = {
35
- 'encoding': options
36
- };
37
- }
38
- else if (!options) {
39
- options = {};
40
- }
41
- const encoding = options.encoding;
42
- const start = options.start;
43
- const end = options.end;
44
- if (start ?? end) {
45
- return new Promise(function(resolve) {
46
- const rs = createReadStream(path, {
47
- 'encoding': encoding,
48
- 'start': start,
49
- 'end': end
50
- });
51
- const data: Buffer[] = [];
52
- rs.on('data', (chunk) => {
53
- if (!(chunk instanceof Buffer)) {
54
- return;
55
- }
56
- data.push(chunk);
57
- }).on('end', function() {
58
- const buf = Buffer.concat(data);
59
- if (encoding) {
60
- resolve(buf.toString());
61
- }
62
- else {
63
- resolve(buf);
64
- }
65
- }).on('error', function() {
66
- resolve(null);
67
- });
68
- });
69
- }
70
- else {
71
- try {
72
- if (encoding) {
73
- return await fs.promises.readFile(path, {
74
- 'encoding': encoding
75
- });
76
- }
77
- else {
78
- return await fs.promises.readFile(path);
79
- }
80
- }
81
- catch {
82
- return null;
83
- }
84
- }
85
- }
86
-
87
- /**
88
- * --- 写入文件内容 ---
89
- * @param path 文件路径
90
- * @param data 要写入的内容
91
- * @param options 选项
92
- */
93
- export async function putContent(
94
- path: string,
95
- data: string | Buffer,
96
- options: {
97
- 'encoding'?: BufferEncoding;
98
- 'mode'?: number;
99
- 'flag'?: string;
100
- } = {}): Promise<boolean> {
101
- try {
102
- await fs.promises.writeFile(path, data, options);
103
- return true;
104
- }
105
- catch {
106
- return false;
107
- }
108
- }
109
-
110
- /**
111
- * --- 读取链接的 target ---
112
- * @param path 要读取的路径
113
- * @param encoding 编码
114
- */
115
- export async function readLink(path: string, encoding?: BufferEncoding): Promise<string | null> {
116
- try {
117
- return await fs.promises.readlink(path, {
118
- 'encoding': encoding
119
- });
120
- }
121
- catch {
122
- return null;
123
- }
124
- }
125
-
126
- /**
127
- * --- 把源文件创建一个 link ---
128
- * @param filePath 源文件
129
- * @param linkPath 连接路径
130
- * @param type 仅 Windows,类型,默认 file
131
- */
132
- export async function symlink(filePath: string, linkPath: string, type?: 'dir' | 'file' | 'junction'): Promise<boolean> {
133
- try {
134
- await fs.promises.symlink(filePath, linkPath, type);
135
- return true;
136
- }
137
- catch {
138
- return false;
139
- }
140
- }
141
-
142
- /**
143
- * --- 删除一个文件 ---
144
- * @param path 要删除的文件路径
145
- */
146
- export async function unlink(path: string): Promise<boolean> {
147
- for (let i = 0; i <= 2; ++i) {
148
- try {
149
- await fs.promises.unlink(path);
150
- return true;
151
- }
152
- catch {
153
- await core.sleep(250);
154
- }
155
- }
156
- try {
157
- await fs.promises.unlink(path);
158
- return true;
159
- }
160
- catch {
161
- return false;
162
- }
163
- }
164
-
165
- /**
166
- * --- 获取对象是否存在,存在则返回 stats 对象,否则返回 null ---
167
- * @param path 对象路径
168
- */
169
- export async function stats(path: string): Promise<fs.Stats | null> {
170
- try {
171
- return await fs.promises.lstat(path);
172
- }
173
- catch {
174
- return null;
175
- }
176
- }
177
-
178
- /**
179
- * --- 判断是否是目录或目录是否存在,是的话返回 stats ---
180
- * @param path 判断路径
181
- */
182
- export async function isDir(path: string): Promise<fs.Stats | false> {
183
- const pstats = await stats(path);
184
- if (!pstats?.isDirectory()) {
185
- return false;
186
- }
187
- return pstats;
188
- }
189
-
190
- /**
191
- * --- 判断是否是文件或文件是否存在,是的话返回 stats ---
192
- * @param path 判断路径
193
- */
194
- export async function isFile(path: string): Promise<fs.Stats | false> {
195
- const pstats = await stats(path);
196
- if (!pstats?.isFile()) {
197
- return false;
198
- }
199
- return pstats;
200
- }
201
-
202
- /**
203
- * --- 深度创建目录,如果最末目录存在,则自动创建成功 ---
204
- * @param path 要创建的路径,如 /a/b/c/
205
- * @param mode 权限
206
- */
207
- export async function mkdir(path: string, mode: number = 0o755): Promise<boolean> {
208
- if (await isDir(path)) {
209
- return true;
210
- }
211
- // --- 深度创建目录 ---
212
- try {
213
- await fs.promises.mkdir(path, {
214
- 'recursive': true,
215
- 'mode': mode
216
- });
217
- return true;
218
- }
219
- catch {
220
- return false;
221
- }
222
- }
223
-
224
- /**
225
- * --- 删除空目录 ---
226
- * @param path 要删除的目录
227
- */
228
- export async function rmdir(path: string): Promise<boolean> {
229
- if (!(await isDir(path))) {
230
- return true;
231
- }
232
- try {
233
- await fs.promises.rmdir(path);
234
- return true;
235
- }
236
- catch {
237
- return false;
238
- }
239
- }
240
-
241
- /**
242
- * --- Danger 危险:危险函数,尽量不要使用 ---
243
- * --- This f**king is a danger function, please don't use it ---
244
- * --- 删除一个非空目录 ---
245
- */
246
- export async function rmdirDeep(path: string): Promise<boolean> {
247
- if (!path.endsWith('/')) {
248
- path += '/';
249
- }
250
- const list = await readDir(path);
251
- for (const item of list) {
252
- const stat = await stats(item.name);
253
- if (!stat) {
254
- return false;
255
- }
256
- if (stat.isDirectory()) {
257
- // --- 目录 ---
258
- const rtn = await rmdirDeep(path + item.name);
259
- if (!rtn) {
260
- return false;
261
- }
262
- }
263
- else {
264
- const rtn = await unlink(path + item.name);
265
- if (!rtn) {
266
- return false;
267
- }
268
- }
269
- }
270
- return rmdir(path);
271
- }
272
-
273
- /**
274
- * --- 修改权限
275
- * @param path 要修改的路径
276
- * @param mod 权限
277
- */
278
- export async function chmod(path: string, mod: string | number): Promise<boolean> {
279
- try {
280
- await fs.promises.chmod(path, mod);
281
- return true;
282
- }
283
- catch {
284
- return false;
285
- }
286
- }
287
-
288
- /**
289
- * --- 重命名/移动 文件文件夹 ---
290
- * @param oldPath 老名
291
- * @param newPath 新名
292
- */
293
- export async function rename(oldPath: string, newPath: string): Promise<boolean> {
294
- try {
295
- await fs.promises.rename(oldPath, newPath);
296
- return true;
297
- }
298
- catch {
299
- return false;
300
- }
301
- }
302
-
303
- /**
304
- * --- 获取文件夹下文件列表 ---
305
- * @param path 文件夹路径
306
- */
307
- export async function readDir(path: string, encoding?: BufferEncoding): Promise<fs.Dirent[]> {
308
- try {
309
- const list: fs.Dirent[] = [];
310
- const dlist = await fs.promises.readdir(path, {
311
- 'encoding': encoding,
312
- 'withFileTypes': true
313
- });
314
- for (const item of dlist) {
315
- if (item.name === '.' || item.name === '..') {
316
- continue;
317
- }
318
- list.push(item);
319
- }
320
- return list;
321
- }
322
- catch {
323
- return [];
324
- }
325
- }
326
-
327
- /**
328
- * --- 复制文件夹里的内容到另一个地方,失败不会回滚 ---
329
- * @param from 源,末尾加 /
330
- * @param to 目标,末尾加 /
331
- * @param ignore 忽略的文件
332
- */
333
- export async function copyFolder(from: string, to: string, ignore: RegExp[] = []): Promise<number> {
334
- let num = 0;
335
- // --- 如果源目录不存在或不是目录,则直接成功 :) ---
336
- if (!await isDir(from)) {
337
- return 0;
338
- }
339
- // --- 遍历源目录文件和文件夹,准备复制 ---
340
- const flist = await readDir(from);
341
- /** --- to 目录是否检查是否存在,空目录不复制,所以确定有 item file 的时候才创建 --- */
342
- let checkTo = false;
343
- for (const item of flist) {
344
- if (item.isDirectory()) {
345
- const r = await copyFolder(from + item.name + '/', to + item.name + '/', ignore);
346
- if (r === -1) {
347
- return r;
348
- }
349
- else {
350
- num += r;
351
- }
352
- }
353
- else if (item.isFile()) {
354
- // --- 先判断本文件是否被排除 ---
355
- if (ignore.length > 0 && text.match(item.name, ignore)) {
356
- continue;
357
- }
358
- if (!checkTo) {
359
- if (!await mkdir(to)) {
360
- return -1;
361
- }
362
- checkTo = true;
363
- }
364
- if (!(await copyFile(from + item.name, to + item.name))) {
365
- continue;
366
- }
367
- ++num;
368
- }
369
- }
370
- return num;
371
- }
372
-
373
- /**
374
- * --- 复制文件 ---
375
- * @param src 源文件
376
- * @param dest 目标文件
377
- */
378
- export async function copyFile(src: string, dest: string): Promise<boolean> {
379
- try {
380
- await fs.promises.copyFile(src, dest);
381
- return true;
382
- }
383
- catch {
384
- return false;
385
- }
386
- }
387
-
388
- /**
389
- * --- 创建读取文件的流 ---
390
- * @param path 文件地址
391
- * @param options 编码或配置
392
- */
393
- export function createReadStream(path: string, options?: BufferEncoding | {
394
- 'flags'?: string;
395
- 'encoding'?: BufferEncoding;
396
- 'autoClose'?: boolean;
397
- 'start'?: number;
398
- 'end'?: number;
399
- }): fs.ReadStream {
400
- if (typeof options === 'string') {
401
- options = {
402
- 'encoding': options
403
- };
404
- }
405
- else if (!options) {
406
- options = {};
407
- }
408
- return fs.createReadStream(path, {
409
- 'flags': options.flags,
410
- 'encoding': options.encoding,
411
- 'autoClose': options.autoClose,
412
- 'start': options.start,
413
- 'end': options.end
414
- });
415
- }
416
-
417
- /**
418
- * --- 读取文件写入到流,并等待写入完成 ---
419
- * @param path 文件地址
420
- * @param destination 要写入的流
421
- * @param options 写入后是否终止写入流,默认终止
422
- */
423
- export function pipe(path: string, destination: NodeJS.WritableStream, options?: {
424
- 'end'?: boolean;
425
- }): Promise<boolean> {
426
- return new Promise((resolve) => {
427
- createReadStream(path).on('error', function() {
428
- resolve(false);
429
- }).on('end', function() {
430
- resolve(true);
431
- }).pipe(destination, options);
432
- });
433
- }
434
-
435
- /**
436
- * --- 创建写入文件的流 ---
437
- * @param path 文件地址
438
- * @param options 编码或配置
439
- */
440
- export function createWriteStream(path: string, options?: BufferEncoding | {
441
- 'flags'?: string;
442
- 'encoding'?: BufferEncoding;
443
- 'mode'?: number;
444
- 'autoClose'?: boolean;
445
- 'start'?: number;
446
- }): fs.WriteStream {
447
- if (typeof options === 'string') {
448
- options = {
449
- 'encoding': options
450
- };
451
- }
452
- else if (!options) {
453
- options = {};
454
- }
455
- return fs.createWriteStream(path, {
456
- 'flags': options.flags,
457
- 'encoding': options.encoding,
458
- 'mode': options.mode,
459
- 'autoClose': options.autoClose,
460
- 'start': options.start
461
- });
462
- }
463
-
464
- /**
465
- * --- 读取文件并输出到 http 的 response ---
466
- * @param path 文件绝对路径
467
- * @param req http 请求对象
468
- * @param res http 响应对象
469
- * @param stat 文件的 stat(如果有)
470
- */
471
- export async function readToResponse(path: string,
472
- req: http2.Http2ServerRequest | http.IncomingMessage,
473
- res: http2.Http2ServerResponse | http.ServerResponse,
474
- stat?: fs.Stats | null
475
- ): Promise<void> {
476
- if (!stat) {
477
- stat = await stats(path);
478
- }
479
- if (!stat) {
480
- res.setHeader('content-length', 22);
481
- res.writeHead(404);
482
- res.end('<h1>404 Not found</h1><hr>Kebab');
483
- return;
484
- }
485
- // --- 判断缓存以及 MIME 和编码 ---
486
- let charset = '';
487
- const mimeData = mime.getData(path);
488
- if (['htm', 'html', 'css', 'js', 'xml', 'jpg', 'jpeg', 'svg', 'gif', 'png'].includes(mimeData.extension)) {
489
- charset = '; charset=utf-8';
490
- // --- 这些文件可能需要缓存 ---
491
- const hash = `W/"${stat.size.toString(16)}-${stat.mtime.getTime().toString(16)}"`;
492
- const lastModified = stat.mtime.toUTCString();
493
- res.setHeader('etag', hash);
494
- res.setHeader('cache-control', 'public, max-age=600');
495
- // --- 判断返回 304 吗 ---
496
- const noneMatch = req.headers['if-none-match'];
497
- const modifiedSince = req.headers['if-modified-since'];
498
- if ((hash === noneMatch) && (lastModified === modifiedSince)) {
499
- res.writeHead(304);
500
- res.end();
501
- return;
502
- }
503
- res.setHeader('last-modified', lastModified);
504
- }
505
- else {
506
- res.setHeader('cache-control', 'no-cache, must-revalidate');
507
- }
508
- // --- 设置 type ---
509
- res.setHeader('content-type', mimeData.mime + charset);
510
- // --- 判断客户端支持的压缩模式 ---
511
- const encoding = req.headers['accept-encoding'] as string ?? '';
512
- if (mimeData.compressible && (stat.size >= 1024)) {
513
- // --- 压缩 ---
514
- const compress = await zlib.compress(encoding, await getContent(path));
515
- if (compress) {
516
- res.setHeader('content-encoding', compress.type);
517
- res.setHeader('content-length', Buffer.byteLength(compress.buffer));
518
- res.writeHead(200);
519
- res.end(compress.buffer);
520
- return;
521
- }
522
- }
523
- // --- 不压缩 ---
524
- res.setHeader('content-length', stat.size);
525
- res.writeHead(200);
526
- await pipe(path, res instanceof http2.Http2ServerResponse ? (res.stream ?? res) : res);
527
- }