UNPKG

6.73 kBJavaScriptView Raw
1import { readdir, stat, lstat } from "node:fs/promises";
2import { readdirSync, statSync, lstatSync } from "node:fs";
3import { sep, resolve } from "node:path";
4import picomatch from "picomatch";
5const encoder = new TextEncoder();
6const toUint8Array = encoder.encode.bind(encoder);
7const decoder = new TextDecoder();
8const toString = decoder.decode.bind(decoder);
9const sepUint8Array = toUint8Array(sep);
10const getEncoding = (dir) => dir instanceof Uint8Array ? "buffer" : "utf8";
11const defaultOpts = {
12 strict: false,
13 stats: false,
14 followSymlinks: false,
15 exclude: void 0,
16 include: void 0,
17 insensitive: false
18};
19function 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}
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}
34function makeMatchers({ include, exclude, insensitive }) {
35 const opts = {
36 dot: true,
37 flags: insensitive ? "i" : void 0
38 };
39 return {
40 includeMatcher: include?.length ? (path) => picomatch(include, opts)(resolve(path)) : null,
41 excludeMatcher: exclude?.length ? (path) => picomatch(exclude, opts)(resolve(path)) : null
42 };
43}
44async 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?.(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}
98async 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?.(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}
154function 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?.(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}
210export {
211 rrdir,
212 rrdirAsync,
213 rrdirSync
214};