rrdir 12.1.0 → 13.1.0

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/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  `rrdir` recursively reads a directory and returns entries within via an async iterator or async/sync as Array. It can typically iterate millions of files in a matter of seconds. Memory usage is `O(1)` for the async iterator and `O(n)` for the Array variants.
7
7
 
8
+ Contrary to other similar modules, this module is optionally able to read any path including ones that contain invalid UTF-8 sequences.
9
+
8
10
  ## Usage
9
11
  ```console
10
12
  npm i rrdir
@@ -0,0 +1,34 @@
1
+ import { Stats } from 'node:fs';
2
+ import { Matcher } from 'picomatch';
3
+
4
+ type Encoding = "utf8" | "buffer";
5
+ type Dir = string | Uint8Array;
6
+ type RRDirOpts = {
7
+ strict?: boolean;
8
+ stats?: boolean;
9
+ followSymlinks?: boolean;
10
+ include?: string[];
11
+ exclude?: string[];
12
+ insensitive?: boolean;
13
+ };
14
+ type InternalOpts = {
15
+ includeMatcher?: Matcher;
16
+ excludeMatcher?: Matcher;
17
+ encoding?: Encoding;
18
+ };
19
+ type Entry = {
20
+ /** The path to the entry, will be relative if `dir` is given relative. If `dir` is a `Uint8Array`, this will be too. Always present. */
21
+ path: Dir;
22
+ /** Boolean indicating whether the entry is a directory. `undefined` on error. */
23
+ directory?: boolean;
24
+ /** Boolean indicating whether the entry is a symbolic link. `undefined` on error. */
25
+ symlink?: boolean;
26
+ /** A [`fs.stats`](https://nodejs.org/api/fs.html#fs_class_fs_stats) object, present when `options.stats` is set. `undefined` on error. */
27
+ stats?: Stats;
28
+ /** Any error encountered while reading this entry. `undefined` on success. */
29
+ err?: Error;
30
+ };
31
+ export declare function rrdir(dir: Dir, opts?: RRDirOpts, { includeMatcher, excludeMatcher, encoding }?: InternalOpts): AsyncGenerator<Entry>;
32
+ export declare function rrdirAsync(dir: Dir, opts?: RRDirOpts, { includeMatcher, excludeMatcher, encoding }?: InternalOpts): Promise<Entry[]>;
33
+ export declare function rrdirSync(dir: Dir, opts?: RRDirOpts, { includeMatcher, excludeMatcher, encoding }?: InternalOpts): Entry[];
34
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,214 @@
1
+ import { readdir, stat, lstat } from "node:fs/promises";
2
+ import { readdirSync, statSync, lstatSync } from "node:fs";
3
+ import { sep, resolve } from "node:path";
4
+ import picomatch from "picomatch";
5
+ const encoder = new TextEncoder();
6
+ const toUint8Array = encoder.encode.bind(encoder);
7
+ const decoder = new TextDecoder();
8
+ const toString = decoder.decode.bind(decoder);
9
+ const sepUint8Array = toUint8Array(sep);
10
+ const getEncoding = (dir) => dir instanceof Uint8Array ? "buffer" : "utf8";
11
+ const defaultOpts = {
12
+ strict: false,
13
+ stats: false,
14
+ followSymlinks: false,
15
+ exclude: void 0,
16
+ include: void 0,
17
+ insensitive: false
18
+ };
19
+ function makePath({ name }, dir, encoding) {
20
+ if (encoding === "buffer") {
21
+ return dir === "." ? name : Uint8Array.from([...dir, ...sepUint8Array, ...name]);
22
+ } else {
23
+ return dir === "." ? name : `${dir}${sep}${name}`;
24
+ }
25
+ }
26
+ function build(dirent, path, stats, opts) {
27
+ return {
28
+ path,
29
+ directory: (stats || dirent).isDirectory(),
30
+ symlink: (stats || dirent).isSymbolicLink(),
31
+ ...opts.stats ? { stats } : {}
32
+ };
33
+ }
34
+ function makeMatchers({ include, exclude, insensitive }) {
35
+ const opts = {
36
+ dot: true,
37
+ flags: insensitive ? "i" : void 0
38
+ };
39
+ return {
40
+ includeMatcher: (include == null ? void 0 : include.length) ? (path) => picomatch(include, opts)(resolve(path)) : null,
41
+ excludeMatcher: (exclude == null ? void 0 : exclude.length) ? (path) => picomatch(exclude, opts)(resolve(path)) : null
42
+ };
43
+ }
44
+ async function* rrdir(dir, opts = {}, { includeMatcher, excludeMatcher, encoding } = {}) {
45
+ if (includeMatcher === void 0) {
46
+ opts = { ...defaultOpts, ...opts };
47
+ ({ includeMatcher, excludeMatcher } = makeMatchers(opts));
48
+ if (typeof dir === "string" && /[/\\]$/.test(dir))
49
+ dir = dir.substring(0, dir.length - 1);
50
+ encoding = getEncoding(dir);
51
+ }
52
+ let dirents = [];
53
+ try {
54
+ dirents = await readdir(dir, { encoding, withFileTypes: true });
55
+ } catch (err) {
56
+ if (opts.strict)
57
+ throw err;
58
+ yield { path: dir, err };
59
+ }
60
+ if (!dirents.length)
61
+ return;
62
+ for (const dirent of dirents) {
63
+ const path = makePath(dirent, dir, encoding);
64
+ if (excludeMatcher == null ? void 0 : excludeMatcher(encoding === "buffer" ? toString(path) : path))
65
+ continue;
66
+ const isSymbolicLink = opts.followSymlinks && dirent.isSymbolicLink();
67
+ const encodedPath = encoding === "buffer" ? toString(path) : path;
68
+ const isIncluded = !includeMatcher || includeMatcher(encodedPath);
69
+ let stats;
70
+ if (isIncluded) {
71
+ if (opts.stats || isSymbolicLink) {
72
+ try {
73
+ stats = await (opts.followSymlinks ? stat : lstat)(path);
74
+ } catch (err) {
75
+ if (opts.strict)
76
+ throw err;
77
+ yield { path, err };
78
+ }
79
+ }
80
+ yield build(dirent, path, stats, opts);
81
+ }
82
+ let recurse = false;
83
+ if (isSymbolicLink) {
84
+ if (!stats)
85
+ try {
86
+ stats = await stat(path);
87
+ } catch {
88
+ }
89
+ if (stats && stats.isDirectory())
90
+ recurse = true;
91
+ } else if (dirent.isDirectory()) {
92
+ recurse = true;
93
+ }
94
+ if (recurse)
95
+ yield* rrdir(path, opts, { includeMatcher, excludeMatcher, encoding });
96
+ }
97
+ }
98
+ async function rrdirAsync(dir, opts = {}, { includeMatcher, excludeMatcher, encoding } = {}) {
99
+ if (includeMatcher === void 0) {
100
+ opts = { ...defaultOpts, ...opts };
101
+ ({ includeMatcher, excludeMatcher } = makeMatchers(opts));
102
+ if (typeof dir === "string" && /[/\\]$/.test(dir))
103
+ dir = dir.substring(0, dir.length - 1);
104
+ encoding = getEncoding(dir);
105
+ }
106
+ const results = [];
107
+ let dirents = [];
108
+ try {
109
+ dirents = await readdir(dir, { encoding, withFileTypes: true });
110
+ } catch (err) {
111
+ if (opts.strict)
112
+ throw err;
113
+ results.push({ path: dir, err });
114
+ }
115
+ if (!dirents.length)
116
+ return results;
117
+ await Promise.all(dirents.map(async (dirent) => {
118
+ const path = makePath(dirent, dir, encoding);
119
+ if (excludeMatcher == null ? void 0 : excludeMatcher(encoding === "buffer" ? toString(path) : path))
120
+ return;
121
+ const isSymbolicLink = opts.followSymlinks && dirent.isSymbolicLink();
122
+ const encodedPath = encoding === "buffer" ? toString(path) : path;
123
+ const isIncluded = !includeMatcher || includeMatcher(encodedPath);
124
+ let stats;
125
+ if (isIncluded) {
126
+ if (opts.stats || isSymbolicLink) {
127
+ try {
128
+ stats = await (opts.followSymlinks ? stat : lstat)(path);
129
+ } catch (err) {
130
+ if (opts.strict)
131
+ throw err;
132
+ results.push({ path, err });
133
+ }
134
+ }
135
+ results.push(build(dirent, path, stats, opts));
136
+ }
137
+ let recurse = false;
138
+ if (isSymbolicLink) {
139
+ if (!stats)
140
+ try {
141
+ stats = await stat(path);
142
+ } catch {
143
+ }
144
+ if (stats && stats.isDirectory())
145
+ recurse = true;
146
+ } else if (dirent.isDirectory()) {
147
+ recurse = true;
148
+ }
149
+ if (recurse)
150
+ results.push(...await rrdirAsync(path, opts, { includeMatcher, excludeMatcher, encoding }));
151
+ }));
152
+ return results;
153
+ }
154
+ function rrdirSync(dir, opts = {}, { includeMatcher, excludeMatcher, encoding } = {}) {
155
+ if (includeMatcher === void 0) {
156
+ opts = { ...defaultOpts, ...opts };
157
+ ({ includeMatcher, excludeMatcher } = makeMatchers(opts));
158
+ if (typeof dir === "string" && /[/\\]$/.test(dir))
159
+ dir = dir.substring(0, dir.length - 1);
160
+ encoding = getEncoding(dir);
161
+ }
162
+ const results = [];
163
+ let dirents = [];
164
+ try {
165
+ dirents = readdirSync(dir, { encoding, withFileTypes: true });
166
+ } catch (err) {
167
+ if (opts.strict)
168
+ throw err;
169
+ results.push({ path: dir, err });
170
+ }
171
+ if (!dirents.length)
172
+ return results;
173
+ for (const dirent of dirents) {
174
+ const path = makePath(dirent, dir, encoding);
175
+ if (excludeMatcher == null ? void 0 : excludeMatcher(encoding === "buffer" ? toString(path) : path))
176
+ continue;
177
+ const isSymbolicLink = opts.followSymlinks && dirent.isSymbolicLink();
178
+ const encodedPath = encoding === "buffer" ? toString(path) : path;
179
+ const isIncluded = !includeMatcher || includeMatcher(encodedPath);
180
+ let stats;
181
+ if (isIncluded) {
182
+ if (opts.stats || isSymbolicLink) {
183
+ try {
184
+ stats = (opts.followSymlinks ? statSync : lstatSync)(path);
185
+ } catch (err) {
186
+ if (opts.strict)
187
+ throw err;
188
+ results.push({ path, err });
189
+ }
190
+ }
191
+ results.push(build(dirent, path, stats, opts));
192
+ }
193
+ let recurse = false;
194
+ if (isSymbolicLink) {
195
+ if (!stats)
196
+ try {
197
+ stats = statSync(path);
198
+ } catch {
199
+ }
200
+ if (stats && stats.isDirectory())
201
+ recurse = true;
202
+ } else if (dirent.isDirectory()) {
203
+ recurse = true;
204
+ }
205
+ if (recurse)
206
+ results.push(...rrdirSync(path, opts, { includeMatcher, excludeMatcher, encoding }));
207
+ }
208
+ return results;
209
+ }
210
+ export {
211
+ rrdir,
212
+ rrdirAsync,
213
+ rrdirSync
214
+ };
package/package.json CHANGED
@@ -1,28 +1,36 @@
1
1
  {
2
2
  "name": "rrdir",
3
- "version": "12.1.0",
3
+ "version": "13.1.0",
4
4
  "description": "Recursive directory reader with a delightful API",
5
5
  "author": "silverwind <me@silverwind.io>",
6
6
  "repository": "silverwind/rrdir",
7
7
  "license": "BSD-2-Clause",
8
- "exports": "./index.js",
9
8
  "type": "module",
10
9
  "sideEffects": false,
11
- "engines": {
12
- "node": ">=18"
13
- },
10
+ "main": "./dist/index.js",
11
+ "exports": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
14
13
  "files": [
15
- "./index.js"
14
+ "dist"
16
15
  ],
17
- "devDependencies": {
18
- "eslint": "8.52.0",
19
- "eslint-config-silverwind": "79.0.4",
20
- "updates": "15.0.3",
21
- "versions": "12.0.0",
22
- "vitest": "0.34.6",
23
- "vitest-config-silverwind": "3.0.0"
16
+ "engines": {
17
+ "node": ">=18"
24
18
  },
25
19
  "dependencies": {
26
- "picomatch": "2.3.1"
20
+ "picomatch": "^4.0.2"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "20.12.12",
24
+ "@types/picomatch": "2.3.3",
25
+ "eslint": "8.57.0",
26
+ "eslint-config-silverwind": "83.0.1",
27
+ "eslint-config-silverwind-typescript": "3.2.5",
28
+ "typescript-config-silverwind": "4.2.0",
29
+ "updates": "16.0.1",
30
+ "versions": "12.0.1",
31
+ "vite": "5.2.11",
32
+ "vite-plugin-dts": "3.9.1",
33
+ "vitest": "1.5.0",
34
+ "vitest-config-silverwind": "8.0.4"
27
35
  }
28
36
  }
package/index.js DELETED
@@ -1,209 +0,0 @@
1
- import {readdir, stat, lstat} from "node:fs/promises";
2
- import {readdirSync, statSync, lstatSync} from "node:fs";
3
- import {sep, resolve} from "node:path";
4
- import picomatch from "picomatch";
5
-
6
- const encoder = new TextEncoder();
7
- const toUint8Array = encoder.encode.bind(encoder);
8
- const decoder = new TextDecoder();
9
- const toString = decoder.decode.bind(decoder);
10
- const sepUint8Array = toUint8Array(sep);
11
-
12
- const defaults = {
13
- strict: false,
14
- stats: false,
15
- followSymlinks: false,
16
- exclude: undefined,
17
- include: undefined,
18
- insensitive: false,
19
- };
20
-
21
- function makePath(entry, dir, encoding) {
22
- if (encoding === "buffer") {
23
- return dir === "." ? entry.name : Uint8Array.from([...dir, ...sepUint8Array, ...entry.name]);
24
- } else {
25
- return dir === "." ? entry.name : `${dir}${sep}${entry.name}`;
26
- }
27
- }
28
-
29
- function build(dirent, path, stats, opts) {
30
- return {
31
- path,
32
- directory: (stats || dirent).isDirectory(),
33
- symlink: (stats || dirent).isSymbolicLink(),
34
- ...(opts.stats ? {stats} : {}),
35
- };
36
- }
37
-
38
- function makeMatchers({include, exclude, insensitive}) {
39
- const opts = {
40
- dot: true,
41
- flags: insensitive ? "i" : undefined,
42
- };
43
-
44
- // resolve the path to an absolute one because picomatch can not deal properly
45
- // with relative paths that start with ./ or .\
46
- // https://github.com/micromatch/picomatch/issues/121
47
- return {
48
- includeMatcher: include?.length ? path => picomatch(include, opts)(resolve(path)) : null,
49
- excludeMatcher: exclude?.length ? path => picomatch(exclude, opts)(resolve(path)) : null,
50
- };
51
- }
52
-
53
- export async function* rrdir(dir, opts = {}, {includeMatcher, excludeMatcher, encoding} = {}) {
54
- if (includeMatcher === undefined) {
55
- opts = {...defaults, ...opts};
56
- ({includeMatcher, excludeMatcher} = makeMatchers(opts));
57
- if (/[/\\]$/.test(dir)) dir = dir.substring(0, dir.length - 1);
58
- encoding = dir instanceof Uint8Array ? "buffer" : undefined;
59
- }
60
-
61
- let dirents = [];
62
- try {
63
- dirents = await readdir(dir, {encoding, withFileTypes: true});
64
- } catch (err) {
65
- if (opts.strict) throw err;
66
- yield {path: dir, err};
67
- }
68
- if (!dirents.length) return;
69
-
70
- for (const dirent of dirents) {
71
- const path = makePath(dirent, dir, encoding);
72
- if (excludeMatcher?.(encoding === "buffer" ? toString(path) : path)) continue;
73
-
74
- const isSymbolicLink = opts.followSymlinks && dirent.isSymbolicLink();
75
- const encodedPath = encoding === "buffer" ? toString(path) : path;
76
- const isIncluded = !includeMatcher || includeMatcher(encodedPath);
77
- let stats;
78
-
79
- if (isIncluded) {
80
- if (opts.stats || isSymbolicLink) {
81
- try {
82
- stats = await (opts.followSymlinks ? stat : lstat)(path);
83
- } catch (err) {
84
- if (opts.strict) throw err;
85
- yield {path, err};
86
- }
87
- }
88
-
89
- yield build(dirent, path, stats, opts);
90
- }
91
-
92
- let recurse = false;
93
- if (isSymbolicLink) {
94
- if (!stats) try { stats = await stat(path); } catch {}
95
- if (stats && stats.isDirectory()) recurse = true;
96
- } else if (dirent.isDirectory()) {
97
- recurse = true;
98
- }
99
-
100
- if (recurse) yield* await rrdir(path, opts, {includeMatcher, excludeMatcher, encoding});
101
- }
102
- }
103
-
104
- export async function rrdirAsync(dir, opts = {}, {includeMatcher, excludeMatcher, encoding} = {}) {
105
- if (includeMatcher === undefined) {
106
- opts = {...defaults, ...opts};
107
- ({includeMatcher, excludeMatcher} = makeMatchers(opts));
108
- if (/[/\\]$/.test(dir)) dir = dir.substring(0, dir.length - 1);
109
- encoding = dir instanceof Uint8Array ? "buffer" : undefined;
110
- }
111
-
112
- const results = [];
113
- let dirents = [];
114
- try {
115
- dirents = await readdir(dir, {encoding, withFileTypes: true});
116
- } catch (err) {
117
- if (opts.strict) throw err;
118
- results.push({path: dir, err});
119
- }
120
- if (!dirents.length) return results;
121
-
122
- await Promise.all(dirents.map(async dirent => {
123
- const path = makePath(dirent, dir, encoding);
124
- if (excludeMatcher?.(encoding === "buffer" ? toString(path) : path)) return;
125
-
126
- const isSymbolicLink = opts.followSymlinks && dirent.isSymbolicLink();
127
- const encodedPath = encoding === "buffer" ? toString(path) : path;
128
- const isIncluded = !includeMatcher || includeMatcher(encodedPath);
129
- let stats;
130
-
131
- if (isIncluded) {
132
- if (opts.stats || isSymbolicLink) {
133
- try {
134
- stats = await (opts.followSymlinks ? stat : lstat)(path);
135
- } catch (err) {
136
- if (opts.strict) throw err;
137
- results.push({path, err});
138
- }
139
- }
140
-
141
- results.push(build(dirent, path, stats, opts));
142
- }
143
-
144
- let recurse = false;
145
- if (isSymbolicLink) {
146
- if (!stats) try { stats = await stat(path); } catch {}
147
- if (stats && stats.isDirectory()) recurse = true;
148
- } else if (dirent.isDirectory()) {
149
- recurse = true;
150
- }
151
-
152
- if (recurse) results.push(...await rrdirAsync(path, opts, {includeMatcher, excludeMatcher, encoding}));
153
- }));
154
-
155
- return results;
156
- }
157
-
158
- export function rrdirSync(dir, opts = {}, {includeMatcher, excludeMatcher, encoding} = {}) {
159
- if (includeMatcher === undefined) {
160
- opts = {...defaults, ...opts};
161
- ({includeMatcher, excludeMatcher} = makeMatchers(opts));
162
- if (/[/\\]$/.test(dir)) dir = dir.substring(0, dir.length - 1);
163
- encoding = dir instanceof Uint8Array ? "buffer" : undefined;
164
- }
165
-
166
- const results = [];
167
- let dirents = [];
168
- try {
169
- dirents = readdirSync(dir, {encoding, withFileTypes: true});
170
- } catch (err) {
171
- if (opts.strict) throw err;
172
- results.push({path: dir, err});
173
- }
174
- if (!dirents.length) return results;
175
-
176
- for (const dirent of dirents) {
177
- const path = makePath(dirent, dir, encoding);
178
- if (excludeMatcher?.(encoding === "buffer" ? toString(path) : path)) continue;
179
-
180
- const isSymbolicLink = opts.followSymlinks && dirent.isSymbolicLink();
181
- const encodedPath = encoding === "buffer" ? toString(path) : path;
182
- const isIncluded = !includeMatcher || includeMatcher(encodedPath);
183
- let stats;
184
-
185
- if (isIncluded) {
186
- if (opts.stats || isSymbolicLink) {
187
- try {
188
- stats = (opts.followSymlinks ? statSync : lstatSync)(path);
189
- } catch (err) {
190
- if (opts.strict) throw err;
191
- results.push({path, err});
192
- }
193
- }
194
- results.push(build(dirent, path, stats, opts));
195
- }
196
-
197
- let recurse = false;
198
- if (isSymbolicLink) {
199
- if (!stats) try { stats = statSync(path); } catch {}
200
- if (stats && stats.isDirectory()) recurse = true;
201
- } else if (dirent.isDirectory()) {
202
- recurse = true;
203
- }
204
-
205
- if (recurse) results.push(...rrdirSync(path, opts, {includeMatcher, excludeMatcher, encoding}));
206
- }
207
-
208
- return results;
209
- }