rrdir 14.2.0 → 14.2.2

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.
Files changed (3) hide show
  1. package/README.md +6 -5
  2. package/dist/index.js +1 -288
  3. package/package.json +8 -8
package/README.md CHANGED
@@ -7,11 +7,12 @@ This module is able to read any path including ones that contain invalid UTF-8 s
7
7
 
8
8
  | Benchmark | rrdir | fdir |
9
9
  |---|---|---|
10
- | async | 64ms | 57ms |
11
- | sync | 154ms | 178ms |
12
- | async + exclude | 46ms | — |
13
- | sync + exclude | 120ms | — |
14
- | async iterator | 319ms | — |
10
+ | async | 56ms | 56ms |
11
+ | sync | 155ms | 174ms |
12
+ | async + exclude | 43ms | — |
13
+ | sync + exclude | 118ms | — |
14
+ | async iterator | 77ms | — |
15
+ | async iterator + exclude | 68ms | — |
15
16
 
16
17
  Results for 122K entries (111K files, 11K dirs), Node.js on macOS. rrdir returns richer entries (path + directory + symlink) while fdir returns only paths. Run with `make bench`.
17
18
 
package/dist/index.js CHANGED
@@ -1,288 +1 @@
1
- import { lstat, readdir, stat } from "node:fs/promises";
2
- import { lstatSync, readdirSync, statSync } from "node:fs";
3
- import { isAbsolute, resolve, sep } from "node:path";
4
-
5
- //#region index.ts
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
- const isWindows = sep === "\\";
12
- const getEncoding = (dir) => dir instanceof Uint8Array ? "buffer" : "utf8";
13
- const defaultOpts = {
14
- strict: false,
15
- stats: false,
16
- followSymlinks: false,
17
- exclude: void 0,
18
- include: void 0,
19
- insensitive: false
20
- };
21
- function makePath({ name }, dir, encoding) {
22
- if (encoding === "buffer") {
23
- if (dir === ".") return name;
24
- const dirBuf = dir;
25
- const nameBuf = name;
26
- const result = new Uint8Array(dirBuf.length + sepUint8Array.length + nameBuf.length);
27
- result.set(dirBuf, 0);
28
- result.set(sepUint8Array, dirBuf.length);
29
- result.set(nameBuf, dirBuf.length + sepUint8Array.length);
30
- return result;
31
- } else return dir === "." ? name : `${dir}${sep}${name}`;
32
- }
33
- function build(path, isDir, isSym, stats, needStats) {
34
- const entry = {
35
- path,
36
- directory: stats ? stats.isDirectory() : isDir,
37
- symlink: stats ? stats.isSymbolicLink() : isSym
38
- };
39
- if (needStats) entry.stats = stats;
40
- return entry;
41
- }
42
- function globToRegex(pattern, insensitive) {
43
- pattern = pattern.replace(/\\/g, "/");
44
- const endsWithDoubleStar = pattern.endsWith("/**");
45
- let regex = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "__DOUBLESTAR__").replace(/\*/g, "[^/]*").replace(/__DOUBLESTAR__/g, ".*");
46
- if (endsWithDoubleStar) {
47
- regex = regex.slice(0, -3);
48
- regex = `^${regex}(?:/.*)?$`;
49
- } else regex = `^${regex}$`;
50
- return new RegExp(regex, insensitive ? "i" : "");
51
- }
52
- function createMatcher(patterns, insensitive) {
53
- if (!patterns?.length) return null;
54
- const regexes = patterns.map((pattern) => globToRegex(pattern, insensitive));
55
- const cwdPrefix = resolve(".") + sep;
56
- return (path) => {
57
- const absolute = isAbsolute(path) ? path : cwdPrefix + path;
58
- const normalizedPath = isWindows ? absolute.replace(/\\/g, "/") : absolute;
59
- return regexes.some((regex) => regex.test(normalizedPath));
60
- };
61
- }
62
- function makeMatchers({ include, exclude, insensitive }) {
63
- return {
64
- includeMatcher: createMatcher(include || [], insensitive || false),
65
- excludeMatcher: createMatcher(exclude || [], insensitive || false)
66
- };
67
- }
68
- function initOpts(dir, opts) {
69
- opts = {
70
- ...defaultOpts,
71
- ...opts
72
- };
73
- const { includeMatcher, excludeMatcher } = makeMatchers(opts);
74
- if (typeof dir === "string" && /[/\\]$/.test(dir)) dir = dir.substring(0, dir.length - 1);
75
- const encoding = getEncoding(dir);
76
- return {
77
- dir,
78
- opts,
79
- internalOpts: {
80
- includeMatcher,
81
- excludeMatcher,
82
- hasMatcher: Boolean(excludeMatcher || includeMatcher),
83
- encoding,
84
- followSymlinks: Boolean(opts.followSymlinks),
85
- needStats: Boolean(opts.stats),
86
- strict: Boolean(opts.strict),
87
- readdirOpts: {
88
- encoding,
89
- withFileTypes: true
90
- }
91
- }
92
- };
93
- }
94
- function getStringPath(path, encoding) {
95
- return encoding === "buffer" ? toString(path) : path;
96
- }
97
- async function* rrdir(dir, opts = {}) {
98
- const init = initOpts(dir, opts);
99
- const { hasMatcher, encoding, followSymlinks, needStats, strict, readdirOpts } = init.internalOpts;
100
- dir = init.dir;
101
- if (!hasMatcher && !followSymlinks && !needStats) {
102
- const stack = [dir];
103
- while (stack.length > 0) {
104
- const currentDir = stack.pop();
105
- let dirents;
106
- try {
107
- dirents = await readdir(currentDir, readdirOpts);
108
- } catch (err) {
109
- if (strict) throw err;
110
- yield {
111
- path: currentDir,
112
- err
113
- };
114
- continue;
115
- }
116
- for (const dirent of dirents) {
117
- const path = makePath(dirent, currentDir, encoding);
118
- const isDir = dirent.isDirectory();
119
- yield {
120
- path,
121
- directory: isDir,
122
- symlink: dirent.isSymbolicLink()
123
- };
124
- if (isDir) stack.push(path);
125
- }
126
- }
127
- return;
128
- }
129
- yield* rrdirInner(dir, init.opts, init.internalOpts);
130
- }
131
- async function* rrdirInner(dir, opts, internalOpts) {
132
- const { includeMatcher, excludeMatcher, hasMatcher, encoding, followSymlinks, needStats, strict, readdirOpts } = internalOpts;
133
- let dirents = [];
134
- try {
135
- dirents = await readdir(dir, readdirOpts);
136
- } catch (err) {
137
- if (strict) throw err;
138
- yield {
139
- path: dir,
140
- err
141
- };
142
- }
143
- if (!dirents.length) return;
144
- for (const dirent of dirents) {
145
- const path = makePath(dirent, dir, encoding);
146
- let isIncluded = true;
147
- if (hasMatcher) {
148
- const stringPath = getStringPath(path, encoding);
149
- if (excludeMatcher?.(stringPath)) continue;
150
- isIncluded = !includeMatcher || includeMatcher(stringPath);
151
- }
152
- const isDir = dirent.isDirectory();
153
- const isSym = dirent.isSymbolicLink();
154
- const isFollowedSym = followSymlinks && isSym;
155
- let stats;
156
- if (isIncluded) {
157
- if (needStats || isFollowedSym) try {
158
- stats = await (followSymlinks ? stat : lstat)(path);
159
- } catch (err) {
160
- if (strict) throw err;
161
- yield {
162
- path,
163
- err
164
- };
165
- }
166
- yield build(path, isDir, isSym, stats, needStats);
167
- }
168
- let recurse = isDir;
169
- if (isFollowedSym) {
170
- if (!stats) try {
171
- stats = await stat(path);
172
- } catch {}
173
- recurse = Boolean(stats?.isDirectory());
174
- }
175
- if (recurse) yield* rrdirInner(path, opts, internalOpts);
176
- }
177
- }
178
- async function rrdirAsync(dir, opts = {}) {
179
- const init = initOpts(dir, opts);
180
- const results = [];
181
- await rrdirAsyncInner(init.dir, init.opts, init.internalOpts, results);
182
- return results;
183
- }
184
- async function rrdirAsyncInner(dir, opts, internalOpts, results) {
185
- const { includeMatcher, excludeMatcher, hasMatcher, encoding, followSymlinks, needStats, strict, readdirOpts } = internalOpts;
186
- let dirents = [];
187
- try {
188
- dirents = await readdir(dir, readdirOpts);
189
- } catch (err) {
190
- if (strict) throw err;
191
- results.push({
192
- path: dir,
193
- err
194
- });
195
- }
196
- if (!dirents.length) return;
197
- const pendingDirs = [];
198
- for (const dirent of dirents) {
199
- const path = makePath(dirent, dir, encoding);
200
- let isIncluded = true;
201
- if (hasMatcher) {
202
- const stringPath = getStringPath(path, encoding);
203
- if (excludeMatcher?.(stringPath)) continue;
204
- isIncluded = !includeMatcher || includeMatcher(stringPath);
205
- }
206
- const isDir = dirent.isDirectory();
207
- const isSym = dirent.isSymbolicLink();
208
- const isFollowedSym = followSymlinks && isSym;
209
- let stats;
210
- if (isIncluded) {
211
- if (needStats || isFollowedSym) try {
212
- stats = await (followSymlinks ? stat : lstat)(path);
213
- } catch (err) {
214
- if (strict) throw err;
215
- results.push({
216
- path,
217
- err
218
- });
219
- }
220
- results.push(build(path, isDir, isSym, stats, needStats));
221
- }
222
- let recurse = isDir;
223
- if (isFollowedSym) {
224
- if (!stats) try {
225
- stats = await stat(path);
226
- } catch {}
227
- recurse = Boolean(stats?.isDirectory());
228
- }
229
- if (recurse) pendingDirs.push(path);
230
- }
231
- if (pendingDirs.length) await Promise.all(pendingDirs.map((p) => rrdirAsyncInner(p, opts, internalOpts, results)));
232
- }
233
- function rrdirSync(dir, opts = {}) {
234
- const init = initOpts(dir, opts);
235
- const results = [];
236
- rrdirSyncInner(init.dir, init.opts, init.internalOpts, results);
237
- return results;
238
- }
239
- function rrdirSyncInner(dir, opts, internalOpts, results) {
240
- const { includeMatcher, excludeMatcher, hasMatcher, encoding, followSymlinks, needStats, strict, readdirOpts } = internalOpts;
241
- let dirents = [];
242
- try {
243
- dirents = readdirSync(dir, readdirOpts);
244
- } catch (err) {
245
- if (strict) throw err;
246
- results.push({
247
- path: dir,
248
- err
249
- });
250
- }
251
- if (!dirents.length) return;
252
- for (const dirent of dirents) {
253
- const path = makePath(dirent, dir, encoding);
254
- let isIncluded = true;
255
- if (hasMatcher) {
256
- const stringPath = getStringPath(path, encoding);
257
- if (excludeMatcher?.(stringPath)) continue;
258
- isIncluded = !includeMatcher || includeMatcher(stringPath);
259
- }
260
- const isDir = dirent.isDirectory();
261
- const isSym = dirent.isSymbolicLink();
262
- const isFollowedSym = followSymlinks && isSym;
263
- let stats;
264
- if (isIncluded) {
265
- if (needStats || isFollowedSym) try {
266
- stats = (followSymlinks ? statSync : lstatSync)(path);
267
- } catch (err) {
268
- if (strict) throw err;
269
- results.push({
270
- path,
271
- err
272
- });
273
- }
274
- results.push(build(path, isDir, isSym, stats, needStats));
275
- }
276
- let recurse = isDir;
277
- if (isFollowedSym) {
278
- if (!stats) try {
279
- stats = statSync(path);
280
- } catch {}
281
- recurse = Boolean(stats?.isDirectory());
282
- }
283
- if (recurse) rrdirSyncInner(path, opts, internalOpts, results);
284
- }
285
- }
286
-
287
- //#endregion
288
- export { rrdir, rrdirAsync, rrdirSync };
1
+ import{lstat as e,readdir as t,stat as n}from"node:fs/promises";import{lstatSync as r,readdirSync as i,statSync as a}from"node:fs";import{isAbsolute as o,resolve as s,sep as c}from"node:path";const l=new TextEncoder,u=l.encode.bind(l),d=new TextDecoder,f=d.decode.bind(d),p=u(c);function m({name:e},t,n){if(n===`buffer`){if(t===`.`)return e;let n=t,r=e,i=new Uint8Array(n.length+p.length+r.length);return i.set(n,0),i.set(p,n.length),i.set(r,n.length+p.length),i}else return t===`.`?e:String(t)+c+String(e)}function h(e,t,n,r,i){let a={path:e,directory:r?r.isDirectory():t,symlink:r?r.isSymbolicLink():n};return i&&(a.stats=r),a}function g(e,t){e=e.replace(/\\/g,`/`);let n=e.endsWith(`/**`),r=e.replace(/[.+?^${}()|[\]\\]/g,`\\$&`).replace(/\*\*/g,`__DOUBLESTAR__`).replace(/\*/g,`[^/]*`).replace(/__DOUBLESTAR__/g,`.*`);return n?(r=r.slice(0,-3),r=`^${r}(?:/.*)?$`):r=`^${r}$`,new RegExp(r,t?`i`:``)}function _(e,t){if(!e?.length)return null;let n=e.map(e=>g(e,t)),r=s(`.`)+c;return e=>{let t=o(e)?e:r+e,i=c===`\\`?t.replace(/\\/g,`/`):t;return n.some(e=>e.test(i))}}function v(e,t){typeof e==`string`&&/[/\\]$/.test(e)&&(e=e.substring(0,e.length-1));let n=e instanceof Uint8Array?`buffer`:`utf8`,r=t.insensitive||!1,i=_(t.include||[],r),a=_(t.exclude||[],r);return{dir:e,internalOpts:{includeMatcher:i,excludeMatcher:a,hasMatcher:!!(a||i),encoding:n,followSymlinks:!!t.followSymlinks,needStats:!!t.stats,strict:!!t.strict,readdirOpts:{encoding:n,withFileTypes:!0}}}}function y(e,t){return t===`buffer`?f(e):e}async function*b(r,i={}){let a=v(r,i),{includeMatcher:o,excludeMatcher:s,hasMatcher:c,encoding:l,followSymlinks:u,needStats:d,strict:f,readdirOpts:p}=a.internalOpts;r=a.dir;let g=[r];for(;g.length>0;){let r=await Promise.all(g.map(e=>t(e,p).then(t=>({dir:e,dirents:t,err:void 0}),t=>({dir:e,dirents:void 0,err:t})))),i=[];for(let{dir:t,dirents:a,err:p}of r){if(p){if(f)throw p;yield{path:t,err:p};continue}for(let r of a){let a=m(r,t,l),p=!0;if(c){let e=y(a,l);if(s?.(e))continue;p=!o||o(e)}let g=r.isDirectory(),_=r.isSymbolicLink(),v=u&&_,b;if(p){if(d||v)try{b=await(u?n:e)(a)}catch(e){if(f)throw e;yield{path:a,err:e}}yield h(a,g,_,b,d)}let x=g;if(v){if(!b)try{b=await n(a)}catch{}x=!!b?.isDirectory()}x&&i.push(a)}}g=i}}async function x(e,t={}){let n=v(e,t),r=[];return await S(n.dir,n.internalOpts,r),r}async function S(r,i,a){let{includeMatcher:o,excludeMatcher:s,hasMatcher:c,encoding:l,followSymlinks:u,needStats:d,strict:f,readdirOpts:p}=i,g=[];try{g=await t(r,p)}catch(e){if(f)throw e;a.push({path:r,err:e})}if(!g.length)return;let _=[];for(let t of g){let i=m(t,r,l),p=!0;if(c){let e=y(i,l);if(s?.(e))continue;p=!o||o(e)}let g=t.isDirectory(),v=t.isSymbolicLink(),b=u&&v,x;if(p){if(d||b)try{x=await(u?n:e)(i)}catch(e){if(f)throw e;a.push({path:i,err:e})}a.push(h(i,g,v,x,d))}let S=g;if(b){if(!x)try{x=await n(i)}catch{}S=!!x?.isDirectory()}S&&_.push(i)}_.length&&await Promise.all(_.map(e=>S(e,i,a)))}function C(e,t={}){let n=v(e,t),r=[];return w(n.dir,n.internalOpts,r),r}function w(e,t,n){let{includeMatcher:o,excludeMatcher:s,hasMatcher:c,encoding:l,followSymlinks:u,needStats:d,strict:f,readdirOpts:p}=t,g=[];try{g=i(e,p)}catch(t){if(f)throw t;n.push({path:e,err:t})}if(g.length)for(let i of g){let p=m(i,e,l),g=!0;if(c){let e=y(p,l);if(s?.(e))continue;g=!o||o(e)}let _=i.isDirectory(),v=i.isSymbolicLink(),b=u&&v,x;if(g){if(d||b)try{x=(u?a:r)(p)}catch(e){if(f)throw e;n.push({path:p,err:e})}n.push(h(p,_,v,x,d))}let S=_;if(b){if(!x)try{x=a(p)}catch{}S=!!x?.isDirectory()}S&&w(p,t,n)}}export{b as rrdir,x as rrdirAsync,C as rrdirSync};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rrdir",
3
- "version": "14.2.0",
3
+ "version": "14.2.2",
4
4
  "description": "Recursive directory reader with a delightful API",
