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