codekingpro/portable-devtools
114k
1import { LRUCache } from 'lru-cache';
2import { posix, win32 } from 'node:path';
3import { fileURLToPath } from 'node:url';
4import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSync as rps, } from 'fs';
5import * as actualFS from 'node: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 'node: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 = 0b0000_0001_0000;
53// set after a successful lstat()
54const LSTAT_CALLED = 0b0000_0010_0000;
55// set if an entry (or one of its parents) is definitely not a dir
56const ENOTDIR = 0b0000_0100_0000;
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 = 0b0000_1000_0000;
60// cannot have child entries -- also verify &IFMT is either IFDIR or IFLNK
61// set if we fail to readlink
62const ENOREADLINK = 0b0001_0000_0000;
63// set if we know realpath() will fail
64const ENOREALPATH = 0b0010_0000_0000;
65const ENOCHILD = ENOTDIR | ENOENT | ENOREALPATH;
66const TYPEMASK = 0b0011_1111_1111;
67const entToType = (s) => s.isFile() ? IFREG
68 : s.isDirectory() ? IFDIR
69 : s.isSymbolicLink() ? IFLNK
70 : s.isCharacterDevice() ? IFCHR
71 : s.isBlockDevice() ? IFBLK
72 : s.isSocket() ? IFSOCK
73 : s.isFIFO() ? IFIFO
74 : UNKNOWN;
75// normalize unicode path names
76const normalizeCache = new LRUCache({ max: 2 ** 12 });
77const normalize = (s) => {
78 const c = normalizeCache.get(s);
79 if (c)
80 return c;
81 const n = s.normalize('NFKD');
82 normalizeCache.set(s, n);
83 return n;
84};
85const normalizeNocaseCache = new LRUCache({ max: 2 ** 12 });
86const normalizeNocase = (s) => {
87 const c = normalizeNocaseCache.get(s);
88 if (c)
89 return c;
90 const n = normalize(s.toLowerCase());
91 normalizeNocaseCache.set(s, n);
92 return n;
93};
94/**
95 * An LRUCache for storing resolved path strings or Path objects.
96 * @internal
97 */
98export class ResolveCache extends LRUCache {
99 constructor() {
100 super({ max: 256 });
101 }
102}
103// In order to prevent blowing out the js heap by allocating hundreds of
104// thousands of Path entries when walking extremely large trees, the "children"
105// in this tree are represented by storing an array of Path entries in an
106// LRUCache, indexed by the parent. At any time, Path.children() may return an
107// empty array, indicating that it doesn't know about any of its children, and
108// thus has to rebuild that cache. This is fine, it just means that we don't
109// benefit as much from having the cached entries, but huge directory walks
110// don't blow out the stack, and smaller ones are still as fast as possible.
111//
112//It does impose some complexity when building up the readdir data, because we
113//need to pass a reference to the children array that we started with.
114/**
115 * an LRUCache for storing child entries.
116 * @internal
117 */
118export class ChildrenCache extends LRUCache {
119 constructor(maxSize = 16 * 1024) {
120 super({
121 maxSize,
122 // parent + children
123 sizeCalculation: a => a.length + 1,
124 });
125 }
126}
127const setAsCwd = Symbol('PathScurry setAsCwd');
128/**
129 * Path objects are sort of like a super-powered
130 * {@link https://nodejs.org/docs/latest/api/fs.html#class-fsdirent fs.Dirent}
131 *
132 * Each one represents a single filesystem entry on disk, which may or may not
133 * exist. It includes methods for reading various types of information via
134 * lstat, readlink, and readdir, and caches all information to the greatest
135 * degree possible.
136 *
137 * Note that fs operations that would normally throw will instead return an
138 * "empty" value. This is in order to prevent excessive overhead from error
139 * stack traces.
140 */
141export class PathBase {
142 /**
143 * the basename of this path
144 *
145 * **Important**: *always* test the path name against any test string
146 * usingthe {@link isNamed} method, and not by directly comparing this
147 * string. Otherwise, unicode path strings that the system sees as identical
148 * will not be properly treated as the same path, leading to incorrect
149 * behavior and possible security issues.
150 */
151 name;
152 /**
153 * the Path entry corresponding to the path root.
154 *
155 * @internal
156 */
157 root;
158 /**
159 * All roots found within the current PathScurry family
160 *
161 * @internal
162 */
163 roots;
164 /**
165 * a reference to the parent path, or undefined in the case of root entries
166 *
167 * @internal
168 */
169 parent;
170 /**
171 * boolean indicating whether paths are compared case-insensitively
172 * @internal
173 */
174 nocase;
175 /**
176 * boolean indicating that this path is the current working directory
177 * of the PathScurry collection that contains it.
178 */
179 isCWD = false;
180 // potential default fs override
181 #fs;
182 // Stats fields
183 #dev;
184 get dev() {
185 return this.#dev;
186 }
187 #mode;
188 get mode() {
189 return this.#mode;
190 }
191 #nlink;
192 get nlink() {
193 return this.#nlink;
194 }
195 #uid;
196 get uid() {
197 return this.#uid;
198 }
199 #gid;
200 get gid() {
201 return this.#gid;
202 }
203 #rdev;
204 get rdev() {
205 return this.#rdev;
206 }
207 #blksize;
208 get blksize() {
209 return this.#blksize;
210 }
211 #ino;
212 get ino() {
213 return this.#ino;
214 }
215 #size;
216 get size() {
217 return this.#size;
218 }
219 #blocks;
220 get blocks() {
221 return this.#blocks;
222 }
223 #atimeMs;
224 get atimeMs() {
225 return this.#atimeMs;
226 }
227 #mtimeMs;
228 get mtimeMs() {
229 return this.#mtimeMs;
230 }
231 #ctimeMs;
232 get ctimeMs() {
233 return this.#ctimeMs;
234 }
235 #birthtimeMs;
236 get birthtimeMs() {
237 return this.#birthtimeMs;
238 }
239 #atime;
240 get atime() {
241 return this.#atime;
242 }
243 #mtime;
244 get mtime() {
245 return this.#mtime;
246 }
247 #ctime;
248 get ctime() {
249 return this.#ctime;
250 }
251 #birthtime;
252 get birthtime() {
253 return this.#birthtime;
254 }
255 #matchName;
256 #depth;
257 #fullpath;
258 #fullpathPosix;
259 #relative;
260 #relativePosix;
261 #type;
262 #children;
263 #linkTarget;
264 #realpath;
265 /**
266 * This property is for compatibility with the Dirent class as of
267 * Node v20, where Dirent['parentPath'] refers to the path of the
268 * directory that was passed to readdir. For root entries, it's the path
269 * to the entry itself.
270 */
271 get parentPath() {
272 return (this.parent || this).fullpath();
273 }
274 /* c8 ignore start */
275 /**
276 * Deprecated alias for Dirent['parentPath'] Somewhat counterintuitively,
277 * this property refers to the *parent* path, not the path object itself.
278 *
279 * @deprecated
280 */
281 get path() {
282 return this.parentPath;
283 }
284 /* c8 ignore stop */
285 /**
286 * Do not create new Path objects directly. They should always be accessed
287 * via the PathScurry class or other methods on the Path class.
288 *
289 * @internal
290 */
291 constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
292 this.name = name;
293 this.#matchName = nocase ? normalizeNocase(name) : normalize(name);
294 this.#type = type & TYPEMASK;
295 this.nocase = nocase;
296 this.roots = roots;
297 this.root = root || this;
298 this.#children = children;
299 this.#fullpath = opts.fullpath;
300 this.#relative = opts.relative;
301 this.#relativePosix = opts.relativePosix;
302 this.parent = opts.parent;
303 if (this.parent) {
304 this.#fs = this.parent.#fs;
305 }
306 else {
307 this.#fs = fsFromOption(opts.fs);
308 }
309 }
310 /**
311 * Returns the depth of the Path object from its root.
312 *
313 * For example, a path at `/foo/bar` would have a depth of 2.
314 */
315 depth() {
316 if (this.#depth !== undefined)
317 return this.#depth;
318 if (!this.parent)
319 return (this.#depth = 0);
320 return (this.#depth = this.parent.depth() + 1);
321 }
322 /**
323 * @internal
324 */
325 childrenCache() {
326 return this.#children;
327 }
328 /**
329 * Get the Path object referenced by the string path, resolved from this Path
330 */
331 resolve(path) {
332 if (!path) {
333 return this;
334 }
335 const rootPath = this.getRootString(path);
336 const dir = path.substring(rootPath.length);
337 const dirParts = dir.split(this.splitSep);
338 const result = rootPath ?
339 this.getRoot(rootPath).#resolveParts(dirParts)
340 : this.#resolveParts(dirParts);
341 return result;
342 }
343 #resolveParts(dirParts) {
344 let p = this;
345 for (const part of dirParts) {
346 p = p.child(part);
347 }
348 return p;
349 }
350 /**
351 * Returns the cached children Path objects, if still available. If they
352 * have fallen out of the cache, then returns an empty array, and resets the
353 * READDIR_CALLED bit, so that future calls to readdir() will require an fs
354 * lookup.
355 *
356 * @internal
357 */
358 children() {
359 const cached = this.#children.get(this);
360 if (cached) {
361 return cached;
362 }
363 const children = Object.assign([], { provisional: 0 });
364 this.#children.set(this, children);
365 this.#type &= ~READDIR_CALLED;
366 return children;
367 }
368 /**
369 * Resolves a path portion and returns or creates the child Path.
370 *
371 * Returns `this` if pathPart is `''` or `'.'`, or `parent` if pathPart is
372 * `'..'`.
373 *
374 * This should not be called directly. If `pathPart` contains any path
375 * separators, it will lead to unsafe undefined behavior.
376 *
377 * Use `Path.resolve()` instead.
378 *
379 * @internal
380 */
381 child(pathPart, opts) {
382 if (pathPart === '' || pathPart === '.') {
383 return this;
384 }
385 if (pathPart === '..') {
386 return this.parent || this;
387 }
388 // find the child
389 const children = this.children();
390 const name = this.nocase ? normalizeNocase(pathPart) : normalize(pathPart);
391 for (const p of children) {
392 if (p.#matchName === name) {
393 return p;
394 }
395 }
396 // didn't find it, create provisional child, since it might not
397 // actually exist. If we know the parent isn't a dir, then
398 // in fact it CAN'T exist.
399 const s = this.parent ? this.sep : '';
400 const fullpath = this.#fullpath ? this.#fullpath + s + pathPart : undefined;
401 const pchild = this.newChild(pathPart, UNKNOWN, {
402 ...opts,
403 parent: this,
404 fullpath,
405 });
406 if (!this.canReaddir()) {
407 pchild.#type |= ENOENT;
408 }
409 // don't have to update provisional, because if we have real children,
410 // then provisional is set to children.length, otherwise a lower number
411 children.push(pchild);
412 return pchild;
413 }
414 /**
415 * The relative path from the cwd. If it does not share an ancestor with
416 * the cwd, then this ends up being equivalent to the fullpath()
417 */
418 relative() {
419 if (this.isCWD)
420 return '';
421 if (this.#relative !== undefined) {
422 return this.#relative;
423 }
424 const name = this.name;
425 const p = this.parent;
426 if (!p) {
427 return (this.#relative = this.name);
428 }
429 const pv = p.relative();
430 return pv + (!pv || !p.parent ? '' : this.sep) + name;
431 }
432 /**
433 * The relative path from the cwd, using / as the path separator.
434 * If it does not share an ancestor with
435 * the cwd, then this ends up being equivalent to the fullpathPosix()
436 * On posix systems, this is identical to relative().
437 */
438 relativePosix() {
439 if (this.sep === '/')
440 return this.relative();
441 if (this.isCWD)
442 return '';
443 if (this.#relativePosix !== undefined)
444 return this.#relativePosix;
445 const name = this.name;
446 const p = this.parent;
447 if (!p) {
448 return (this.#relativePosix = this.fullpathPosix());
449 }
450 const pv = p.relativePosix();
451 return pv + (!pv || !p.parent ? '' : '/') + name;
452 }
453 /**
454 * The fully resolved path string for this Path entry
455 */
456 fullpath() {
457 if (this.#fullpath !== undefined) {
458 return this.#fullpath;
459 }
460 const name = this.name;
461 const p = this.parent;
462 if (!p) {
463 return (this.#fullpath = this.name);
464 }
465 const pv = p.fullpath();
466 const fp = pv + (!p.parent ? '' : this.sep) + name;
467 return (this.#fullpath = fp);
468 }
469 /**
470 * On platforms other than windows, this is identical to fullpath.
471 *
472 * On windows, this is overridden to return the forward-slash form of the
473 * full UNC path.
474 */
475 fullpathPosix() {
476 if (this.#fullpathPosix !== undefined)
477 return this.#fullpathPosix;
478 if (this.sep === '/')
479 return (this.#fullpathPosix = this.fullpath());
480 if (!this.parent) {
481 const p = this.fullpath().replace(/\\/g, '/');
482 if (/^[a-z]:\//i.test(p)) {
483 return (this.#fullpathPosix = `//?/${p}`);
484 }
485 else {
486 return (this.#fullpathPosix = p);
487 }
488 }
489 const p = this.parent;
490 const pfpp = p.fullpathPosix();
491 const fpp = pfpp + (!pfpp || !p.parent ? '' : '/') + this.name;
492 return (this.#fullpathPosix = fpp);
493 }
494 /**
495 * Is the Path of an unknown type?
496 *
497 * Note that we might know *something* about it if there has been a previous
498 * filesystem operation, for example that it does not exist, or is not a
499 * link, or whether it has child entries.
500 */
501 isUnknown() {
502 return (this.#type & IFMT) === UNKNOWN;
503 }
504 isType(type) {
505 return this[`is${type}`]();
506 }
507 getType() {
508 return (this.isUnknown() ? 'Unknown'
509 : this.isDirectory() ? 'Directory'
510 : this.isFile() ? 'File'
511 : this.isSymbolicLink() ? 'SymbolicLink'
512 : this.isFIFO() ? 'FIFO'
513 : this.isCharacterDevice() ? 'CharacterDevice'
514 : this.isBlockDevice() ? 'BlockDevice'
515 : /* c8 ignore start */ this.isSocket() ? '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 = (await this.parent.realpath())?.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.realpathSync()?.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 const c = children[p];
723 if (c)
724 c.#markENOENT();
725 }
726 }
727 #markENOENT() {
728 // mark as UNKNOWN and ENOENT
729 if (this.#type & ENOENT)
730 return;
731 this.#type = (this.#type | ENOENT) & IFMT_UNKNOWN;
732 this.#markChildrenENOENT();
733 }
734 #markChildrenENOENT() {
735 // all children are provisional and do not exist
736 const children = this.children();
737 children.provisional = 0;
738 for (const p of children) {
739 p.#markENOENT();
740 }
741 }
742 #markENOREALPATH() {
743 this.#type |= ENOREALPATH;
744 this.#markENOTDIR();
745 }
746 // save the information when we know the entry is not a dir
747 #markENOTDIR() {
748 // entry is not a directory, so any children can't exist.
749 // this *should* be impossible, since any children created
750 // after it's been marked ENOTDIR should be marked ENOENT,
751 // so it won't even get to this point.
752 /* c8 ignore start */
753 if (this.#type & ENOTDIR)
754 return;
755 /* c8 ignore stop */
756 let t = this.#type;
757 // this could happen if we stat a dir, then delete it,
758 // then try to read it or one of its children.
759 if ((t & IFMT) === IFDIR)
760 t &= IFMT_UNKNOWN;
761 this.#type = t | ENOTDIR;
762 this.#markChildrenENOENT();
763 }
764 #readdirFail(code = '') {
765 // markENOTDIR and markENOENT also set provisional=0
766 if (code === 'ENOTDIR' || code === 'EPERM') {
767 this.#markENOTDIR();
768 }
769 else if (code === 'ENOENT') {
770 this.#markENOENT();
771 }
772 else {
773 this.children().provisional = 0;
774 }
775 }
776 #lstatFail(code = '') {
777 // Windows just raises ENOENT in this case, disable for win CI
778 /* c8 ignore start */
779 if (code === 'ENOTDIR') {
780 // already know it has a parent by this point
781 const p = this.parent;
782 p.#markENOTDIR();
783 }
784 else if (code === 'ENOENT') {
785 /* c8 ignore stop */
786 this.#markENOENT();
787 }
788 }
789 #readlinkFail(code = '') {
790 let ter = this.#type;
791 ter |= ENOREADLINK;
792 if (code === 'ENOENT')
793 ter |= ENOENT;
794 // windows gets a weird error when you try to readlink a file
795 if (code === 'EINVAL' || code === 'UNKNOWN') {
796 // exists, but not a symlink, we don't know WHAT it is, so remove
797 // all IFMT bits.
798 ter &= IFMT_UNKNOWN;
799 }
800 this.#type = ter;
801 // windows just gets ENOENT in this case. We do cover the case,
802 // just disabled because it's impossible on Windows CI
803 /* c8 ignore start */
804 if (code === 'ENOTDIR' && this.parent) {
805 this.parent.#markENOTDIR();
806 }
807 /* c8 ignore stop */
808 }
809 #readdirAddChild(e, c) {
810 return (this.#readdirMaybePromoteChild(e, c) ||
811 this.#readdirAddNewChild(e, c));
812 }
813 #readdirAddNewChild(e, c) {
814 // alloc new entry at head, so it's never provisional
815 const type = entToType(e);
816 const child = this.newChild(e.name, type, { parent: this });
817 const ifmt = child.#type & IFMT;
818 if (ifmt !== IFDIR && ifmt !== IFLNK && ifmt !== UNKNOWN) {
819 child.#type |= ENOTDIR;
820 }
821 c.unshift(child);
822 c.provisional++;
823 return child;
824 }
825 #readdirMaybePromoteChild(e, c) {
826 for (let p = c.provisional; p < c.length; p++) {
827 const pchild = c[p];
828 const name = this.nocase ? normalizeNocase(e.name) : 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 oldCwd.isCWD = false;
1128 this.isCWD = true;
1129 const changed = new Set([]);
1130 let rp = [];
1131 let p = this;
1132 while (p && p.parent) {
1133 changed.add(p);
1134 p.#relative = rp.join(this.sep);
1135 p.#relativePosix = rp.join('/');
1136 p = p.parent;
1137 rp.push('..');
1138 }
1139 // now un-memoize parents of old cwd
1140 p = oldCwd;
1141 while (p && p.parent && !changed.has(p)) {
1142 p.#relative = undefined;
1143 p.#relativePosix = undefined;
1144 p = p.parent;
1145 }
1146 }
1147}
1148/**
1149 * Path class used on win32 systems
1150 *
1151 * Uses `'\\'` as the path separator for returned paths, either `'\\'` or `'/'`
1152 * as the path separator for parsing paths.
1153 */
1154export class PathWin32 extends PathBase {
1155 /**
1156 * Separator for generating path strings.
1157 */
1158 sep = '\\';
1159 /**
1160 * Separator for parsing path strings.
1161 */
1162 splitSep = eitherSep;
1163 /**
1164 * Do not create new Path objects directly. They should always be accessed
1165 * via the PathScurry class or other methods on the Path class.
1166 *
1167 * @internal
1168 */
1169 constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
1170 super(name, type, root, roots, nocase, children, opts);
1171 }
1172 /**
1173 * @internal
1174 */
1175 newChild(name, type = UNKNOWN, opts = {}) {
1176 return new PathWin32(name, type, this.root, this.roots, this.nocase, this.childrenCache(), opts);
1177 }
1178 /**
1179 * @internal
1180 */
1181 getRootString(path) {
1182 return win32.parse(path).root;
1183 }
1184 /**
1185 * @internal
1186 */
1187 getRoot(rootPath) {
1188 rootPath = uncToDrive(rootPath.toUpperCase());
1189 if (rootPath === this.root.name) {
1190 return this.root;
1191 }
1192 // ok, not that one, check if it matches another we know about
1193 for (const [compare, root] of Object.entries(this.roots)) {
1194 if (this.sameRoot(rootPath, compare)) {
1195 return (this.roots[rootPath] = root);
1196 }
1197 }
1198 // otherwise, have to create a new one.
1199 return (this.roots[rootPath] = new PathScurryWin32(rootPath, this).root);
1200 }