5
5
  "author": "silverwind <me@silverwind.io>",
6
6
  "repository": "silverwind/rrdir",
@@ -17,17 +17,17 @@
17
17
  "node": ">=22"
18
18
  },
19
19
  "devDependencies": {
20
- "@types/node": "25.3.5",
21
- "@typescript/native-preview": "7.0.0-dev.20260306.1",
22
- "eslint": "9.39.3",
23
- "eslint-config-silverwind": "122.0.6",
20
+ "@types/node": "25.4.0",
21
+ "@typescript/native-preview": "7.0.0-dev.20260312.1",
22
+ "eslint": "9.39.4",
23
+ "eslint-config-silverwind": "124.0.7",
24
24
  "fdir": "6.5.0",
25
25
  "jest-extended": "7.0.0",
26
- "tsdown": "0.21.0",
27
- "tsdown-config-silverwind": "2.0.0",
26
+ "tsdown": "0.21.2",
27
+ "tsdown-config-silverwind": "2.0.1",
28
28
  "typescript": "5.9.3",
29
29
  "typescript-config-silverwind": "15.0.0",
30
- "updates": "17.8.2",
30
+ "updates": "17.9.1",
31
31
  "updates-config-silverwind": "1.0.3",
32
32
  "versions": "14.2.1",
33
33
  "vitest": "4.0.18",