• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import { LRUCache } from 'lru-cache';
2import { posix, win32 } from 'path';
3import { fileURLToPath } from 'url';
4import * as actualFS from 'fs';
5import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSync as rps, } from 'fs';
6const realpathSync = rps.native;
7// TODO: test perf of fs/promises realpath vs realpathCB,
8// since the promises one uses realpath.native
9import { lstat, readdir, readlink, realpath } from 'fs/promises';
10import { Minipass } from 'minipass';
11const defaultFS = {
12    lstatSync,
13    readdir: readdirCB,
14    readdirSync,
15    readlinkSync,
16    realpathSync,
17    promises: {
18        lstat,
19        readdir,
20        readlink,
21        realpath,
22    },
23};
24// if they just gave us require('fs') then use our default
25const fsFromOption = (fsOption) => !fsOption || fsOption === defaultFS || fsOption === actualFS
26    ? defaultFS
27    : {
28        ...defaultFS,
29        ...fsOption,
30        promises: {
31            ...defaultFS.promises,
32            ...(fsOption.promises || {}),
33        },
34    };
35// turn something like //?/c:/ into c:\
36const uncDriveRegexp = /^\\\\\?\\([a-z]:)\\?$/i;
37const uncToDrive = (rootPath) => rootPath.replace(/\//g, '\\').replace(uncDriveRegexp, '$1\\');
38// windows paths are separated by either / or \
39const eitherSep = /[\\\/]/;
40const UNKNOWN = 0; // may not even exist, for all we know
41const IFIFO = 0b0001;
42const IFCHR = 0b0010;
43const IFDIR = 0b0100;
44const IFBLK = 0b0110;
45const IFREG = 0b1000;
46const IFLNK = 0b1010;
47const IFSOCK = 0b1100;
48const IFMT = 0b1111;
49// mask to unset low 4 bits
50const IFMT_UNKNOWN = ~IFMT;
51// set after successfully calling readdir() and getting entries.
52const READDIR_CALLED = 16;
53// set after a successful lstat()
54const LSTAT_CALLED = 32;
55// set if an entry (or one of its parents) is definitely not a dir
56const ENOTDIR = 64;
57// set if an entry (or one of its parents) does not exist
58// (can also be set on lstat errors like EACCES or ENAMETOOLONG)
59const ENOENT = 128;
60// cannot have child entries -- also verify &IFMT is either IFDIR or IFLNK
61// set if we fail to readlink
62const ENOREADLINK = 256;
63// set if we know realpath() will fail
64const ENOREALPATH = 512;
65const ENOCHILD = ENOTDIR | ENOENT | ENOREALPATH;
66const TYPEMASK = 1023;
67const entToType = (s) => s.isFile()
68    ? IFREG
69    : s.isDirectory()
70        ? IFDIR
71        : s.isSymbolicLink()
72            ? IFLNK
73            : s.isCharacterDevice()
74                ? IFCHR
75                : s.isBlockDevice()
76                    ? IFBLK
77                    : s.isSocket()
78                        ? IFSOCK
79                        : s.isFIFO()
80                            ? IFIFO
81                            : UNKNOWN;
82// normalize unicode path names
83const normalizeCache = new Map();
84const normalize = (s) => {
85    const c = normalizeCache.get(s);
86    if (c)
87        return c;
88    const n = s.normalize('NFKD');
89    normalizeCache.set(s, n);
90    return n;
91};
92const normalizeNocaseCache = new Map();
93const normalizeNocase = (s) => {
94    const c = normalizeNocaseCache.get(s);
95    if (c)
96        return c;
97    const n = normalize(s.toLowerCase());
98    normalizeNocaseCache.set(s, n);
99    return n;
100};
101/**
102 * An LRUCache for storing resolved path strings or Path objects.
103 * @internal
104 */
105export class ResolveCache extends LRUCache {
106    constructor() {
107        super({ max: 256 });
108    }
109}
110// In order to prevent blowing out the js heap by allocating hundreds of
111// thousands of Path entries when walking extremely large trees, the "children"
112// in this tree are represented by storing an array of Path entries in an
113// LRUCache, indexed by the parent.  At any time, Path.children() may return an
114// empty array, indicating that it doesn't know about any of its children, and
115// thus has to rebuild that cache.  This is fine, it just means that we don't
116// benefit as much from having the cached entries, but huge directory walks
117// don't blow out the stack, and smaller ones are still as fast as possible.
118//
119//It does impose some complexity when building up the readdir data, because we
120//need to pass a reference to the children array that we started with.
121/**
122 * an LRUCache for storing child entries.
123 * @internal
124 */
125export class ChildrenCache extends LRUCache {
126    constructor(maxSize = 16 * 1024) {
127        super({
128            maxSize,
129            // parent + children
130            sizeCalculation: a => a.length + 1,
131        });
132    }
133}
134const setAsCwd = Symbol('PathScurry setAsCwd');
135/**
136 * Path objects are sort of like a super-powered
137 * {@link https://nodejs.org/docs/latest/api/fs.html#class-fsdirent fs.Dirent}
138 *
139 * Each one represents a single filesystem entry on disk, which may or may not
140 * exist. It includes methods for reading various types of information via
141 * lstat, readlink, and readdir, and caches all information to the greatest
142 * degree possible.
143 *
144 * Note that fs operations that would normally throw will instead return an
145 * "empty" value. This is in order to prevent excessive overhead from error
146 * stack traces.
147 */
148export class PathBase {
149    /**
150     * the basename of this path
151     *
152     * **Important**: *always* test the path name against any test string
153     * usingthe {@link isNamed} method, and not by directly comparing this
154     * string. Otherwise, unicode path strings that the system sees as identical
155     * will not be properly treated as the same path, leading to incorrect
156     * behavior and possible security issues.
157     */
158    name;
159    /**
160     * the Path entry corresponding to the path root.
161     *
162     * @internal
163     */
164    root;
165    /**
166     * All roots found within the current PathScurry family
167     *
168     * @internal
169     */
170    roots;
171    /**
172     * a reference to the parent path, or undefined in the case of root entries
173     *
174     * @internal
175     */
176    parent;
177    /**
178     * boolean indicating whether paths are compared case-insensitively
179     * @internal
180     */
181    nocase;
182    // potential default fs override
183    #fs;
184    // Stats fields
185    #dev;
186    get dev() {
187        return this.#dev;
188    }
189    #mode;
190    get mode() {
191        return this.#mode;
192    }
193    #nlink;
194    get nlink() {
195        return this.#nlink;
196    }
197    #uid;
198    get uid() {
199        return this.#uid;
200    }
201    #gid;
202    get gid() {
203        return this.#gid;
204    }
205    #rdev;
206    get rdev() {
207        return this.#rdev;
208    }
209    #blksize;
210    get blksize() {
211        return this.#blksize;
212    }
213    #ino;
214    get ino() {
215        return this.#ino;
216    }
217    #size;
218    get size() {
219        return this.#size;
220    }
221    #blocks;
222    get blocks() {
223        return this.#blocks;
224    }
225    #atimeMs;
226    get atimeMs() {
227        return this.#atimeMs;
228    }
229    #mtimeMs;
230    get mtimeMs() {
231        return this.#mtimeMs;
232    }
233    #ctimeMs;
234    get ctimeMs() {
235        return this.#ctimeMs;
236    }
237    #birthtimeMs;
238    get birthtimeMs() {
239        return this.#birthtimeMs;
240    }
241    #atime;
242    get atime() {
243        return this.#atime;
244    }
245    #mtime;
246    get mtime() {
247        return this.#mtime;
248    }
249    #ctime;
250    get ctime() {
251        return this.#ctime;
252    }
253    #birthtime;
254    get birthtime() {
255        return this.#birthtime;
256    }
257    #matchName;
258    #depth;
259    #fullpath;
260    #fullpathPosix;
261    #relative;
262    #relativePosix;
263    #type;
264    #children;
265    #linkTarget;
266    #realpath;
267    /**
268     * This property is for compatibility with the Dirent class as of
269     * Node v20, where Dirent['path'] refers to the path of the directory
270     * that was passed to readdir.  So, somewhat counterintuitively, this
271     * property refers to the *parent* path, not the path object itself.
272     * For root entries, it's the path to the entry itself.
273     */
274    get path() {
275        return (this.parent || this).fullpath();
276    }
277    /**
278     * Do not create new Path objects directly.  They should always be accessed
279     * via the PathScurry class or other methods on the Path class.
280     *
281     * @internal
282     */
283    constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
284        this.name = name;
285        this.#matchName = nocase ? normalizeNocase(name) : normalize(name);
286        this.#type = type & TYPEMASK;
287        this.nocase = nocase;
288        this.roots = roots;
289        this.root = root || this;
290        this.#children = children;
291        this.#fullpath = opts.fullpath;
292        this.#relative = opts.relative;
293        this.#relativePosix = opts.relativePosix;
294        this.parent = opts.parent;
295        if (this.parent) {
296            this.#fs = this.parent.#fs;
297        }
298        else {
299            this.#fs = fsFromOption(opts.fs);
300        }
301    }
302    /**
303     * Returns the depth of the Path object from its root.
304     *
305     * For example, a path at `/foo/bar` would have a depth of 2.
306     */
307    depth() {
308        if (this.#depth !== undefined)
309            return this.#depth;
310        if (!this.parent)
311            return (this.#depth = 0);
312        return (this.#depth = this.parent.depth() + 1);
313    }
314    /**
315     * @internal
316     */
317    childrenCache() {
318        return this.#children;
319    }
320    /**
321     * Get the Path object referenced by the string path, resolved from this Path
322     */
323    resolve(path) {
324        if (!path) {
325            return this;
326        }
327        const rootPath = this.getRootString(path);
328        const dir = path.substring(rootPath.length);
329        const dirParts = dir.split(this.splitSep);
330        const result = rootPath
331            ? this.getRoot(rootPath).#resolveParts(dirParts)
332            : this.#resolveParts(dirParts);
333        return result;
334    }
335    #resolveParts(dirParts) {
336        let p = this;
337        for (const part of dirParts) {
338            p = p.child(part);
339        }
340        return p;
341    }
342    /**
343     * Returns the cached children Path objects, if still available.  If they
344     * have fallen out of the cache, then returns an empty array, and resets the
345     * READDIR_CALLED bit, so that future calls to readdir() will require an fs
346     * lookup.
347     *
348     * @internal
349     */
350    children() {
351        const cached = this.#children.get(this);
352        if (cached) {
353            return cached;
354        }
355        const children = Object.assign([], { provisional: 0 });
356        this.#children.set(this, children);
357        this.#type &= ~READDIR_CALLED;
358        return children;
359    }
360    /**
361     * Resolves a path portion and returns or creates the child Path.
362     *
363     * Returns `this` if pathPart is `''` or `'.'`, or `parent` if pathPart is
364     * `'..'`.
365     *
366     * This should not be called directly.  If `pathPart` contains any path
367     * separators, it will lead to unsafe undefined behavior.
368     *
369     * Use `Path.resolve()` instead.
370     *
371     * @internal
372     */
373    child(pathPart, opts) {
374        if (pathPart === '' || pathPart === '.') {
375            return this;
376        }
377        if (pathPart === '..') {
378            return this.parent || this;
379        }
380        // find the child
381        const children = this.children();
382        const name = this.nocase
383            ? normalizeNocase(pathPart)
384            : normalize(pathPart);
385        for (const p of children) {
386            if (p.#matchName === name) {
387                return p;
388            }
389        }
390        // didn't find it, create provisional child, since it might not
391        // actually exist.  If we know the parent isn't a dir, then
392        // in fact it CAN'T exist.
393        const s = this.parent ? this.sep : '';
394        const fullpath = this.#fullpath
395            ? this.#fullpath + s + pathPart
396            : undefined;
397        const pchild = this.newChild(pathPart, UNKNOWN, {
398            ...opts,
399            parent: this,
400            fullpath,
401        });
402        if (!this.canReaddir()) {
403            pchild.#type |= ENOENT;
404        }
405        // don't have to update provisional, because if we have real children,
406        // then provisional is set to children.length, otherwise a lower number
407        children.push(pchild);
408        return pchild;
409    }
410    /**
411     * The relative path from the cwd. If it does not share an ancestor with
412     * the cwd, then this ends up being equivalent to the fullpath()
413     */
414    relative() {
415        if (this.#relative !== undefined) {
416            return this.#relative;
417        }
418        const name = this.name;
419        const p = this.parent;
420        if (!p) {
421            return (this.#relative = this.name);
422        }
423        const pv = p.relative();
424        return pv + (!pv || !p.parent ? '' : this.sep) + name;
425    }
426    /**
427     * The relative path from the cwd, using / as the path separator.
428     * If it does not share an ancestor with
429     * the cwd, then this ends up being equivalent to the fullpathPosix()
430     * On posix systems, this is identical to relative().
431     */
432    relativePosix() {
433        if (this.sep === '/')
434            return this.relative();
435        if (this.#relativePosix !== undefined)
436            return this.#relativePosix;
437        const name = this.name;
438        const p = this.parent;
439        if (!p) {
440            return (this.#relativePosix = this.fullpathPosix());
441        }
442        const pv = p.relativePosix();
443        return pv + (!pv || !p.parent ? '' : '/') + name;
444    }
445    /**
446     * The fully resolved path string for this Path entry
447     */
448    fullpath() {
449        if (this.#fullpath !== undefined) {
450            return this.#fullpath;
451        }
452        const name = this.name;
453        const p = this.parent;
454        if (!p) {
455            return (this.#fullpath = this.name);
456        }
457        const pv = p.fullpath();
458        const fp = pv + (!p.parent ? '' : this.sep) + name;
459        return (this.#fullpath = fp);
460    }
461    /**
462     * On platforms other than windows, this is identical to fullpath.
463     *
464     * On windows, this is overridden to return the forward-slash form of the
465     * full UNC path.
466     */
467    fullpathPosix() {
468        if (this.#fullpathPosix !== undefined)
469            return this.#fullpathPosix;
470        if (this.sep === '/')
471            return (this.#fullpathPosix = this.fullpath());
472        if (!this.parent) {
473            const p = this.fullpath().replace(/\\/g, '/');
474            if (/^[a-z]:\//i.test(p)) {
475                return (this.#fullpathPosix = `//?/${p}`);
476            }
477            else {
478                return (this.#fullpathPosix = p);
479            }
480        }
481        const p = this.parent;
482        const pfpp = p.fullpathPosix();
483        const fpp = pfpp + (!pfpp || !p.parent ? '' : '/') + this.name;
484        return (this.#fullpathPosix = fpp);
485    }
486    /**
487     * Is the Path of an unknown type?
488     *
489     * Note that we might know *something* about it if there has been a previous
490     * filesystem operation, for example that it does not exist, or is not a
491     * link, or whether it has child entries.
492     */
493    isUnknown() {
494        return (this.#type & IFMT) === UNKNOWN;
495    }
496    isType(type) {
497        return this[`is${type}`]();
498    }
499    getType() {
500        return this.isUnknown()
501            ? 'Unknown'
502            : this.isDirectory()
503                ? 'Directory'
504                : this.isFile()
505                    ? 'File'
506                    : this.isSymbolicLink()
507                        ? 'SymbolicLink'
508                        : this.isFIFO()
509                            ? 'FIFO'
510                            : this.isCharacterDevice()
511                                ? 'CharacterDevice'
512                                : this.isBlockDevice()
513                                    ? 'BlockDevice'
514                                    : /* c8 ignore start */ this.isSocket()
515                                        ? 'Socket'
516                                        : 'Unknown';
517        /* c8 ignore stop */
518    }
519    /**
520     * Is the Path a regular file?
521     */
522    isFile() {
523        return (this.#type & IFMT) === IFREG;
524    }
525    /**
526     * Is the Path a directory?
527     */
528    isDirectory() {
529        return (this.#type & IFMT) === IFDIR;
530    }
531    /**
532     * Is the path a character device?
533     */
534    isCharacterDevice() {
535        return (this.#type & IFMT) === IFCHR;
536    }
537    /**
538     * Is the path a block device?
539     */
540    isBlockDevice() {
541        return (this.#type & IFMT) === IFBLK;
542    }
543    /**
544     * Is the path a FIFO pipe?
545     */
546    isFIFO() {
547        return (this.#type & IFMT) === IFIFO;
548    }
549    /**
550     * Is the path a socket?
551     */
552    isSocket() {
553        return (this.#type & IFMT) === IFSOCK;
554    }
555    /**
556     * Is the path a symbolic link?
557     */
558    isSymbolicLink() {
559        return (this.#type & IFLNK) === IFLNK;
560    }
561    /**
562     * Return the entry if it has been subject of a successful lstat, or
563     * undefined otherwise.
564     *
565     * Does not read the filesystem, so an undefined result *could* simply
566     * mean that we haven't called lstat on it.
567     */
568    lstatCached() {
569        return this.#type & LSTAT_CALLED ? this : undefined;
570    }
571    /**
572     * Return the cached link target if the entry has been the subject of a
573     * successful readlink, or undefined otherwise.
574     *
575     * Does not read the filesystem, so an undefined result *could* just mean we
576     * don't have any cached data. Only use it if you are very sure that a
577     * readlink() has been called at some point.
578     */
579    readlinkCached() {
580        return this.#linkTarget;
581    }
582    /**
583     * Returns the cached realpath target if the entry has been the subject
584     * of a successful realpath, or undefined otherwise.
585     *
586     * Does not read the filesystem, so an undefined result *could* just mean we
587     * don't have any cached data. Only use it if you are very sure that a
588     * realpath() has been called at some point.
589     */
590    realpathCached() {
591        return this.#realpath;
592    }
593    /**
594     * Returns the cached child Path entries array if the entry has been the
595     * subject of a successful readdir(), or [] otherwise.
596     *
597     * Does not read the filesystem, so an empty array *could* just mean we
598     * don't have any cached data. Only use it if you are very sure that a
599     * readdir() has been called recently enough to still be valid.
600     */
601    readdirCached() {
602        const children = this.children();
603        return children.slice(0, children.provisional);
604    }
605    /**
606     * Return true if it's worth trying to readlink.  Ie, we don't (yet) have
607     * any indication that readlink will definitely fail.
608     *
609     * Returns false if the path is known to not be a symlink, if a previous
610     * readlink failed, or if the entry does not exist.
611     */
612    canReadlink() {
613        if (this.#linkTarget)
614            return true;
615        if (!this.parent)
616            return false;
617        // cases where it cannot possibly succeed
618        const ifmt = this.#type & IFMT;
619        return !((ifmt !== UNKNOWN && ifmt !== IFLNK) ||
620            this.#type & ENOREADLINK ||
621            this.#type & ENOENT);
622    }
623    /**
624     * Return true if readdir has previously been successfully called on this
625     * path, indicating that cachedReaddir() is likely valid.
626     */
627    calledReaddir() {
628        return !!(this.#type & READDIR_CALLED);
629    }
630    /**
631     * Returns true if the path is known to not exist. That is, a previous lstat
632     * or readdir failed to verify its existence when that would have been
633     * expected, or a parent entry was marked either enoent or enotdir.
634     */
635    isENOENT() {
636        return !!(this.#type & ENOENT);
637    }
638    /**
639     * Return true if the path is a match for the given path name.  This handles
640     * case sensitivity and unicode normalization.
641     *
642     * Note: even on case-sensitive systems, it is **not** safe to test the
643     * equality of the `.name` property to determine whether a given pathname
644     * matches, due to unicode normalization mismatches.
645     *
646     * Always use this method instead of testing the `path.name` property
647     * directly.
648     */
649    isNamed(n) {
650        return !this.nocase
651            ? this.#matchName === normalize(n)
652            : this.#matchName === normalizeNocase(n);
653    }
654    /**
655     * Return the Path object corresponding to the target of a symbolic link.
656     *
657     * If the Path is not a symbolic link, or if the readlink call fails for any
658     * reason, `undefined` is returned.
659     *
660     * Result is cached, and thus may be outdated if the filesystem is mutated.
661     */
662    async readlink() {
663        const target = this.#linkTarget;
664        if (target) {
665            return target;
666        }
667        if (!this.canReadlink()) {
668            return undefined;
669        }
670        /* c8 ignore start */
671        // already covered by the canReadlink test, here for ts grumples
672        if (!this.parent) {
673            return undefined;
674        }
675        /* c8 ignore stop */
676        try {
677            const read = await this.#fs.promises.readlink(this.fullpath());
678            const linkTarget = this.parent.resolve(read);
679            if (linkTarget) {
680                return (this.#linkTarget = linkTarget);
681            }
682        }
683        catch (er) {
684            this.#readlinkFail(er.code);
685            return undefined;
686        }
687    }
688    /**
689     * Synchronous {@link PathBase.readlink}
690     */
691    readlinkSync() {
692        const target = this.#linkTarget;
693        if (target) {
694            return target;
695        }
696        if (!this.canReadlink()) {
697            return undefined;
698        }
699        /* c8 ignore start */
700        // already covered by the canReadlink test, here for ts grumples
701        if (!this.parent) {
702            return undefined;
703        }
704        /* c8 ignore stop */
705        try {
706            const read = this.#fs.readlinkSync(this.fullpath());
707            const linkTarget = this.parent.resolve(read);
708            if (linkTarget) {
709                return (this.#linkTarget = linkTarget);
710            }
711        }
712        catch (er) {
713            this.#readlinkFail(er.code);
714            return undefined;
715        }
716    }
717    #readdirSuccess(children) {
718        // succeeded, mark readdir called bit
719        this.#type |= READDIR_CALLED;
720        // mark all remaining provisional children as ENOENT
721        for (let p = children.provisional; p < children.length; p++) {
722            children[p].#markENOENT();
723        }
724    }
725    #markENOENT() {
726        // mark as UNKNOWN and ENOENT
727        if (this.#type & ENOENT)
728            return;
729        this.#type = (this.#type | ENOENT) & IFMT_UNKNOWN;
730        this.#markChildrenENOENT();
731    }
732    #markChildrenENOENT() {
733        // all children are provisional and do not exist
734        const children = this.children();
735        children.provisional = 0;
736        for (const p of children) {
737            p.#markENOENT();
738        }
739    }
740    #markENOREALPATH() {
741        this.#type |= ENOREALPATH;
742        this.#markENOTDIR();
743    }
744    // save the information when we know the entry is not a dir
745    #markENOTDIR() {
746        // entry is not a directory, so any children can't exist.
747        // this *should* be impossible, since any children created
748        // after it's been marked ENOTDIR should be marked ENOENT,
749        // so it won't even get to this point.
750        /* c8 ignore start */
751        if (this.#type & ENOTDIR)
752            return;
753        /* c8 ignore stop */
754        let t = this.#type;
755        // this could happen if we stat a dir, then delete it,
756        // then try to read it or one of its children.
757        if ((t & IFMT) === IFDIR)
758            t &= IFMT_UNKNOWN;
759        this.#type = t | ENOTDIR;
760        this.#markChildrenENOENT();
761    }
762    #readdirFail(code = '') {
763        // markENOTDIR and markENOENT also set provisional=0
764        if (code === 'ENOTDIR' || code === 'EPERM') {
765            this.#markENOTDIR();
766        }
767        else if (code === 'ENOENT') {
768            this.#markENOENT();
769        }
770        else {
771            this.children().provisional = 0;
772        }
773    }
774    #lstatFail(code = '') {
775        // Windows just raises ENOENT in this case, disable for win CI
776        /* c8 ignore start */
777        if (code === 'ENOTDIR') {
778            // already know it has a parent by this point
779            const p = this.parent;
780            p.#markENOTDIR();
781        }
782        else if (code === 'ENOENT') {
783            /* c8 ignore stop */
784            this.#markENOENT();
785        }
786    }
787    #readlinkFail(code = '') {
788        let ter = this.#type;
789        ter |= ENOREADLINK;
790        if (code === 'ENOENT')
791            ter |= ENOENT;
792        // windows gets a weird error when you try to readlink a file
793        if (code === 'EINVAL' || code === 'UNKNOWN') {
794            // exists, but not a symlink, we don't know WHAT it is, so remove
795            // all IFMT bits.
796            ter &= IFMT_UNKNOWN;
797        }
798        this.#type = ter;
799        // windows just gets ENOENT in this case.  We do cover the case,
800        // just disabled because it's impossible on Windows CI
801        /* c8 ignore start */
802        if (code === 'ENOTDIR' && this.parent) {
803            this.parent.#markENOTDIR();
804        }
805        /* c8 ignore stop */
806    }
807    #readdirAddChild(e, c) {
808        return (this.#readdirMaybePromoteChild(e, c) ||
809            this.#readdirAddNewChild(e, c));
810    }
811    #readdirAddNewChild(e, c) {
812        // alloc new entry at head, so it's never provisional
813        const type = entToType(e);
814        const child = this.newChild(e.name, type, { parent: this });
815        const ifmt = child.#type & IFMT;
816        if (ifmt !== IFDIR && ifmt !== IFLNK && ifmt !== UNKNOWN) {
817            child.#type |= ENOTDIR;
818        }
819        c.unshift(child);
820        c.provisional++;
821        return child;
822    }
823    #readdirMaybePromoteChild(e, c) {
824        for (let p = c.provisional; p < c.length; p++) {
825            const pchild = c[p];
826            const name = this.nocase
827                ? normalizeNocase(e.name)
828                : normalize(e.name);
829            if (name !== pchild.#matchName) {
830                continue;
831            }
832            return this.#readdirPromoteChild(e, pchild, p, c);
833        }
834    }
835    #readdirPromoteChild(e, p, index, c) {
836        const v = p.name;
837        // retain any other flags, but set ifmt from dirent
838        p.#type = (p.#type & IFMT_UNKNOWN) | entToType(e);
839        // case sensitivity fixing when we learn the true name.
840        if (v !== e.name)
841            p.name = e.name;
842        // just advance provisional index (potentially off the list),
843        // otherwise we have to splice/pop it out and re-insert at head
844        if (index !== c.provisional) {
845            if (index === c.length - 1)
846                c.pop();
847            else
848                c.splice(index, 1);
849            c.unshift(p);
850        }
851        c.provisional++;
852        return p;
853    }
854    /**
855     * Call lstat() on this Path, and update all known information that can be
856     * determined.
857     *
858     * Note that unlike `fs.lstat()`, the returned value does not contain some
859     * information, such as `mode`, `dev`, `nlink`, and `ino`.  If that
860     * information is required, you will need to call `fs.lstat` yourself.
861     *
862     * If the Path refers to a nonexistent file, or if the lstat call fails for
863     * any reason, `undefined` is returned.  Otherwise the updated Path object is
864     * returned.
865     *
866     * Results are cached, and thus may be out of date if the filesystem is
867     * mutated.
868     */
869    async lstat() {
870        if ((this.#type & ENOENT) === 0) {
871            try {
872                this.#applyStat(await this.#fs.promises.lstat(this.fullpath()));
873                return this;
874            }
875            catch (er) {
876                this.#lstatFail(er.code);
877            }
878        }
879    }
880    /**
881     * synchronous {@link PathBase.lstat}
882     */
883    lstatSync() {
884        if ((this.#type & ENOENT) === 0) {
885            try {
886                this.#applyStat(this.#fs.lstatSync(this.fullpath()));
887                return this;
888            }
889            catch (er) {
890                this.#lstatFail(er.code);
891            }
892        }
893    }
894    #applyStat(st) {
895        const { atime, atimeMs, birthtime, birthtimeMs, blksize, blocks, ctime, ctimeMs, dev, gid, ino, mode, mtime, mtimeMs, nlink, rdev, size, uid, } = st;
896        this.#atime = atime;
897        this.#atimeMs = atimeMs;
898        this.#birthtime = birthtime;
899        this.#birthtimeMs = birthtimeMs;
900        this.#blksize = blksize;
901        this.#blocks = blocks;
902        this.#ctime = ctime;
903        this.#ctimeMs = ctimeMs;
904        this.#dev = dev;
905        this.#gid = gid;
906        this.#ino = ino;
907        this.#mode = mode;
908        this.#mtime = mtime;
909        this.#mtimeMs = mtimeMs;
910        this.#nlink = nlink;
911        this.#rdev = rdev;
912        this.#size = size;
913        this.#uid = uid;
914        const ifmt = entToType(st);
915        // retain any other flags, but set the ifmt
916        this.#type = (this.#type & IFMT_UNKNOWN) | ifmt | LSTAT_CALLED;
917        if (ifmt !== UNKNOWN && ifmt !== IFDIR && ifmt !== IFLNK) {
918            this.#type |= ENOTDIR;
919        }
920    }
921    #onReaddirCB = [];
922    #readdirCBInFlight = false;
923    #callOnReaddirCB(children) {
924        this.#readdirCBInFlight = false;
925        const cbs = this.#onReaddirCB.slice();
926        this.#onReaddirCB.length = 0;
927        cbs.forEach(cb => cb(null, children));
928    }
929    /**
930     * Standard node-style callback interface to get list of directory entries.
931     *
932     * If the Path cannot or does not contain any children, then an empty array
933     * is returned.
934     *
935     * Results are cached, and thus may be out of date if the filesystem is
936     * mutated.
937     *
938     * @param cb The callback called with (er, entries).  Note that the `er`
939     * param is somewhat extraneous, as all readdir() errors are handled and
940     * simply result in an empty set of entries being returned.
941     * @param allowZalgo Boolean indicating that immediately known results should
942     * *not* be deferred with `queueMicrotask`. Defaults to `false`. Release
943     * zalgo at your peril, the dark pony lord is devious and unforgiving.
944     */
945    readdirCB(cb, allowZalgo = false) {
946        if (!this.canReaddir()) {
947            if (allowZalgo)
948                cb(null, []);
949            else
950                queueMicrotask(() => cb(null, []));
951            return;
952        }
953        const children = this.children();
954        if (this.calledReaddir()) {
955            const c = children.slice(0, children.provisional);
956            if (allowZalgo)
957                cb(null, c);
958            else
959                queueMicrotask(() => cb(null, c));
960            return;
961        }
962        // don't have to worry about zalgo at this point.
963        this.#onReaddirCB.push(cb);
964        if (this.#readdirCBInFlight) {
965            return;
966        }
967        this.#readdirCBInFlight = true;
968        // else read the directory, fill up children
969        // de-provisionalize any provisional children.
970        const fullpath = this.fullpath();
971        this.#fs.readdir(fullpath, { withFileTypes: true }, (er, entries) => {
972            if (er) {
973                this.#readdirFail(er.code);
974                children.provisional = 0;
975            }
976            else {
977                // if we didn't get an error, we always get entries.
978                //@ts-ignore
979                for (const e of entries) {
980                    this.#readdirAddChild(e, children);
981                }
982                this.#readdirSuccess(children);
983            }
984            this.#callOnReaddirCB(children.slice(0, children.provisional));
985            return;
986        });
987    }
988    #asyncReaddirInFlight;
989    /**
990     * Return an array of known child entries.
991     *
992     * If the Path cannot or does not contain any children, then an empty array
993     * is returned.
994     *
995     * Results are cached, and thus may be out of date if the filesystem is
996     * mutated.
997     */
998    async readdir() {
999        if (!this.canReaddir()) {
1000            return [];
1001        }
1002        const children = this.children();
1003        if (this.calledReaddir()) {
1004            return children.slice(0, children.provisional);
1005        }
1006        // else read the directory, fill up children
1007        // de-provisionalize any provisional children.
1008        const fullpath = this.fullpath();
1009        if (this.#asyncReaddirInFlight) {
1010            await this.#asyncReaddirInFlight;
1011        }
1012        else {
1013            /* c8 ignore start */
1014            let resolve = () => { };
1015            /* c8 ignore stop */
1016            this.#asyncReaddirInFlight = new Promise(res => (resolve = res));
1017            try {
1018                for (const e of await this.#fs.promises.readdir(fullpath, {
1019                    withFileTypes: true,
1020                })) {
1021                    this.#readdirAddChild(e, children);
1022                }
1023                this.#readdirSuccess(children);
1024            }
1025            catch (er) {
1026                this.#readdirFail(er.code);
1027                children.provisional = 0;
1028            }
1029            this.#asyncReaddirInFlight = undefined;
1030            resolve();
1031        }
1032        return children.slice(0, children.provisional);
1033    }
1034    /**
1035     * synchronous {@link PathBase.readdir}
1036     */
1037    readdirSync() {
1038        if (!this.canReaddir()) {
1039            return [];
1040        }
1041        const children = this.children();
1042        if (this.calledReaddir()) {
1043            return children.slice(0, children.provisional);
1044        }
1045        // else read the directory, fill up children
1046        // de-provisionalize any provisional children.
1047        const fullpath = this.fullpath();
1048        try {
1049            for (const e of this.#fs.readdirSync(fullpath, {
1050                withFileTypes: true,
1051            })) {
1052                this.#readdirAddChild(e, children);
1053            }
1054            this.#readdirSuccess(children);
1055        }
1056        catch (er) {
1057            this.#readdirFail(er.code);
1058            children.provisional = 0;
1059        }
1060        return children.slice(0, children.provisional);
1061    }
1062    canReaddir() {
1063        if (this.#type & ENOCHILD)
1064            return false;
1065        const ifmt = IFMT & this.#type;
1066        // we always set ENOTDIR when setting IFMT, so should be impossible
1067        /* c8 ignore start */
1068        if (!(ifmt === UNKNOWN || ifmt === IFDIR || ifmt === IFLNK)) {
1069            return false;
1070        }
1071        /* c8 ignore stop */
1072        return true;
1073    }
1074    shouldWalk(dirs, walkFilter) {
1075        return ((this.#type & IFDIR) === IFDIR &&
1076            !(this.#type & ENOCHILD) &&
1077            !dirs.has(this) &&
1078            (!walkFilter || walkFilter(this)));
1079    }
1080    /**
1081     * Return the Path object corresponding to path as resolved
1082     * by realpath(3).
1083     *
1084     * If the realpath call fails for any reason, `undefined` is returned.
1085     *
1086     * Result is cached, and thus may be outdated if the filesystem is mutated.
1087     * On success, returns a Path object.
1088     */
1089    async realpath() {
1090        if (this.#realpath)
1091            return this.#realpath;
1092        if ((ENOREALPATH | ENOREADLINK | ENOENT) & this.#type)
1093            return undefined;
1094        try {
1095            const rp = await this.#fs.promises.realpath(this.fullpath());
1096            return (this.#realpath = this.resolve(rp));
1097        }
1098        catch (_) {
1099            this.#markENOREALPATH();
1100        }
1101    }
1102    /**
1103     * Synchronous {@link realpath}
1104     */
1105    realpathSync() {
1106        if (this.#realpath)
1107            return this.#realpath;
1108        if ((ENOREALPATH | ENOREADLINK | ENOENT) & this.#type)
1109            return undefined;
1110        try {
1111            const rp = this.#fs.realpathSync(this.fullpath());
1112            return (this.#realpath = this.resolve(rp));
1113        }
1114        catch (_) {
1115            this.#markENOREALPATH();
1116        }
1117    }
1118    /**
1119     * Internal method to mark this Path object as the scurry cwd,
1120     * called by {@link PathScurry#chdir}
1121     *
1122     * @internal
1123     */
1124    [setAsCwd](oldCwd) {
1125        if (oldCwd === this)
1126            return;
1127        const changed = new Set([]);
1128        let rp = [];
1129        let p = this;
1130        while (p && p.parent) {
1131            changed.add(p);
1132            p.#relative = rp.join(this.sep);
1133            p.#relativePosix = rp.join('/');
1134            p = p.parent;
1135            rp.push('..');
1136        }
1137        // now un-memoize parents of old cwd
1138        p = oldCwd;
1139        while (p && p.parent && !changed.has(p)) {
1140            p.#relative = undefined;
1141            p.#relativePosix = undefined;
1142            p = p.parent;
1143        }
1144    }
1145}
1146/**
1147 * Path class used on win32 systems
1148 *
1149 * Uses `'\\'` as the path separator for returned paths, either `'\\'` or `'/'`
1150 * as the path separator for parsing paths.
1151 */
1152export class PathWin32 extends PathBase {
1153    /**
1154     * Separator for generating path strings.
1155     */
1156    sep = '\\';
1157    /**
1158     * Separator for parsing path strings.
1159     */
1160    splitSep = eitherSep;
1161    /**
1162     * Do not create new Path objects directly.  They should always be accessed
1163     * via the PathScurry class or other methods on the Path class.
1164     *
1165     * @internal
1166     */
1167    constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
1168        super(name, type, root, roots, nocase, children, opts);
1169    }
1170    /**
1171     * @internal
1172     */
1173    newChild(name, type = UNKNOWN, opts = {}) {
1174        return new PathWin32(name, type, this.root, this.roots, this.nocase, this.childrenCache(), opts);
1175    }
1176    /**
1177     * @internal
1178     */
1179    getRootString(path) {
1180        return win32.parse(path).root;
1181    }
1182    /**
1183     * @internal
1184     */
1185    getRoot(rootPath) {
1186        rootPath = uncToDrive(rootPath.toUpperCase());
1187        if (rootPath === this.root.name) {
1188            return this.root;
1189        }
1190        // ok, not that one, check if it matches another we know about
1191        for (const [compare, root] of Object.entries(this.roots)) {
1192            if (this.sameRoot(rootPath, compare)) {
1193                return (this.roots[rootPath] = root);
1194            }
1195        }
1196        // otherwise, have to create a new one.
1197        return (this.roots[rootPath] = new PathScurryWin32(rootPath, this).root);
1198    }
1199    /**
1200     * @internal
1201     */
1202    sameRoot(rootPath, compare = this.root.name) {
1203        // windows can (rarely) have case-sensitive filesystem, but
1204        // UNC and drive letters are always case-insensitive, and canonically
1205        // represented uppercase.
1206        rootPath = rootPath
1207            .toUpperCase()
1208            .replace(/\//g, '\\')
1209            .replace(uncDriveRegexp, '$1\\');
1210        return rootPath === compare;
1211    }
1212}
1213/**
1214 * Path class used on all posix systems.
1215 *
1216 * Uses `'/'` as the path separator.
1217 */
1218export class PathPosix extends PathBase {
1219    /**
1220     * separator for parsing path strings
1221     */
1222    splitSep = '/';
1223    /**
1224     * separator for generating path strings
1225     */
1226    sep = '/';
1227    /**
1228     * Do not create new Path objects directly.  They should always be accessed
1229     * via the PathScurry class or other methods on the Path class.
1230     *
1231     * @internal
1232     */
1233    constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
1234        super(name, type, root, roots, nocase, children, opts);
1235    }
1236    /**
1237     * @internal
1238     */
1239    getRootString(path) {
1240        return path.startsWith('/') ? '/' : '';
1241    }
1242    /**
1243     * @internal
1244     */
1245    getRoot(_rootPath) {
1246        return this.root;
1247    }
1248    /**
1249     * @internal
1250     */
1251    newChild(name, type = UNKNOWN, opts = {}) {
1252        return new PathPosix(name, type, this.root, this.roots, this.nocase, this.childrenCache(), opts);
1253    }
1254}
1255/**
1256 * The base class for all PathScurry classes, providing the interface for path
1257 * resolution and filesystem operations.
1258 *
1259 * Typically, you should *not* instantiate this class directly, but rather one
1260 * of the platform-specific classes, or the exported {@link PathScurry} which
1261 * defaults to the current platform.
1262 */
1263export class PathScurryBase {
1264    /**
1265     * The root Path entry for the current working directory of this Scurry
1266     */
1267    root;
1268    /**
1269     * The string path for the root of this Scurry's current working directory
1270     */
1271    rootPath;
1272    /**
1273     * A collection of all roots encountered, referenced by rootPath
1274     */
1275    roots;
1276    /**
1277     * The Path entry corresponding to this PathScurry's current working directory.
1278     */
1279    cwd;
1280    #resolveCache;
1281    #resolvePosixCache;
1282    #children;
1283    /**
1284     * Perform path comparisons case-insensitively.
1285     *
1286     * Defaults true on Darwin and Windows systems, false elsewhere.
1287     */
1288    nocase;
1289    #fs;
1290    /**
1291     * This class should not be instantiated directly.
1292     *
1293     * Use PathScurryWin32, PathScurryDarwin, PathScurryPosix, or PathScurry
1294     *
1295     * @internal
1296     */
1297    constructor(cwd = process.cwd(), pathImpl, sep, { nocase, childrenCacheSize = 16 * 1024, fs = defaultFS, } = {}) {
1298        this.#fs = fsFromOption(fs);
1299        if (cwd instanceof URL || cwd.startsWith('file://')) {
1300            cwd = fileURLToPath(cwd);
1301        }
1302        // resolve and split root, and then add to the store.
1303        // this is the only time we call path.resolve()
1304        const cwdPath = pathImpl.resolve(cwd);
1305        this.roots = Object.create(null);
1306        this.rootPath = this.parseRootPath(cwdPath);
1307        this.#resolveCache = new ResolveCache();
1308        this.#resolvePosixCache = new ResolveCache();
1309        this.#children = new ChildrenCache(childrenCacheSize);
1310        const split = cwdPath.substring(this.rootPath.length).split(sep);
1311        // resolve('/') leaves '', splits to [''], we don't want that.
1312        if (split.length === 1 && !split[0]) {
1313            split.pop();
1314        }
1315        /* c8 ignore start */
1316        if (nocase === undefined) {
1317            throw new TypeError('must provide nocase setting to PathScurryBase ctor');
1318        }
1319        /* c8 ignore stop */
1320        this.nocase = nocase;
1321        this.root = this.newRoot(this.#fs);
1322        this.roots[this.rootPath] = this.root;
1323        let prev = this.root;
1324        let len = split.length - 1;
1325        const joinSep = pathImpl.sep;
1326        let abs = this.rootPath;
1327        let sawFirst = false;
1328        for (const part of split) {
1329            const l = len--;
1330            prev = prev.child(part, {
1331                relative: new Array(l).fill('..').join(joinSep),
1332                relativePosix: new Array(l).fill('..').join('/'),
1333                fullpath: (abs += (sawFirst ? '' : joinSep) + part),
1334            });
1335            sawFirst = true;
1336        }
1337        this.cwd = prev;
1338    }
1339    /**
1340     * Get the depth of a provided path, string, or the cwd
1341     */
1342    depth(path = this.cwd) {
1343        if (typeof path === 'string') {
1344            path = this.cwd.resolve(path);
1345        }
1346        return path.depth();
1347    }
1348    /**
1349     * Return the cache of child entries.  Exposed so subclasses can create
1350     * child Path objects in a platform-specific way.
1351     *
1352     * @internal
1353     */
1354    childrenCache() {
1355        return this.#children;
1356    }
1357    /**
1358     * Resolve one or more path strings to a resolved string
1359     *
1360     * Same interface as require('path').resolve.
1361     *
1362     * Much faster than path.resolve() when called multiple times for the same
1363     * path, because the resolved Path objects are cached.  Much slower
1364     * otherwise.
1365     */
1366    resolve(...paths) {
1367        // first figure out the minimum number of paths we have to test
1368        // we always start at cwd, but any absolutes will bump the start
1369        let r = '';
1370        for (let i = paths.length - 1; i >= 0; i--) {
1371            const p = paths[i];
1372            if (!p || p === '.')
1373                continue;
1374            r = r ? `${p}/${r}` : p;
1375            if (this.isAbsolute(p)) {
1376                break;
1377            }
1378        }
1379        const cached = this.#resolveCache.get(r);
1380        if (cached !== undefined) {
1381            return cached;
1382        }
1383        const result = this.cwd.resolve(r).fullpath();
1384        this.#resolveCache.set(r, result);
1385        return result;
1386    }
1387    /**
1388     * Resolve one or more path strings to a resolved string, returning
1389     * the posix path.  Identical to .resolve() on posix systems, but on
1390     * windows will return a forward-slash separated UNC path.
1391     *
1392     * Same interface as require('path').resolve.
1393     *
1394     * Much faster than path.resolve() when called multiple times for the same
1395     * path, because the resolved Path objects are cached.  Much slower
1396     * otherwise.
1397     */
1398    resolvePosix(...paths) {
1399        // first figure out the minimum number of paths we have to test
1400        // we always start at cwd, but any absolutes will bump the start
1401        let r = '';
1402        for (let i = paths.length - 1; i >= 0; i--) {
1403            const p = paths[i];
1404            if (!p || p === '.')
1405                continue;
1406            r = r ? `${p}/${r}` : p;
1407            if (this.isAbsolute(p)) {
1408                break;
1409            }
1410        }
1411        const cached = this.#resolvePosixCache.get(r);
1412        if (cached !== undefined) {
1413            return cached;
1414        }
1415        const result = this.cwd.resolve(r).fullpathPosix();
1416        this.#resolvePosixCache.set(r, result);
1417        return result;
1418    }
1419    /**
1420     * find the relative path from the cwd to the supplied path string or entry
1421     */
1422    relative(entry = this.cwd) {
1423        if (typeof entry === 'string') {
1424            entry = this.cwd.resolve(entry);
1425        }
1426        return entry.relative();
1427    }
1428    /**
1429     * find the relative path from the cwd to the supplied path string or
1430     * entry, using / as the path delimiter, even on Windows.
1431     */
1432    relativePosix(entry = this.cwd) {
1433        if (typeof entry === 'string') {
1434            entry = this.cwd.resolve(entry);
1435        }
1436        return entry.relativePosix();
1437    }
1438    /**
1439     * Return the basename for the provided string or Path object
1440     */
1441    basename(entry = this.cwd) {
1442        if (typeof entry === 'string') {
1443            entry = this.cwd.resolve(entry);
1444        }
1445        return entry.name;
1446    }
1447    /**
1448     * Return the dirname for the provided string or Path object
1449     */
1450    dirname(entry = this.cwd) {
1451        if (typeof entry === 'string') {
1452            entry = this.cwd.resolve(entry);
1453        }
1454        return (entry.parent || entry).fullpath();
1455    }
1456    async readdir(entry = this.cwd, opts = {
1457        withFileTypes: true,
1458    }) {
1459        if (typeof entry === 'string') {
1460            entry = this.cwd.resolve(entry);
1461        }
1462        else if (!(entry instanceof PathBase)) {
1463            opts = entry;
1464            entry = this.cwd;
1465        }
1466        const { withFileTypes } = opts;
1467        if (!entry.canReaddir()) {
1468            return [];
1469        }
1470        else {
1471            const p = await entry.readdir();
1472            return withFileTypes ? p : p.map(e => e.name);
1473        }
1474    }
1475    readdirSync(entry = this.cwd, opts = {
1476        withFileTypes: true,
1477    }) {
1478        if (typeof entry === 'string') {
1479            entry = this.cwd.resolve(entry);
1480        }
1481        else if (!(entry instanceof PathBase)) {
1482            opts = entry;
1483            entry = this.cwd;
1484        }
1485        const { withFileTypes = true } = opts;
1486        if (!entry.canReaddir()) {
1487            return [];
1488        }
1489        else if (withFileTypes) {
1490            return entry.readdirSync();
1491        }
1492        else {
1493            return entry.readdirSync().map(e => e.name);
1494        }
1495    }
1496    /**
1497     * Call lstat() on the string or Path object, and update all known
1498     * information that can be determined.
1499     *
1500     * Note that unlike `fs.lstat()`, the returned value does not contain some
1501     * information, such as `mode`, `dev`, `nlink`, and `ino`.  If that
1502     * information is required, you will need to call `fs.lstat` yourself.
1503     *
1504     * If the Path refers to a nonexistent file, or if the lstat call fails for
1505     * any reason, `undefined` is returned.  Otherwise the updated Path object is
1506     * returned.
1507     *
1508     * Results are cached, and thus may be out of date if the filesystem is
1509     * mutated.
1510     */
1511    async lstat(entry = this.cwd) {
1512        if (typeof entry === 'string') {
1513            entry = this.cwd.resolve(entry);
1514        }
1515        return entry.lstat();
1516    }
1517    /**
1518     * synchronous {@link PathScurryBase.lstat}
1519     */
1520    lstatSync(entry = this.cwd) {
1521        if (typeof entry === 'string') {
1522            entry = this.cwd.resolve(entry);
1523        }
1524        return entry.lstatSync();
1525    }
1526    async readlink(entry = this.cwd, { withFileTypes } = {
1527        withFileTypes: false,
1528    }) {
1529        if (typeof entry === 'string') {
1530            entry = this.cwd.resolve(entry);
1531        }
1532        else if (!(entry instanceof PathBase)) {
1533            withFileTypes = entry.withFileTypes;
1534            entry = this.cwd;
1535        }
1536        const e = await entry.readlink();
1537        return withFileTypes ? e : e?.fullpath();
1538    }
1539    readlinkSync(entry = this.cwd, { withFileTypes } = {
1540        withFileTypes: false,
1541    }) {
1542        if (typeof entry === 'string') {
1543            entry = this.cwd.resolve(entry);
1544        }
1545        else if (!(entry instanceof PathBase)) {
1546            withFileTypes = entry.withFileTypes;
1547            entry = this.cwd;
1548        }
1549        const e = entry.readlinkSync();
1550        return withFileTypes ? e : e?.fullpath();
1551    }
1552    async realpath(entry = this.cwd, { withFileTypes } = {
1553        withFileTypes: false,
1554    }) {
1555        if (typeof entry === 'string') {
1556            entry = this.cwd.resolve(entry);
1557        }
1558        else if (!(entry instanceof PathBase)) {
1559            withFileTypes = entry.withFileTypes;
1560            entry = this.cwd;
1561        }
1562        const e = await entry.realpath();
1563        return withFileTypes ? e : e?.fullpath();
1564    }
1565    realpathSync(entry = this.cwd, { withFileTypes } = {
1566        withFileTypes: false,
1567    }) {
1568        if (typeof entry === 'string') {
1569            entry = this.cwd.resolve(entry);
1570        }
1571        else if (!(entry instanceof PathBase)) {
1572            withFileTypes = entry.withFileTypes;
1573            entry = this.cwd;
1574        }
1575        const e = entry.realpathSync();
1576        return withFileTypes ? e : e?.fullpath();
1577    }
1578    async walk(entry = this.cwd, opts = {}) {
1579        if (typeof entry === 'string') {
1580            entry = this.cwd.resolve(entry);
1581        }
1582        else if (!(entry instanceof PathBase)) {
1583            opts = entry;
1584            entry = this.cwd;
1585        }
1586        const { withFileTypes = true, follow = false, filter, walkFilter, } = opts;
1587        const results = [];
1588        if (!filter || filter(entry)) {
1589            results.push(withFileTypes ? entry : entry.fullpath());
1590        }
1591        const dirs = new Set();
1592        const walk = (dir, cb) => {
1593            dirs.add(dir);
1594            dir.readdirCB((er, entries) => {
1595                /* c8 ignore start */
1596                if (er) {
1597                    return cb(er);
1598                }
1599                /* c8 ignore stop */
1600                let len = entries.length;
1601                if (!len)
1602                    return cb();
1603                const next = () => {
1604                    if (--len === 0) {
1605                        cb();
1606                    }
1607                };
1608                for (const e of entries) {
1609                    if (!filter || filter(e)) {
1610                        results.push(withFileTypes ? e : e.fullpath());
1611                    }
1612                    if (follow && e.isSymbolicLink()) {
1613                        e.realpath()
1614                            .then(r => (r?.isUnknown() ? r.lstat() : r))
1615                            .then(r => r?.shouldWalk(dirs, walkFilter) ? walk(r, next) : next());
1616                    }
1617                    else {
1618                        if (e.shouldWalk(dirs, walkFilter)) {
1619                            walk(e, next);
1620                        }
1621                        else {
1622                            next();
1623                        }
1624                    }
1625                }
1626            }, true); // zalgooooooo
1627        };
1628        const start = entry;
1629        return new Promise((res, rej) => {
1630            walk(start, er => {
1631                /* c8 ignore start */
1632                if (er)
1633                    return rej(er);
1634                /* c8 ignore stop */
1635                res(results);
1636            });
1637        });
1638    }
1639    walkSync(entry = this.cwd, opts = {}) {
1640        if (typeof entry === 'string') {
1641            entry = this.cwd.resolve(entry);
1642        }
1643        else if (!(entry instanceof PathBase)) {
1644            opts = entry;
1645            entry = this.cwd;
1646        }
1647        const { withFileTypes = true, follow = false, filter, walkFilter, } = opts;
1648        const results = [];
1649        if (!filter || filter(entry)) {
1650            results.push(withFileTypes ? entry : entry.fullpath());
1651        }
1652        const dirs = new Set([entry]);
1653        for (const dir of dirs) {
1654            const entries = dir.readdirSync();
1655            for (const e of entries) {
1656                if (!filter || filter(e)) {
1657                    results.push(withFileTypes ? e : e.fullpath());
1658                }
1659                let r = e;
1660                if (e.isSymbolicLink()) {
1661                    if (!(follow && (r = e.realpathSync())))
1662                        continue;
1663                    if (r.isUnknown())
1664                        r.lstatSync();
1665                }
1666                if (r.shouldWalk(dirs, walkFilter)) {
1667                    dirs.add(r);
1668                }
1669            }
1670        }
1671        return results;
1672    }
1673    /**
1674     * Support for `for await`
1675     *
1676     * Alias for {@link PathScurryBase.iterate}
1677     *
1678     * Note: As of Node 19, this is very slow, compared to other methods of
1679     * walking.  Consider using {@link PathScurryBase.stream} if memory overhead
1680     * and backpressure are concerns, or {@link PathScurryBase.walk} if not.
1681     */
1682    [Symbol.asyncIterator]() {
1683        return this.iterate();
1684    }
1685    iterate(entry = this.cwd, options = {}) {
1686        // iterating async over the stream is significantly more performant,
1687        // especially in the warm-cache scenario, because it buffers up directory
1688        // entries in the background instead of waiting for a yield for each one.
1689        if (typeof entry === 'string') {
1690            entry = this.cwd.resolve(entry);
1691        }
1692        else if (!(entry instanceof PathBase)) {
1693            options = entry;
1694            entry = this.cwd;
1695        }
1696        return this.stream(entry, options)[Symbol.asyncIterator]();
1697    }
1698    /**
1699     * Iterating over a PathScurry performs a synchronous walk.
1700     *
1701     * Alias for {@link PathScurryBase.iterateSync}
1702     */
1703    [Symbol.iterator]() {
1704        return this.iterateSync();
1705    }
1706    *iterateSync(entry = this.cwd, opts = {}) {
1707        if (typeof entry === 'string') {
1708            entry = this.cwd.resolve(entry);
1709        }
1710        else if (!(entry instanceof PathBase)) {
1711            opts = entry;
1712            entry = this.cwd;
1713        }
1714        const { withFileTypes = true, follow = false, filter, walkFilter, } = opts;
1715        if (!filter || filter(entry)) {
1716            yield withFileTypes ? entry : entry.fullpath();
1717        }
1718        const dirs = new Set([entry]);
1719        for (const dir of dirs) {
1720            const entries = dir.readdirSync();
1721            for (const e of entries) {
1722                if (!filter || filter(e)) {
1723                    yield withFileTypes ? e : e.fullpath();
1724                }
1725                let r = e;
1726                if (e.isSymbolicLink()) {
1727                    if (!(follow && (r = e.realpathSync())))
1728                        continue;
1729                    if (r.isUnknown())
1730                        r.lstatSync();
1731                }
1732                if (r.shouldWalk(dirs, walkFilter)) {
1733                    dirs.add(r);
1734                }
1735            }
1736        }
1737    }
1738    stream(entry = this.cwd, opts = {}) {
1739        if (typeof entry === 'string') {
1740            entry = this.cwd.resolve(entry);
1741        }
1742        else if (!(entry instanceof PathBase)) {
1743            opts = entry;
1744            entry = this.cwd;
1745        }
1746        const { withFileTypes = true, follow = false, filter, walkFilter, } = opts;
1747        const results = new Minipass({ objectMode: true });
1748        if (!filter || filter(entry)) {
1749            results.write(withFileTypes ? entry : entry.fullpath());
1750        }
1751        const dirs = new Set();
1752        const queue = [entry];
1753        let processing = 0;
1754        const process = () => {
1755            let paused = false;
1756            while (!paused) {
1757                const dir = queue.shift();
1758                if (!dir) {
1759                    if (processing === 0)
1760                        results.end();
1761                    return;
1762                }
1763                processing++;
1764                dirs.add(dir);
1765                const onReaddir = (er, entries, didRealpaths = false) => {
1766                    /* c8 ignore start */
1767                    if (er)
1768                        return results.emit('error', er);
1769                    /* c8 ignore stop */
1770                    if (follow && !didRealpaths) {
1771                        const promises = [];
1772                        for (const e of entries) {
1773                            if (e.isSymbolicLink()) {
1774                                promises.push(e
1775                                    .realpath()
1776                                    .then((r) => r?.isUnknown() ? r.lstat() : r));
1777                            }
1778                        }
1779                        if (promises.length) {
1780                            Promise.all(promises).then(() => onReaddir(null, entries, true));
1781                            return;
1782                        }
1783                    }
1784                    for (const e of entries) {
1785                        if (e && (!filter || filter(e))) {
1786                            if (!results.write(withFileTypes ? e : e.fullpath())) {
1787                                paused = true;
1788                            }
1789                        }
1790                    }
1791                    processing--;
1792                    for (const e of entries) {
1793                        const r = e.realpathCached() || e;
1794                        if (r.shouldWalk(dirs, walkFilter)) {
1795                            queue.push(r);
1796                        }
1797                    }
1798                    if (paused && !results.flowing) {
1799                        results.once('drain', process);
1800                    }
1801                    else if (!sync) {
1802                        process();
1803                    }
1804                };
1805                // zalgo containment
1806                let sync = true;
1807                dir.readdirCB(onReaddir, true);
1808                sync = false;
1809            }
1810        };
1811        process();
1812        return results;
1813    }
1814    streamSync(entry = this.cwd, opts = {}) {
1815        if (typeof entry === 'string') {
1816            entry = this.cwd.resolve(entry);
1817        }
1818        else if (!(entry instanceof PathBase)) {
1819            opts = entry;
1820            entry = this.cwd;
1821        }
1822        const { withFileTypes = true, follow = false, filter, walkFilter, } = opts;
1823        const results = new Minipass({ objectMode: true });
1824        const dirs = new Set();
1825        if (!filter || filter(entry)) {
1826            results.write(withFileTypes ? entry : entry.fullpath());
1827        }
1828        const queue = [entry];
1829        let processing = 0;
1830        const process = () => {
1831            let paused = false;
1832            while (!paused) {
1833                const dir = queue.shift();
1834                if (!dir) {
1835                    if (processing === 0)
1836                        results.end();
1837                    return;
1838                }
1839                processing++;
1840                dirs.add(dir);
1841                const entries = dir.readdirSync();
1842                for (const e of entries) {
1843                    if (!filter || filter(e)) {
1844                        if (!results.write(withFileTypes ? e : e.fullpath())) {
1845                            paused = true;
1846                        }
1847                    }
1848                }
1849                processing--;
1850                for (const e of entries) {
1851                    let r = e;
1852                    if (e.isSymbolicLink()) {
1853                        if (!(follow && (r = e.realpathSync())))
1854                            continue;
1855                        if (r.isUnknown())
1856                            r.lstatSync();
1857                    }
1858                    if (r.shouldWalk(dirs, walkFilter)) {
1859                        queue.push(r);
1860                    }
1861                }
1862            }
1863            if (paused && !results.flowing)
1864                results.once('drain', process);
1865        };
1866        process();
1867        return results;
1868    }
1869    chdir(path = this.cwd) {
1870        const oldCwd = this.cwd;
1871        this.cwd = typeof path === 'string' ? this.cwd.resolve(path) : path;
1872        this.cwd[setAsCwd](oldCwd);
1873    }
1874}
1875/**
1876 * Windows implementation of {@link PathScurryBase}
1877 *
1878 * Defaults to case insensitve, uses `'\\'` to generate path strings.  Uses
1879 * {@link PathWin32} for Path objects.
1880 */
1881export class PathScurryWin32 extends PathScurryBase {
1882    /**
1883     * separator for generating path strings
1884     */
1885    sep = '\\';
1886    constructor(cwd = process.cwd(), opts = {}) {
1887        const { nocase = true } = opts;
1888        super(cwd, win32, '\\', { ...opts, nocase });
1889        this.nocase = nocase;
1890        for (let p = this.cwd; p; p = p.parent) {
1891            p.nocase = this.nocase;
1892        }
1893    }
1894    /**
1895     * @internal
1896     */
1897    parseRootPath(dir) {
1898        // if the path starts with a single separator, it's not a UNC, and we'll
1899        // just get separator as the root, and driveFromUNC will return \
1900        // In that case, mount \ on the root from the cwd.
1901        return win32.parse(dir).root.toUpperCase();
1902    }
1903    /**
1904     * @internal
1905     */
1906    newRoot(fs) {
1907        return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs });
1908    }
1909    /**
1910     * Return true if the provided path string is an absolute path
1911     */
1912    isAbsolute(p) {
1913        return (p.startsWith('/') || p.startsWith('\\') || /^[a-z]:(\/|\\)/i.test(p));
1914    }
1915}
1916/**
1917 * {@link PathScurryBase} implementation for all posix systems other than Darwin.
1918 *
1919 * Defaults to case-sensitive matching, uses `'/'` to generate path strings.
1920 *
1921 * Uses {@link PathPosix} for Path objects.
1922 */
1923export class PathScurryPosix extends PathScurryBase {
1924    /**
1925     * separator for generating path strings
1926     */
1927    sep = '/';
1928    constructor(cwd = process.cwd(), opts = {}) {
1929        const { nocase = false } = opts;
1930        super(cwd, posix, '/', { ...opts, nocase });
1931        this.nocase = nocase;
1932    }
1933    /**
1934     * @internal
1935     */
1936    parseRootPath(_dir) {
1937        return '/';
1938    }
1939    /**
1940     * @internal
1941     */
1942    newRoot(fs) {
1943        return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs });
1944    }
1945    /**
1946     * Return true if the provided path string is an absolute path
1947     */
1948    isAbsolute(p) {
1949        return p.startsWith('/');
1950    }
1951}
1952/**
1953 * {@link PathScurryBase} implementation for Darwin (macOS) systems.
1954 *
1955 * Defaults to case-insensitive matching, uses `'/'` for generating path
1956 * strings.
1957 *
1958 * Uses {@link PathPosix} for Path objects.
1959 */
1960export class PathScurryDarwin extends PathScurryPosix {
1961    constructor(cwd = process.cwd(), opts = {}) {
1962        const { nocase = true } = opts;
1963        super(cwd, { ...opts, nocase });
1964    }
1965}
1966/**
1967 * Default {@link PathBase} implementation for the current platform.
1968 *
1969 * {@link PathWin32} on Windows systems, {@link PathPosix} on all others.
1970 */
1971export const Path = process.platform === 'win32' ? PathWin32 : PathPosix;
1972/**
1973 * Default {@link PathScurryBase} implementation for the current platform.
1974 *
1975 * {@link PathScurryWin32} on Windows systems, {@link PathScurryDarwin} on
1976 * Darwin (macOS) systems, {@link PathScurryPosix} on all others.
1977 */
1978export const PathScurry = process.platform === 'win32'
1979    ? PathScurryWin32
1980    : process.platform === 'darwin'
1981        ? PathScurryDarwin
1982        : PathScurryPosix;
1983//# sourceMappingURL=index.js.map