codekingpro/portable-devtools
114k
1import { createRequire } from "module";
2import { basename, dirname, normalize, relative, resolve, sep } from "path";
3import * as nativeFs from "fs";
4
5//#region rolldown:runtime
6var __require = /* @__PURE__ */ createRequire(import.meta.url);
7
8//#endregion
9//#region src/utils.ts
10function cleanPath(path) {
11 let normalized = normalize(path);
12 if (normalized.length > 1 && normalized[normalized.length - 1] === sep) normalized = normalized.substring(0, normalized.length - 1);
13 return normalized;
14}
15const SLASHES_REGEX = /[\\/]/g;
16function convertSlashes(path, separator) {
17 return path.replace(SLASHES_REGEX, separator);
18}
19const WINDOWS_ROOT_DIR_REGEX = /^[a-z]:[\\/]$/i;
20function isRootDirectory(path) {
21 return path === "/" || WINDOWS_ROOT_DIR_REGEX.test(path);
22}
23function normalizePath(path, options) {
24 const { resolvePaths, normalizePath: normalizePath$1, pathSeparator } = options;
25 const pathNeedsCleaning = process.platform === "win32" && path.includes("/") || path.startsWith(".");
26 if (resolvePaths) path = resolve(path);
27 if (normalizePath$1 || pathNeedsCleaning) path = cleanPath(path);
28 if (path === ".") return "";
29 const needsSeperator = path[path.length - 1] !== pathSeparator;
30 return convertSlashes(needsSeperator ? path + pathSeparator : path, pathSeparator);
31}
32
33//#endregion
34//#region src/api/functions/join-path.ts
35function joinPathWithBasePath(filename, directoryPath) {
36 return directoryPath + filename;
37}
38function joinPathWithRelativePath(root, options) {
39 return function(filename, directoryPath) {
40 const sameRoot = directoryPath.startsWith(root);
41 if (sameRoot) return directoryPath.slice(root.length) + filename;
42 else return convertSlashes(relative(root, directoryPath), options.pathSeparator) + options.pathSeparator + filename;
43 };
44}
45function joinPath(filename) {
46 return filename;
47}
48function joinDirectoryPath(filename, directoryPath, separator) {
49 return directoryPath + filename + separator;
50}
51function build$7(root, options) {
52 const { relativePaths, includeBasePath } = options;
53 return relativePaths && root ? joinPathWithRelativePath(root, options) : includeBasePath ? joinPathWithBasePath : joinPath;
54}
55
56//#endregion
57//#region src/api/functions/push-directory.ts
58function pushDirectoryWithRelativePath(root) {
59 return function(directoryPath, paths) {
60 paths.push(directoryPath.substring(root.length) || ".");
61 };
62}
63function pushDirectoryFilterWithRelativePath(root) {
64 return function(directoryPath, paths, filters) {
65 const relativePath = directoryPath.substring(root.length) || ".";
66 if (filters.every((filter) => filter(relativePath, true))) paths.push(relativePath);
67 };
68}
69const pushDirectory = (directoryPath, paths) => {
70 paths.push(directoryPath || ".");
71};
72const pushDirectoryFilter = (directoryPath, paths, filters) => {
73 const path = directoryPath || ".";
74 if (filters.every((filter) => filter(path, true))) paths.push(path);
75};
76const empty$2 = () => {};
77function build$6(root, options) {
78 const { includeDirs, filters, relativePaths } = options;
79 if (!includeDirs) return empty$2;
80 if (relativePaths) return filters && filters.length ? pushDirectoryFilterWithRelativePath(root) : pushDirectoryWithRelativePath(root);
81 return filters && filters.length ? pushDirectoryFilter : pushDirectory;
82}
83
84//#endregion
85//#region src/api/functions/push-file.ts
86const pushFileFilterAndCount = (filename, _paths, counts, filters) => {
87 if (filters.every((filter) => filter(filename, false))) counts.files++;
88};
89const pushFileFilter = (filename, paths, _counts, filters) => {
90 if (filters.every((filter) => filter(filename, false))) paths.push(filename);
91};
92const pushFileCount = (_filename, _paths, counts, _filters) => {
93 counts.files++;
94};
95const pushFile = (filename, paths) => {
96 paths.push(filename);
97};
98const empty$1 = () => {};
99function build$5(options) {
100 const { excludeFiles, filters, onlyCounts } = options;
101 if (excludeFiles) return empty$1;
102 if (filters && filters.length) return onlyCounts ? pushFileFilterAndCount : pushFileFilter;
103 else if (onlyCounts) return pushFileCount;
104 else return pushFile;
105}
106
107//#endregion
108//#region src/api/functions/get-array.ts
109const getArray = (paths) => {
110 return paths;
111};
112const getArrayGroup = () => {
113 return [""].slice(0, 0);
114};
115function build$4(options) {
116 return options.group ? getArrayGroup : getArray;
117}
118
119//#endregion
120//#region src/api/functions/group-files.ts
121const groupFiles = (groups, directory, files) => {
122 groups.push({
123 directory,
124 files,
125 dir: directory
126 });
127};
128const empty = () => {};
129function build$3(options) {
130 return options.group ? groupFiles : empty;
131}
132
133//#endregion
134//#region src/api/functions/resolve-symlink.ts
135const resolveSymlinksAsync = function(path, state, callback$1) {
136 const { queue, fs, options: { suppressErrors } } = state;
137 queue.enqueue();
138 fs.realpath(path, (error, resolvedPath) => {
139 if (error) return queue.dequeue(suppressErrors ? null : error, state);
140 fs.stat(resolvedPath, (error$1, stat) => {
141 if (error$1) return queue.dequeue(suppressErrors ? null : error$1, state);
142 if (stat.isDirectory() && isRecursive(path, resolvedPath, state)) return queue.dequeue(null, state);
143 callback$1(stat, resolvedPath);
144 queue.dequeue(null, state);
145 });
146 });
147};
148const resolveSymlinks = function(path, state, callback$1) {
149 const { queue, fs, options: { suppressErrors } } = state;
150 queue.enqueue();
151 try {
152 const resolvedPath = fs.realpathSync(path);
153 const stat = fs.statSync(resolvedPath);
154 if (stat.isDirectory() && isRecursive(path, resolvedPath, state)) return;
155 callback$1(stat, resolvedPath);
156 } catch (e) {
157 if (!suppressErrors) throw e;
158 }
159};
160function build$2(options, isSynchronous) {
161 if (!options.resolveSymlinks || options.excludeSymlinks) return null;
162 return isSynchronous ? resolveSymlinks : resolveSymlinksAsync;
163}
164function isRecursive(path, resolved, state) {
165 if (state.options.useRealPaths) return isRecursiveUsingRealPaths(resolved, state);
166 let parent = dirname(path);
167 let depth = 1;
168 while (parent !== state.root && depth < 2) {
169 const resolvedPath = state.symlinks.get(parent);
170 const isSameRoot = !!resolvedPath && (resolvedPath === resolved || resolvedPath.startsWith(resolved) || resolved.startsWith(resolvedPath));
171 if (isSameRoot) depth++;
172 else parent = dirname(parent);
173 }
174 state.symlinks.set(path, resolved);
175 return depth > 1;
176}
177function isRecursiveUsingRealPaths(resolved, state) {
178 return state.visited.includes(resolved + state.options.pathSeparator);
179}
180
181//#endregion
182//#region src/api/functions/invoke-callback.ts
183const onlyCountsSync = (state) => {
184 return state.counts;
185};
186const groupsSync = (state) => {
187 return state.groups;
188};
189const defaultSync = (state) => {
190 return state.paths;
191};
192const limitFilesSync = (state) => {
193 return state.paths.slice(0, state.options.maxFiles);
194};
195const onlyCountsAsync = (state, error, callback$1) => {
196 report(error, callback$1, state.counts, state.options.suppressErrors);
197 return null;
198};
199const defaultAsync = (state, error, callback$1) => {
200 report(error, callback$1, state.paths, state.options.suppressErrors);
201 return null;
202};
203const limitFilesAsync = (state, error, callback$1) => {
204 report(error, callback$1, state.paths.slice(0, state.options.maxFiles), state.options.suppressErrors);
205 return null;
206};
207const groupsAsync = (state, error, callback$1) => {
208 report(error, callback$1, state.groups, state.options.suppressErrors);
209 return null;
210};
211function report(error, callback$1, output, suppressErrors) {
212 if (error && !suppressErrors) callback$1(error, output);
213 else callback$1(null, output);
214}
215function build$1(options, isSynchronous) {
216 const { onlyCounts, group, maxFiles } = options;
217 if (onlyCounts) return isSynchronous ? onlyCountsSync : onlyCountsAsync;
218 else if (group) return isSynchronous ? groupsSync : groupsAsync;
219 else if (maxFiles) return isSynchronous ? limitFilesSync : limitFilesAsync;
220 else return isSynchronous ? defaultSync : defaultAsync;
221}
222
223//#endregion
224//#region src/api/functions/walk-directory.ts
225const readdirOpts = { withFileTypes: true };
226const walkAsync = (state, crawlPath, directoryPath, currentDepth, callback$1) => {
227 state.queue.enqueue();
228 if (currentDepth < 0) return state.queue.dequeue(null, state);
229 const { fs } = state;
230 state.visited.push(crawlPath);
231 state.counts.directories++;
232 fs.readdir(crawlPath || ".", readdirOpts, (error, entries = []) => {
233 callback$1(entries, directoryPath, currentDepth);
234 state.queue.dequeue(state.options.suppressErrors ? null : error, state);
235 });
236};
237const walkSync = (state, crawlPath, directoryPath, currentDepth, callback$1) => {
238 const { fs } = state;
239 if (currentDepth < 0) return;
240 state.visited.push(crawlPath);
241 state.counts.directories++;
242 let entries = [];
243 try {
244 entries = fs.readdirSync(crawlPath || ".", readdirOpts);
245 } catch (e) {
246 if (!state.options.suppressErrors) throw e;
247 }
248 callback$1(entries, directoryPath, currentDepth);
249};
250function build(isSynchronous) {
251 return isSynchronous ? walkSync : walkAsync;
252}
253
254//#endregion
255//#region src/api/queue.ts
256/**
257* This is a custom stateless queue to track concurrent async fs calls.
258* It increments a counter whenever a call is queued and decrements it
259* as soon as it completes. When the counter hits 0, it calls onQueueEmpty.
260*/
261var Queue = class {
262 count = 0;
263 constructor(onQueueEmpty) {
264 this.onQueueEmpty = onQueueEmpty;
265 }
266 enqueue() {
267 this.count++;
268 return this.count;
269 }
270 dequeue(error, output) {
271 if (this.onQueueEmpty && (--this.count <= 0 || error)) {
272 this.onQueueEmpty(error, output);
273 if (error) {
274 output.controller.abort();
275 this.onQueueEmpty = void 0;
276 }
277 }
278 }
279};
280
281//#endregion
282//#region src/api/counter.ts
283var Counter = class {
284 _files = 0;
285 _directories = 0;
286 set files(num) {
287 this._files = num;
288 }
289 get files() {
290 return this._files;
291 }
292 set directories(num) {
293 this._directories = num;
294 }
295 get directories() {
296 return this._directories;
297 }
298 /**
299 * @deprecated use `directories` instead
300 */
301 /* c8 ignore next 3 */
302 get dirs() {
303 return this._directories;
304 }
305};
306
307//#endregion
308//#region src/api/aborter.ts
309/**
310* AbortController is not supported on Node 14 so we use this until we can drop
311* support for Node 14.
312*/
313var Aborter = class {
314 aborted = false;
315 abort() {
316 this.aborted = true;
317 }
318};
319
320//#endregion
321//#region src/api/walker.ts
322var Walker = class {
323 root;
324 isSynchronous;
325 state;
326 joinPath;
327 pushDirectory;
328 pushFile;
329 getArray;
330 groupFiles;
331 resolveSymlink;
332 walkDirectory;
333 callbackInvoker;
334 constructor(root, options, callback$1) {
335 this.isSynchronous = !callback$1;
336 this.callbackInvoker = build$1(options, this.isSynchronous);
337 this.root = normalizePath(root, options);
338 this.state = {
339 root: isRootDirectory(this.root) ? this.root : this.root.slice(0, -1),
340 paths: [""].slice(0, 0),
341 groups: [],
342 counts: new Counter(),
343 options,
344 queue: new Queue((error, state) => this.callbackInvoker(state, error, callback$1)),
345 symlinks: /* @__PURE__ */ new Map(),
346 visited: [""].slice(0, 0),
347 controller: new Aborter(),
348 fs: options.fs || nativeFs
349 };
350 this.joinPath = build$7(this.root, options);
351 this.pushDirectory = build$6(this.root, options);
352 this.pushFile = build$5(options);
353 this.getArray = build$4(options);
354 this.groupFiles = build$3(options);
355 this.resolveSymlink = build$2(options, this.isSynchronous);
356 this.walkDirectory = build(this.isSynchronous);
357 }
358 start() {
359 this.pushDirectory(this.root, this.state.paths, this.state.options.filters);
360 this.walkDirectory(this.state, this.root, this.root, this.state.options.maxDepth, this.walk);
361 return this.isSynchronous ? this.callbackInvoker(this.state, null) : null;
362 }
363 walk = (entries, directoryPath, depth) => {
364 const { paths, options: { filters, resolveSymlinks: resolveSymlinks$1, excludeSymlinks, exclude, maxFiles, signal, useRealPaths, pathSeparator }, controller } = this.state;
365 if (controller.aborted || signal && signal.aborted || maxFiles && paths.length > maxFiles) return;
366 const files = this.getArray(this.state.paths);
367 for (let i = 0; i < entries.length; ++i) {
368 const entry = entries[i];
369 if (entry.isFile() || entry.isSymbolicLink() && !resolveSymlinks$1 && !excludeSymlinks) {
370 const filename = this.joinPath(entry.name, directoryPath);
371 this.pushFile(filename, files, this.state.counts, filters);
372 } else if (entry.isDirectory()) {
373 let path = joinDirectoryPath(entry.name, directoryPath, this.state.options.pathSeparator);
374 if (exclude && exclude(entry.name, path)) continue;
375 this.pushDirectory(path, paths, filters);
376 this.walkDirectory(this.state, path, path, depth - 1, this.walk);
377 } else if (this.resolveSymlink && entry.isSymbolicLink()) {
378 let path = joinPathWithBasePath(entry.name, directoryPath);
379 this.resolveSymlink(path, this.state, (stat, resolvedPath) => {
380 if (stat.isDirectory()) {
381 resolvedPath = normalizePath(resolvedPath, this.state.options);
382 if (exclude && exclude(entry.name, useRealPaths ? resolvedPath : path + pathSeparator)) return;
383 this.walkDirectory(this.state, resolvedPath, useRealPaths ? resolvedPath : path + pathSeparator, depth - 1, this.walk);
384 } else {
385 resolvedPath = useRealPaths ? resolvedPath : path;
386 const filename = basename(resolvedPath);
387 const directoryPath$1 = normalizePath(dirname(resolvedPath), this.state.options);
388 resolvedPath = this.joinPath(filename, directoryPath$1);
389 this.pushFile(resolvedPath, files, this.state.counts, filters);
390 }
391 });
392 }
393 }
394 this.groupFiles(this.state.groups, directoryPath, files);
395 };
396};
397
398//#endregion
399//#region src/api/async.ts
400function promise(root, options) {
401 return new Promise((resolve$1, reject) => {
402 callback(root, options, (err, output) => {
403 if (err) return reject(err);
404 resolve$1(output);
405 });
406 });
407}
408function callback(root, options, callback$1) {
409 let walker = new Walker(root, options, callback$1);
410 walker.start();
411}
412
413//#endregion
414//#region src/api/sync.ts
415function sync(root, options) {
416 const walker = new Walker(root, options);
417 return walker.start();
418}
419
420//#endregion
421//#region src/builder/api-builder.ts
422var APIBuilder = class {
423 constructor(root, options) {
424 this.root = root;
425 this.options = options;
426 }
427 withPromise() {
428 return promise(this.root, this.options);
429 }
430 withCallback(cb) {
431 callback(this.root, this.options, cb);
432 }
433 sync() {
434 return sync(this.root, this.options);
435 }
436};
437
438//#endregion
439//#region src/builder/index.ts
440let pm = null;
441/* c8 ignore next 6 */
442try {
443 __require.resolve("picomatch");
444 pm = __require("picomatch");
445} catch {}
446var Builder = class {
447 globCache = {};
448 options = {
449 maxDepth: Infinity,
450 suppressErrors: true,
451 pathSeparator: sep,
452 filters: []
453 };
454 globFunction;
455 constructor(options) {
456 this.options = {
457 ...this.options,
458 ...options
459 };
460 this.globFunction = this.options.globFunction;
461 }
462 group() {
463 this.options.group = true;
464 return this;
465 }
466 withPathSeparator(separator) {
467 this.options.pathSeparator = separator;
468 return this;
469 }
470 withBasePath() {
471 this.options.includeBasePath = true;
472 return this;
473 }
474 withRelativePaths() {
475 this.options.relativePaths = true;
476 return this;
477 }
478 withDirs() {
479 this.options.includeDirs = true;
480 return this;
481 }
482 withMaxDepth(depth) {
483 this.options.maxDepth = depth;
484 return this;
485 }
486 withMaxFiles(limit) {
487 this.options.maxFiles = limit;
488 return this;
489 }
490 withFullPaths() {
491 this.options.resolvePaths = true;
492 this.options.includeBasePath = true;
493 return this;
494 }
495 withErrors() {
496 this.options.suppressErrors = false;
497 return this;
498 }
499 withSymlinks({ resolvePaths = true } = {}) {
500 this.options.resolveSymlinks = true;
501 this.options.useRealPaths = resolvePaths;
502 return this.withFullPaths();
503 }
504 withAbortSignal(signal) {
505 this.options.signal = signal;
506 return this;
507 }
508 normalize() {
509 this.options.normalizePath = true;
510 return this;
511 }
512 filter(predicate) {
513 this.options.filters.push(predicate);
514 return this;
515 }
516 onlyDirs() {
517 this.options.excludeFiles = true;
518 this.options.includeDirs = true;
519 return this;
520 }
521 exclude(predicate) {
522 this.options.exclude = predicate;
523 return this;
524 }
525 onlyCounts() {
526 this.options.onlyCounts = true;
527 return this;
528 }
529 crawl(root) {
530 return new APIBuilder(root || ".", this.options);
531 }
532 withGlobFunction(fn) {
533 this.globFunction = fn;
534 return this;
535 }
536 /**
537 * @deprecated Pass options using the constructor instead:
538 * ```ts
539 * new fdir(options).crawl("/path/to/root");
540 * ```
541 * This method will be removed in v7.0
542 */
543 /* c8 ignore next 4 */
544 crawlWithOptions(root, options) {
545 this.options = {
546 ...this.options,
547 ...options
548 };
549 return new APIBuilder(root || ".", this.options);
550 }
551 glob(...patterns) {
552 if (this.globFunction) return this.globWithOptions(patterns);
553 return this.globWithOptions(patterns, ...[{ dot: true }]);
554 }
555 globWithOptions(patterns, ...options) {
556 const globFn = this.globFunction || pm;
557 /* c8 ignore next 5 */
558 if (!globFn) throw new Error("Please specify a glob function to use glob matching.");
559 var isMatch = this.globCache[patterns.join("\0")];
560 if (!isMatch) {
561 isMatch = globFn(patterns, ...options);
562 this.globCache[patterns.join("\0")] = isMatch;
563 }
564 this.options.filters.push((path) => isMatch(path));
565 return this;
566 }
567};
568
569//#endregion
570export { Builder as fdir };