UNPKG

6.3 kBJavaScriptView Raw
1import {readdir, stat, lstat} from "node:fs/promises";
2import {readdirSync, statSync, lstatSync} from "node:fs";
3import {sep} from "node:path";
4
5const sepBuffer = Buffer.from(sep);
6
7const defaults = {
8 strict: false,
9 stats: false,
10 followSymlinks: false,
11 exclude: undefined,
12 include: undefined,
13 insensitive: false,
14};
15
16const escRe = str => str.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&");
17
18function makePath(entry, dir, encoding) {
19 if (encoding === "buffer") {
20 return dir === "." ? entry.name : Buffer.from([...dir, ...sepBuffer, ...entry.name]);
21 } else {
22 return dir === "." ? entry.name : `${dir}${sep}${entry.name}`;
23 }
24}
25
26function 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
35function makeMatcher(filters, flags) {
36 const res = filters.map(f => {
37 return new RegExp(`${escRe(f).replace(/\\\*+/g, ".*").replace(/\/\.\*/, ".*")}$`, flags);
38 });
39 return str => {
40 for (const re of res) {
41 if (re.test(str)) return true;
42 }
43 return false;
44 };
45}
46
47function makeMatchers({include, exclude, insensitive}) {
48 const flags = insensitive ? "i" : "";
49 return {
50 includeMatcher: include ? makeMatcher(include, flags) : null,
51 excludeMatcher: exclude ? makeMatcher(exclude, flags) : null,
52 };
53}
54
55export async function* rrdir(dir, opts = {}, {includeMatcher, excludeMatcher, encoding} = {}) {
56 if (includeMatcher === undefined) {
57 opts = {...defaults, ...opts};
58 ({includeMatcher, excludeMatcher} = makeMatchers(opts));
59 if (/[/\\]$/.test(dir)) dir = dir.substring(0, dir.length - 1);
60 encoding = Buffer.isBuffer(dir) ? "buffer" : undefined;
61 }
62
63 let dirents = [];
64 try {
65 dirents = await readdir(dir, {encoding, withFileTypes: true});
66 } catch (err) {
67 if (opts.strict) throw err;
68 yield {path: dir, err};
69 }
70 if (!dirents.length) return;
71
72 for (const dirent of dirents) {
73 const path = makePath(dirent, dir, encoding);
74 if (excludeMatcher?.(encoding === "buffer" ? String(path) : path)) continue;
75
76 const isSymbolicLink = opts.followSymlinks && dirent.isSymbolicLink();
77 const isIncluded = !includeMatcher || includeMatcher(encoding === "buffer" ? String(path) : path);
78 let stats;
79
80 if (isIncluded) {
81 if (opts.stats || isSymbolicLink) {
82 try {
83 stats = await (opts.followSymlinks ? stat : lstat)(path);
84 } catch (err) {
85 if (opts.strict) throw err;
86 yield {path, err};
87 }
88 }
89
90 yield build(dirent, path, stats, opts);
91 }
92
93 let recurse = false;
94 if (isSymbolicLink) {
95 if (!stats) try { stats = await stat(path); } catch {}
96 if (stats && stats.isDirectory()) recurse = true;
97 } else if (dirent.isDirectory()) {
98 recurse = true;
99 }
100
101 if (recurse) yield* await rrdir(path, opts, {includeMatcher, excludeMatcher, encoding});
102 }
103}
104
105export async function rrdirAsync(dir, opts = {}, {includeMatcher, excludeMatcher, encoding} = {}) {
106 if (includeMatcher === undefined) {
107 opts = {...defaults, ...opts};
108 ({includeMatcher, excludeMatcher} = makeMatchers(opts));
109 if (/[/\\]$/.test(dir)) dir = dir.substring(0, dir.length - 1);
110 encoding = Buffer.isBuffer(dir) ? "buffer" : undefined;
111 }
112
113 const results = [];
114 let dirents = [];
115 try {
116 dirents = await readdir(dir, {encoding, withFileTypes: true});
117 } catch (err) {
118 if (opts.strict) throw err;
119 results.push({path: dir, err});
120 }
121 if (!dirents.length) return results;
122
123 await Promise.all(dirents.map(async dirent => {
124 const path = makePath(dirent, dir, encoding);
125 if (excludeMatcher?.(encoding === "buffer" ? String(path) : path)) return;
126
127 const isSymbolicLink = opts.followSymlinks && dirent.isSymbolicLink();
128 const isIncluded = !includeMatcher || includeMatcher(encoding === "buffer" ? String(path) : path);
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
158export 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 = Buffer.isBuffer(dir) ? "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" ? String(path) : path)) continue;
179
180 const isSymbolicLink = opts.followSymlinks && dirent.isSymbolicLink();
181 const isIncluded = !includeMatcher || includeMatcher(encoding === "buffer" ? String(path) : path);
182 let stats;
183
184 if (isIncluded) {
185 if (opts.stats || isSymbolicLink) {
186 try {
187 stats = (opts.followSymlinks ? statSync : lstatSync)(path);
188 } catch (err) {
189 if (opts.strict) throw err;
190 results.push({path, err});
191 }
192 }
193 results.push(build(dirent, path, stats, opts));
194 }
195
196 let recurse = false;
197 if (isSymbolicLink) {
198 if (!stats) try { stats = statSync(path); } catch {}
199 if (stats && stats.isDirectory()) recurse = true;
200 } else if (dirent.isDirectory()) {
201 recurse = true;
202 }
203
204 if (recurse) results.push(...rrdirSync(path, opts, {includeMatcher, excludeMatcher, encoding}));
205 }
206
207 return results;
208}