前端初始化

This commit is contained in:
“hsc”
2026-08-21 15:36:24 +08:00
commit e4d8ecde13
14411 changed files with 2275932 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2012-2019 Thorsten Lorenz, Paul Miller (https://paulmillr.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+122
View File
@@ -0,0 +1,122 @@
# readdirp [![Weekly downloads](https://img.shields.io/npm/dw/readdirp.svg)](https://github.com/paulmillr/readdirp)
Recursive version of fs.readdir. Exposes a **stream API** (with small RAM & CPU footprint) and a **promise API**.
Faster than built-in `fs.readdir recursive: true`, and uses less RAM.
```sh
npm install readdirp
jsr add jsr:@paulmillr/readdirp
```
```javascript
// Use streams to achieve small RAM & CPU footprint.
// 1) Streams example with for-await.
import readdirp from 'readdirp';
for await (const entry of readdirp('.')) {
const {path} = entry;
console.log(`${JSON.stringify({path})}`);
}
// 2) Streams example, non for-await.
// Print out all JS files along with their size within the current folder & subfolders.
import readdirp from 'readdirp';
readdirp('.', {alwaysStat: true, fileFilter: (f) => f.basename.endsWith('.js')})
.on('data', (entry) => {
const {path, stats: {size}} = entry;
console.log(`${JSON.stringify({path, size})}`);
})
// Optionally call stream.destroy() in `warn()` in order to abort and cause 'close' to be emitted
.on('warn', error => console.error('non-fatal error', error))
.on('error', error => console.error('fatal error', error))
.on('end', () => console.log('done'));
// 3) Promise example. More RAM and CPU than streams / for-await.
import { readdirpPromise } from 'readdirp';
const files = await readdirpPromise('.');
console.log(files.map(file => file.path));
// Other options.
import readdirp from 'readdirp';
readdirp('test', {
fileFilter: (f) => f.basename.endsWith('.js'),
directoryFilter: (d) => d.basename !== '.git',
// directoryFilter: (di) => di.basename.length === 9
type: 'files_directories',
depth: 1
});
```
## API
`const stream = readdirp(root[, options])`**Stream API**
- Reads given root recursively and returns a `stream` of [entry infos](#entryinfo)
- Optionally can be used like `for await (const entry of stream)` with node.js 10+ (`asyncIterator`).
- `on('data', (entry) => {})` [entry info](#entryinfo) for every file / dir.
- `on('warn', (error) => {})` non-fatal `Error` that prevents a file / dir from being processed. Example: inaccessible to the user.
- `on('error', (error) => {})` fatal `Error` which also ends the stream. Example: illegal options where passed.
- `on('end')` — we are done. Called when all entries were found and no more will be emitted.
- `on('close')` — stream is destroyed via `stream.destroy()`.
Could be useful if you want to manually abort even on a non fatal error.
At that point the stream is no longer `readable` and no more entries, warning or errors are emitted
- To learn more about streams, consult the very detailed [nodejs streams documentation](https://nodejs.org/api/stream.html)
or the [stream-handbook](https://github.com/substack/stream-handbook)
`const entries = await readdirp.promise(root[, options])`**Promise API**. Returns a list of [entry infos](#entryinfo).
First argument is awalys `root`, path in which to start reading and recursing into subdirectories.
### options
- `fileFilter`: filter to include or exclude files
- **Function**: a function that takes an entry info as a parameter and returns true to include or false to exclude the entry
- `directoryFilter`: filter to include/exclude directories found and to recurse into. Directories that do not pass a filter will not be recursed into.
- `depth: 5`: depth at which to stop recursing even if more subdirectories are found
- `type: 'files'`: determines if data events on the stream should be emitted for `'files'` (default), `'directories'`, `'files_directories'`, or `'all'`. Setting to `'all'` will also include entries for other types of file descriptors like character devices, unix sockets and named pipes.
- `alwaysStat: false`: always return `stats` property for every file. Default is `false`, readdirp will return `Dirent` entries. Setting it to `true` can double readdir execution time - use it only when you need file `size`, `mtime` etc. Cannot be enabled on node <10.10.0.
- `lstat: false`: include symlink entries in the stream along with files. When `true`, `fs.lstat` would be used instead of `fs.stat`
### `EntryInfo`
Has the following properties:
- `path: 'assets/javascripts/react.js'`: path to the file/directory (relative to given root)
- `fullPath: '/Users/dev/projects/app/assets/javascripts/react.js'`: full path to the file/directory found
- `basename: 'react.js'`: name of the file/directory
- `dirent: fs.Dirent`: built-in [dir entry object](https://nodejs.org/api/fs.html#fs_class_fs_dirent) - only with `alwaysStat: false`
- `stats: fs.Stats`: built in [stat object](https://nodejs.org/api/fs.html#fs_class_fs_stats) - only with `alwaysStat: true`
## Changelog
- 4.0 (Aug 25, 2024) rewritten in typescript, producing hybrid common.js / esm module.
- Remove glob support and all dependencies
- Make sure you're using `let {readdirp} = require('readdirp')` in common.js
- 3.5 (Oct 13, 2020) disallows recursive directory-based symlinks.
Before, it could have entered infinite loop.
- 3.4 (Mar 19, 2020) adds support for directory-based symlinks.
- 3.3 (Dec 6, 2019) stabilizes RAM consumption and enables perf management with `highWaterMark` option. Fixes race conditions related to `for-await` looping.
- 3.2 (Oct 14, 2019) improves performance by 250% and makes streams implementation more idiomatic.
- 3.1 (Jul 7, 2019) brings `bigint` support to `stat` output on Windows. This is backwards-incompatible for some cases. Be careful. It you use it incorrectly, you'll see "TypeError: Cannot mix BigInt and other types, use explicit conversions".
- 3.0 brings huge performance improvements and stream backpressure support.
- Upgrading 2.x to 3.x:
- Signature changed from `readdirp(options)` to `readdirp(root, options)`
- Replaced callback API with promise API.
- Renamed `entryType` option to `type`
- Renamed `entryType: 'both'` to `'files_directories'`
- `EntryInfo`
- Renamed `stat` to `stats`
- Emitted only when `alwaysStat: true`
- `dirent` is emitted instead of `stats` by default with `alwaysStat: false`
- Renamed `name` to `basename`
- Removed `parentDir` and `fullParentDir` properties
- Supported node.js versions:
- 4.x: node 14+
- 3.x: node 8+
- 2.x: node 0.6+
## License
Copyright (c) 2012-2019 Thorsten Lorenz, Paul Miller (<https://paulmillr.com>)
MIT License, see [LICENSE](LICENSE) file.
+117
View File
@@ -0,0 +1,117 @@
/**
* Recursive version of readdir. Exposes a streaming API and promise API.
* Streaming API allows to use a small amount of RAM.
*
* @module
* @example
```js
import readdirp from 'readdirp';
for await (const entry of readdirp('.')) {
const {path} = entry;
console.log(`${JSON.stringify({path})}`);
}
```
*/
/*! readdirp - MIT License (c) 2012-2019 Thorsten Lorenz, Paul Miller (https://paulmillr.com) */
import type { Dirent, Stats } from 'node:fs';
import { Readable } from 'node:stream';
/** Path in file system. */
export type Path = string;
/** Emitted entry. Contains relative & absolute path, basename, and either stats or dirent. */
export interface EntryInfo {
path: string;
fullPath: string;
stats?: Stats;
dirent?: Dirent;
basename: string;
}
/** Path or dir entries (files) */
export type PathOrDirent = Dirent | Path;
/** Filterer for files */
export type Tester = (entryInfo: EntryInfo) => boolean;
export type Predicate = string[] | string | Tester;
export declare const EntryTypes: {
readonly FILE_TYPE: "files";
readonly DIR_TYPE: "directories";
readonly FILE_DIR_TYPE: "files_directories";
readonly EVERYTHING_TYPE: "all";
};
export type EntryType = (typeof EntryTypes)[keyof typeof EntryTypes];
/**
* Options for readdirp.
* * type: files, directories, or both
* * lstat: whether to use symlink-friendly stat
* * depth: max depth
* * alwaysStat: whether to use stat (more resources) or dirent
* * highWaterMark: streaming param, specifies max amount of resources per entry
*/
export type ReaddirpOptions = {
root: string;
fileFilter?: Predicate;
directoryFilter?: Predicate;
type?: EntryType;
lstat?: boolean;
depth?: number;
alwaysStat?: boolean;
highWaterMark?: number;
};
/** Directory entry. Contains path, depth count, and files. */
export interface DirEntry {
/** Undefined when the directory could not be read (a 'warn' was emitted). */
files: PathOrDirent[] | undefined;
depth: number;
path: Path;
}
/** Readable readdir stream, emitting new files as they're being listed. */
interface PendingDir {
path: Path;
depth: number;
pending?: Promise<DirEntry>;
}
export declare class ReaddirpStream extends Readable {
/**
* Directories discovered but not yet emitted from. Listings are read
* lazily (on pop, plus one prefetch) instead of eagerly on discovery:
* keeping whole listings for every queued dir balloons RAM on wide trees.
*/
parents: PendingDir[];
reading: boolean;
parent?: DirEntry;
_stat: Function;
_maxDepth: number;
_wantsDir: boolean;
_wantsFile: boolean;
_wantsEverything: boolean;
_root: Path;
_isDirent: boolean;
_statsProp: 'dirent' | 'stats';
_rdOptions: {
encoding: 'utf8';
withFileTypes: boolean;
};
_fileFilter: Tester;
_directoryFilter: Tester;
_relStart: number;
constructor(options?: Partial<ReaddirpOptions>);
_read(batch: number): Promise<void>;
_exploreDir(path: Path, depth: number): Promise<DirEntry>;
_formatEntry(dirent: PathOrDirent, path: Path): EntryInfo | undefined | Promise<EntryInfo | undefined>;
_onError(err: Error): void;
_getEntryType(entry: EntryInfo): '' | 'file' | 'directory' | Promise<'' | 'file' | 'directory'>;
_getSymlinkEntryType(entry: EntryInfo): Promise<'' | 'file' | 'directory'>;
_includeAsFile(entry: EntryInfo): boolean | undefined;
}
/**
* Streaming version: Reads all files and directories in given root recursively.
* Consumes ~constant small amount of RAM.
* @param root Root directory
* @param options Options to specify root (start directory), filters and recursion depth
*/
export declare function readdirp(root: Path, options?: Partial<ReaddirpOptions>): ReaddirpStream;
/**
* Promise version: Reads all files and directories in given root recursively.
* Compared to streaming version, will consume a lot of RAM e.g. when 1 million files are listed.
* @returns array of paths and their entry infos
*/
export declare function readdirpPromise(root: Path, options?: Partial<ReaddirpOptions>): Promise<EntryInfo[]>;
export default readdirp;
+333
View File
@@ -0,0 +1,333 @@
import { lstat, readdir, realpath, stat } from 'node:fs/promises';
import { join as pjoin, resolve as presolve, sep as psep } from 'node:path';
import { Readable } from 'node:stream';
export const EntryTypes = {
FILE_TYPE: 'files',
DIR_TYPE: 'directories',
FILE_DIR_TYPE: 'files_directories',
EVERYTHING_TYPE: 'all',
};
const defaultOptions = {
root: '.',
fileFilter: (_entryInfo) => true,
directoryFilter: (_entryInfo) => true,
type: EntryTypes.FILE_TYPE,
lstat: false,
depth: 2147483648,
alwaysStat: false,
// Throughput is flat from 16 to 65536 (traversal is I/O-bound), but
// batches of 1024+ entries survive young-gen GC and bloat RSS ~20-60%.
highWaterMark: 256,
};
Object.freeze(defaultOptions);
const RECURSIVE_ERROR_CODE = 'READDIRP_RECURSIVE_ERROR';
const NORMAL_FLOW_ERRORS = new Set(['ENOENT', 'EPERM', 'EACCES', 'ELOOP', RECURSIVE_ERROR_CODE]);
const ALL_TYPES = [
EntryTypes.DIR_TYPE,
EntryTypes.EVERYTHING_TYPE,
EntryTypes.FILE_DIR_TYPE,
EntryTypes.FILE_TYPE,
];
const DIR_TYPES = new Set([
EntryTypes.DIR_TYPE,
EntryTypes.EVERYTHING_TYPE,
EntryTypes.FILE_DIR_TYPE,
]);
const FILE_TYPES = new Set([
EntryTypes.EVERYTHING_TYPE,
EntryTypes.FILE_DIR_TYPE,
EntryTypes.FILE_TYPE,
]);
const isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
const wantBigintFsStats = process.platform === 'win32';
const emptyFn = (_entryInfo) => true;
const normalizeFilter = (filter) => {
if (filter === undefined)
return emptyFn;
if (typeof filter === 'function')
return filter;
if (typeof filter === 'string') {
const fl = filter.trim();
return (entry) => entry.basename === fl;
}
if (Array.isArray(filter)) {
const trItems = filter.map((item) => item.trim());
return (entry) => trItems.some((f) => entry.basename === f);
}
return emptyFn;
};
export class ReaddirpStream extends Readable {
/**
* Directories discovered but not yet emitted from. Listings are read
* lazily (on pop, plus one prefetch) instead of eagerly on discovery:
* keeping whole listings for every queued dir balloons RAM on wide trees.
*/
parents;
reading;
parent;
_stat;
_maxDepth;
_wantsDir;
_wantsFile;
_wantsEverything;
_root;
_isDirent;
_statsProp;
_rdOptions;
_fileFilter;
_directoryFilter;
_relStart;
constructor(options = {}) {
super({
objectMode: true,
autoDestroy: true,
highWaterMark: options.highWaterMark ?? defaultOptions.highWaterMark,
});
const opts = { ...defaultOptions, ...options };
// Use ?? so an explicit `undefined` in user options doesn't shadow defaults.
const root = opts.root ?? defaultOptions.root;
const type = opts.type ?? defaultOptions.type;
this._fileFilter = normalizeFilter(opts.fileFilter);
this._directoryFilter = normalizeFilter(opts.directoryFilter);
const statMethod = opts.lstat ? lstat : stat;
// Use bigint stats if it's windows and stat() supports options (node 10+).
if (wantBigintFsStats) {
this._stat = (path) => statMethod(path, { bigint: true });
}
else {
this._stat = statMethod;
}
this._maxDepth =
opts.depth != null && Number.isSafeInteger(opts.depth) ? opts.depth : defaultOptions.depth;
this._wantsDir = DIR_TYPES.has(type);
this._wantsFile = FILE_TYPES.has(type);
this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
this._root = presolve(root);
// Every fullPath is `_root + sep + relative path` (see _formatEntry), so
// the relative path is a slice starting past the root and its trailing
// separator (which resolved paths lack, except fs roots like '/', 'C:\').
this._relStart = this._root.endsWith(psep) ? this._root.length : this._root.length + 1;
this._isDirent = !opts.alwaysStat;
this._statsProp = this._isDirent ? 'dirent' : 'stats';
this._rdOptions = { encoding: 'utf8', withFileTypes: this._isDirent };
// Launch stream with one parent, the root dir, whose readdir starts
// right away. Explore the resolved root so all parent paths stay
// absolute even if process.cwd() changes mid-iteration.
const rootDir = { path: this._root, depth: 1 };
rootDir.pending = this._exploreDir(this._root, 1);
this.parents = [rootDir];
this.reading = false;
this.parent = undefined;
}
async _read(batch) {
if (this.reading)
return;
this.reading = true;
try {
while (!this.destroyed && batch > 0) {
const par = this.parent;
const fil = par && par.files;
if (fil && fil.length > 0) {
const { path, depth } = par;
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path));
// In dirent mode _formatEntry is synchronous: skip Promise.all and
// its per-entry microtask overhead.
const awaited = this._isDirent
? slice
: await Promise.all(slice);
for (const entry of awaited) {
if (!entry)
continue;
if (this.destroyed)
return;
// Only symlinks require async work; plain files / dirs resolve synchronously.
let entryType = this._getEntryType(entry);
if (typeof entryType !== 'string')
entryType = await entryType;
if (entryType === 'directory' && this._directoryFilter(entry)) {
if (depth <= this._maxDepth) {
// Lazy: don't readdir until this dir is popped. Keeping whole
// listings for every queued dir would balloon RAM on wide trees.
this.parents.push({ path: entry.fullPath, depth: depth + 1 });
}
if (this._wantsDir) {
this.push(entry);
batch--;
}
}
else if ((entryType === 'file' || this._includeAsFile(entry)) &&
this._fileFilter(entry)) {
if (this._wantsFile) {
this.push(entry);
batch--;
}
}
}
}
else {
const parent = this.parents.pop();
if (!parent) {
this.push(null);
break;
}
const dir = parent.pending ?? this._exploreDir(parent.path, parent.depth);
// Prefetch the next dir so its readdir overlaps with processing
// this one's entries. Only the stack top is prefetched, keeping at
// most a handful of listings (~tree depth) in RAM at once.
const next = this.parents[this.parents.length - 1];
if (next && !next.pending) {
next.pending = this._exploreDir(next.path, next.depth);
}
this.parent = await dir;
if (this.destroyed)
return;
}
}
}
catch (error) {
this.destroy(error);
}
finally {
this.reading = false;
}
}
// NOTE: native `readdir(path, { recursive: true })` was evaluated as a
// replacement for this per-directory traversal and rejected:
// - Not faster: node implements it in JS, walking directories sequentially
// just like this loop, but with extra path bookkeeping. Benchmarks
// (node 24): ~10% slower on wide trees, ~40% slower on small ones,
// parity on deep ones.
// - Much more RAM: it buffers the entire subtree listing in one array,
// instead of one directory at a time, defeating streaming.
// - Semantics diverge: it can't limit depth, can't skip directories a
// directoryFilter rejects, doesn't follow symlinked dirs, and fails
// wholesale (all entries lost) if anything in the subtree is unreadable,
// instead of emitting a 'warn' and continuing.
async _exploreDir(path, depth) {
let files;
try {
files = await readdir(path, this._rdOptions);
}
catch (error) {
this._onError(error);
}
return { files, depth, path };
}
// Synchronous in dirent mode; returns a promise only when stats are needed.
_formatEntry(dirent, path) {
const basename = this._isDirent ? dirent.name : dirent;
// `path` is always an absolute, normalized parent dir (see _exploreDir
// seeding in the constructor), so a plain join is enough — resolve()
// would re-read cwd on every entry.
const fullPath = pjoin(path, basename);
// Slice instead of path.relative(): equivalent here (fullPath is always
// under _root) and avoids several intermediate allocations per entry.
const entry = { path: fullPath.slice(this._relStart), fullPath, basename };
if (this._isDirent) {
entry.dirent = dirent;
return entry;
}
return this._stat(fullPath).then((stats) => {
entry.stats = stats;
return entry;
}, (err) => {
this._onError(err);
return undefined;
});
}
_onError(err) {
if (isNormalFlowError(err) && !this.destroyed) {
this.emit('warn', err);
}
else {
this.destroy(err);
}
}
// Synchronous for regular files and directories; returns a promise only for
// symlinks, which need realpath() to be classified.
_getEntryType(entry) {
// entry may be undefined, because a warning or an error were emitted
// and the statsProp is undefined
if (!entry || !(this._statsProp in entry)) {
return '';
}
const stats = entry[this._statsProp];
if (stats.isFile())
return 'file';
if (stats.isDirectory())
return 'directory';
if (stats.isSymbolicLink())
return this._getSymlinkEntryType(entry);
return '';
}
async _getSymlinkEntryType(entry) {
const full = entry.fullPath;
try {
const entryRealPath = await realpath(full);
const entryRealPathStats = await lstat(entryRealPath);
if (entryRealPathStats.isFile()) {
return 'file';
}
if (entryRealPathStats.isDirectory()) {
const len = entryRealPath.length;
if (full.startsWith(entryRealPath) && full[len] === psep) {
const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
// @ts-ignore
recursiveError.code = RECURSIVE_ERROR_CODE;
this._onError(recursiveError);
return '';
}
return 'directory';
}
}
catch (error) {
this._onError(error);
}
return '';
}
_includeAsFile(entry) {
const stats = entry && entry[this._statsProp];
return stats && this._wantsEverything && !stats.isDirectory();
}
}
/**
* Streaming version: Reads all files and directories in given root recursively.
* Consumes ~constant small amount of RAM.
* @param root Root directory
* @param options Options to specify root (start directory), filters and recursion depth
*/
export function readdirp(root, options = {}) {
// @ts-ignore
let type = options.entryType || options.type;
if (type === 'both')
type = EntryTypes.FILE_DIR_TYPE; // backwards-compatibility
if (!root) {
throw new Error('readdirp: root argument is required. Usage: readdirp(root, options)');
}
else if (typeof root !== 'string') {
throw new TypeError('readdirp: root argument must be a string. Usage: readdirp(root, options)');
}
else if (type && !ALL_TYPES.includes(type)) {
throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(', ')}`);
}
// Copy options instead of mutating the caller's object.
const opts = { ...options, root };
if (type)
opts.type = type;
return new ReaddirpStream(opts);
}
/**
* Promise version: Reads all files and directories in given root recursively.
* Compared to streaming version, will consume a lot of RAM e.g. when 1 million files are listed.
* @returns array of paths and their entry infos
*/
export function readdirpPromise(root, options = {}) {
return new Promise((resolve, reject) => {
const files = [];
readdirp(root, options)
.on('data', (entry) => files.push(entry))
.on('end', () => resolve(files))
.on('error', (error) => reject(error));
});
}
export default readdirp;
+62
View File
@@ -0,0 +1,62 @@
{
"name": "readdirp",
"description": "Recursive version of fs.readdir with small RAM & CPU footprint",
"version": "5.1.1",
"homepage": "https://github.com/paulmillr/readdirp",
"repository": {
"type": "git",
"url": "https://github.com/paulmillr/readdirp.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/paulmillr/readdirp/issues"
},
"author": "Thorsten Lorenz <thlorenz@gmx.de> (thlorenz.com)",
"contributors": [
"Thorsten Lorenz <thlorenz@gmx.de> (thlorenz.com)",
"Paul Miller (https://paulmillr.com)"
],
"engines": {
"node": ">= 20.19.0"
},
"files": [
"index.js",
"index.d.ts"
],
"type": "module",
"main": "./index.js",
"module": "./index.js",
"types": "./index.d.ts",
"exports": {
".": "./index.js"
},
"sideEffects": false,
"keywords": [
"recursive",
"fs",
"stream",
"streams",
"readdir",
"filesystem",
"find",
"filter"
],
"scripts": {
"build": "tsc",
"format": "prettier --write index.ts test/index.test.js",
"test": "node test/index.test.js"
},
"devDependencies": {
"@paulmillr/jsbt": "0.6.1",
"@types/node": "24.10.1",
"c8": "10.1.3",
"chai": "4.3.4",
"chai-subset": "1.6.0",
"prettier": "3.1.1",
"typescript": "5.9.2"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
}