-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
225 lines (187 loc) · 5.78 KB
/
Copy pathindex.js
File metadata and controls
225 lines (187 loc) · 5.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
const { opendir, stat } = require("fs/promises");
const { opendirSync, statSync } = require("fs");
/**
* Check if a directory is empty (async)
* @param {string|string[]} dirPath - Path(s) to directory to check
* @param {Object|Function} [options] - Options object or callback
* @param {Array<string|RegExp|Function>} [options.ignore] - Patterns to ignore
* @param {boolean} [options.followSymlinks] - Whether to follow symbolic links
* @param {Function} [callback] - Node-style callback: (err, result) => void
* @returns {Promise<boolean>|undefined} - Promise result or undefined when callback is used
*/
function isEmptyDir(dirPath, options = {}, callback) {
if (typeof options === "function") {
callback = options;
options = {};
}
if (callback !== undefined && typeof callback !== "function") {
throw new TypeError("Expected callback to be a function");
}
const promise = isEmptyDirCore(dirPath, options);
if (callback) {
promise.then((result) => callback(null, result)).catch((error) => callback(error));
return undefined;
}
return promise;
}
async function isEmptyDirCore(dirPath, options = {}) {
const { ignore = [], followSymlinks = false } = options;
validateDirPathInput(dirPath);
if (Array.isArray(dirPath)) {
for (const singlePath of dirPath) {
const isEmpty = await isEmptySingleDir(singlePath, { ignore, followSymlinks });
if (!isEmpty) {
return false;
}
}
return true;
}
return isEmptySingleDir(dirPath, { ignore, followSymlinks });
}
/**
* Check if a directory is empty (sync)
* @param {string|string[]} dirPath - Path(s) to directory to check
* @param {Object} [options] - Options object
* @param {Array<string|RegExp|Function>} [options.ignore] - Patterns to ignore
* @param {boolean} [options.followSymlinks] - Whether to follow symbolic links
* @returns {boolean} - True if ALL given directories are empty or only contain ignored files
*/
function isEmptyDirSync(dirPath, options = {}) {
const { ignore = [], followSymlinks = false } = options;
validateDirPathInput(dirPath);
if (Array.isArray(dirPath)) {
return dirPath.every((singlePath) => isEmptySingleDirSync(singlePath, { ignore, followSymlinks }));
}
return isEmptySingleDirSync(dirPath, { ignore, followSymlinks });
}
async function isEmptySingleDir(dirPath, { ignore, followSymlinks }) {
let stats;
try {
stats = await stat(dirPath);
} catch (error) {
throw mapFsError(error, dirPath);
}
if (!stats.isDirectory()) {
throw new Error(`Path is not a directory: ${dirPath}`);
}
let dir;
try {
dir = await opendir(dirPath);
} catch (error) {
throw mapFsError(error, dirPath);
}
try {
// Iterate lazily so large directories can short-circuit early.
for await (const file of dir) {
if (shouldIgnore(file.name, ignore)) {
continue;
}
if (!followSymlinks && file.isSymbolicLink()) {
continue;
}
return false; // Found an entry that shouldn't be ignored
}
} finally {
await closeDir(dir);
}
return true;
}
function isEmptySingleDirSync(dirPath, { ignore, followSymlinks }) {
let stats;
try {
stats = statSync(dirPath);
} catch (error) {
throw mapFsError(error, dirPath);
}
if (!stats.isDirectory()) {
throw new Error(`Path is not a directory: ${dirPath}`);
}
let dir;
try {
dir = opendirSync(dirPath);
} catch (error) {
throw mapFsError(error, dirPath);
}
try {
let file;
while ((file = dir.readSync()) !== null) {
if (shouldIgnore(file.name, ignore)) {
continue;
}
if (!followSymlinks && file.isSymbolicLink()) {
continue;
}
return false; // Found an entry that shouldn't be ignored
}
} finally {
closeDirSync(dir);
}
return true;
}
function shouldIgnore(filename, ignorePatterns) {
if (!Array.isArray(ignorePatterns)) {
return false;
}
return ignorePatterns.some((pattern) => {
if (typeof pattern === "string") {
return filename === pattern;
}
if (pattern instanceof RegExp) {
return pattern.test(filename);
}
if (typeof pattern === "function") {
return pattern(filename);
}
throw new TypeError(
`ignore patterns must be a string, RegExp, or function; got ${typeof pattern}`,
);
});
}
function validateDirPathInput(dirPath) {
if (!(typeof dirPath === "string" || Array.isArray(dirPath))) {
throw new TypeError("Expected dirPath to be a string or an array of strings");
}
if (typeof dirPath === "string") {
if (dirPath.length === 0) {
throw new TypeError("Expected dirPath to be a non-empty string");
}
return;
}
if (dirPath.length === 0) {
throw new TypeError("Expected dirPath array to contain at least one path");
}
for (const currentPath of dirPath) {
if (typeof currentPath !== "string" || currentPath.length === 0) {
throw new TypeError("Expected each dirPath entry to be a non-empty string");
}
}
}
function mapFsError(error, targetPath) {
if (error && error.code === "ENOENT") {
return new Error(`Directory does not exist: ${targetPath}`);
}
if (error && (error.code === "EACCES" || error.code === "EPERM")) {
return new Error(`Permission denied: ${targetPath}`);
}
return error;
}
async function closeDir(dir) {
try {
await dir.close();
} catch {
// Ignore close errors; iteration already produced the meaningful failure.
}
}
function closeDirSync(dir) {
try {
dir.closeSync();
} catch {
// Ignore close errors; iteration already produced the meaningful failure.
}
}
// CommonJS exports
module.exports = isEmptyDir;
module.exports.isEmptyDir = isEmptyDir;
module.exports.isEmptyDirSync = isEmptyDirSync;
// ES modules exports (will be handled by build script)
module.exports.default = isEmptyDir;