| "use strict"; |
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { |
| if (k2 === undefined) k2 = k; |
| var desc = Object.getOwnPropertyDescriptor(m, k); |
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { |
| desc = { enumerable: true, get: function() { return m[k]; } }; |
| } |
| Object.defineProperty(o, k2, desc); |
| }) : (function(o, m, k, k2) { |
| if (k2 === undefined) k2 = k; |
| o[k2] = m[k]; |
| })); |
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { |
| Object.defineProperty(o, "default", { enumerable: true, value: v }); |
| }) : function(o, v) { |
| o["default"] = v; |
| }); |
| var __importStar = (this && this.__importStar) || function (mod) { |
| if (mod && mod.__esModule) return mod; |
| var result = {}; |
| if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); |
| __setModuleDefault(result, mod); |
| return result; |
| }; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.PathScurry = exports.Path = exports.PathScurryDarwin = exports.PathScurryPosix = exports.PathScurryWin32 = exports.PathScurryBase = exports.PathPosix = exports.PathWin32 = exports.PathBase = exports.ChildrenCache = exports.ResolveCache = void 0; |
| const lru_cache_1 = require("lru-cache"); |
| const node_path_1 = require("node:path"); |
| const node_url_1 = require("node:url"); |
| const fs_1 = require("fs"); |
| const actualFS = __importStar(require("node:fs")); |
| const realpathSync = fs_1.realpathSync.native; |
| // TODO: test perf of fs/promises realpath vs realpathCB, |
| // since the promises one uses realpath.native |
| const promises_1 = require("node:fs/promises"); |
| const minipass_1 = require("minipass"); |
| const defaultFS = { |
| lstatSync: fs_1.lstatSync, |
| readdir: fs_1.readdir, |
| readdirSync: fs_1.readdirSync, |
| readlinkSync: fs_1.readlinkSync, |
| realpathSync, |
| promises: { |
| lstat: promises_1.lstat, |
| readdir: promises_1.readdir, |
| readlink: promises_1.readlink, |
| realpath: promises_1.realpath, |
| }, |
| }; |
| // if they just gave us require('fs') then use our default |
| const fsFromOption = (fsOption) => !fsOption || fsOption === defaultFS || fsOption === actualFS ? |
| defaultFS |
| : { |
| ...defaultFS, |
| ...fsOption, |
| promises: { |
| ...defaultFS.promises, |
| ...(fsOption.promises || {}), |
| }, |
| }; |
| // turn something like //?/c:/ into c:\ |
| const uncDriveRegexp = /^\\\\\?\\([a-z]:)\\?$/i; |
| const uncToDrive = (rootPath) => rootPath.replace(/\//g, '\\').replace(uncDriveRegexp, '$1\\'); |
| // windows paths are separated by either / or \ |
| const eitherSep = /[\\\/]/; |
| const UNKNOWN = 0; // may not even exist, for all we know |
| const IFIFO = 0b0001; |
| const IFCHR = 0b0010; |
| const IFDIR = 0b0100; |
| const IFBLK = 0b0110; |
| const IFREG = 0b1000; |
| const IFLNK = 0b1010; |
| const IFSOCK = 0b1100; |
| const IFMT = 0b1111; |
| // mask to unset low 4 bits |
| const IFMT_UNKNOWN = ~IFMT; |
| // set after successfully calling readdir() and getting entries. |
| const READDIR_CALLED = 0b0000_0001_0000; |
| // set after a successful lstat() |
| const LSTAT_CALLED = 0b0000_0010_0000; |
| // set if an entry (or one of its parents) is definitely not a dir |
| const ENOTDIR = 0b0000_0100_0000; |
| // set if an entry (or one of its parents) does not exist |
| // (can also be set on lstat errors like EACCES or ENAMETOOLONG) |
| const ENOENT = 0b0000_1000_0000; |
| // cannot have child entries -- also verify &IFMT is either IFDIR or IFLNK |
| // set if we fail to readlink |
| const ENOREADLINK = 0b0001_0000_0000; |
| // set if we know realpath() will fail |
| const ENOREALPATH = 0b0010_0000_0000; |
| const ENOCHILD = ENOTDIR | ENOENT | ENOREALPATH; |
| const TYPEMASK = 0b0011_1111_1111; |
| const entToType = (s) => s.isFile() ? IFREG |
| : s.isDirectory() ? IFDIR |
| : s.isSymbolicLink() ? IFLNK |
| : s.isCharacterDevice() ? IFCHR |
| : s.isBlockDevice() ? IFBLK |
| : s.isSocket() ? IFSOCK |
| : s.isFIFO() ? IFIFO |
| : UNKNOWN; |
| // normalize unicode path names |
| const normalizeCache = new Map(); |
| const normalize = (s) => { |
| const c = normalizeCache.get(s); |
| if (c) |
| return c; |
| const n = s.normalize('NFKD'); |
| normalizeCache.set(s, n); |
| return n; |
| }; |
| const normalizeNocaseCache = new Map(); |
| const normalizeNocase = (s) => { |
| const c = normalizeNocaseCache.get(s); |
| if (c) |
| return c; |
| const n = normalize(s.toLowerCase()); |
| normalizeNocaseCache.set(s, n); |
| return n; |
| }; |
| /** |
| * An LRUCache for storing resolved path strings or Path objects. |
| * @internal |
| */ |
| class ResolveCache extends lru_cache_1.LRUCache { |
| constructor() { |
| super({ max: 256 }); |
| } |
| } |
| exports.ResolveCache = ResolveCache; |
| // In order to prevent blowing out the js heap by allocating hundreds of |
| // thousands of Path entries when walking extremely large trees, the "children" |
| // in this tree are represented by storing an array of Path entries in an |
| // LRUCache, indexed by the parent. At any time, Path.children() may return an |
| // empty array, indicating that it doesn't know about any of its children, and |
| // thus has to rebuild that cache. This is fine, it just means that we don't |
| // benefit as much from having the cached entries, but huge directory walks |
| // don't blow out the stack, and smaller ones are still as fast as possible. |
| // |
| //It does impose some complexity when building up the readdir data, because we |
| //need to pass a reference to the children array that we started with. |
| /** |
| * an LRUCache for storing child entries. |
| * @internal |
| */ |
| class ChildrenCache extends lru_cache_1.LRUCache { |
| constructor(maxSize = 16 * 1024) { |
| super({ |
| maxSize, |
| // parent + children |
| sizeCalculation: a => a.length + 1, |
| }); |
| } |
| } |
| exports.ChildrenCache = ChildrenCache; |
| const setAsCwd = Symbol('PathScurry setAsCwd'); |
| /** |
| * Path objects are sort of like a super-powered |
| * {@link https://nodejs.org/docs/latest/api/fs.html#class-fsdirent fs.Dirent} |
| * |
| * Each one represents a single filesystem entry on disk, which may or may not |
| * exist. It includes methods for reading various types of information via |
| * lstat, readlink, and readdir, and caches all information to the greatest |
| * degree possible. |
| * |
| * Note that fs operations that would normally throw will instead return an |
| * "empty" value. This is in order to prevent excessive overhead from error |
| * stack traces. |
| */ |
| class PathBase { |
| /** |
| * the basename of this path |
| * |
| * **Important**: *always* test the path name against any test string |
| * usingthe {@link isNamed} method, and not by directly comparing this |
| * string. Otherwise, unicode path strings that the system sees as identical |
| * will not be properly treated as the same path, leading to incorrect |
| * behavior and possible security issues. |
| */ |
| name; |
| /** |
| * the Path entry corresponding to the path root. |
| * |
| * @internal |
| */ |
| root; |
| /** |
| * All roots found within the current PathScurry family |
| * |
| * @internal |
| */ |
| roots; |
| /** |
| * a reference to the parent path, or undefined in the case of root entries |
| * |
| * @internal |
| */ |
| parent; |
| /** |
| * boolean indicating whether paths are compared case-insensitively |
| * @internal |
| */ |
| nocase; |
| /** |
| * boolean indicating that this path is the current working directory |
| * of the PathScurry collection that contains it. |
| */ |
| isCWD = false; |
| // potential default fs override |
| #fs; |
| // Stats fields |
| #dev; |
| get dev() { |
| return this.#dev; |
| } |
| #mode; |
| get mode() { |
| return this.#mode; |
| } |
| #nlink; |
| get nlink() { |
| return this.#nlink; |
| } |
| #uid; |
| get uid() { |
| return this.#uid; |
| } |
| #gid; |
| get gid() { |
| return this.#gid; |
| } |
| #rdev; |
| get rdev() { |
| return this.#rdev; |
| } |
| #blksize; |
| get blksize() { |
| return this.#blksize; |
| } |
| #ino; |
| get ino() { |
| return this.#ino; |
| } |
| #size; |
| get size() { |
| return this.#size; |
| } |
| #blocks; |
| get blocks() { |
| return this.#blocks; |
| } |
| #atimeMs; |
| get atimeMs() { |
| return this.#atimeMs; |
| } |
| #mtimeMs; |
| get mtimeMs() { |
| return this.#mtimeMs; |
| } |
| #ctimeMs; |
| get ctimeMs() { |
| return this.#ctimeMs; |
| } |
| #birthtimeMs; |
| get birthtimeMs() { |
| return this.#birthtimeMs; |
| } |
| #atime; |
| get atime() { |
| return this.#atime; |
| } |
| #mtime; |
| get mtime() { |
| return this.#mtime; |
| } |
| #ctime; |
| get ctime() { |
| return this.#ctime; |
| } |
| #birthtime; |
| get birthtime() { |
| return this.#birthtime; |
| } |
| #matchName; |
| #depth; |
| #fullpath; |
| #fullpathPosix; |
| #relative; |
| #relativePosix; |
| #type; |
| #children; |
| #linkTarget; |
| #realpath; |
| /** |
| * This property is for compatibility with the Dirent class as of |
| * Node v20, where Dirent['parentPath'] refers to the path of the |
| * directory that was passed to readdir. For root entries, it's the path |
| * to the entry itself. |
| */ |
| get parentPath() { |
| return (this.parent || this).fullpath(); |
| } |
| /** |
| * Deprecated alias for Dirent['parentPath'] Somewhat counterintuitively, |
| * this property refers to the *parent* path, not the path object itself. |
| */ |
| get path() { |
| return this.parentPath; |
| } |
| /** |
| * Do not create new Path objects directly. They should always be accessed |
| * via the PathScurry class or other methods on the Path class. |
| * |
| * @internal |
| */ |
| constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) { |
| this.name = name; |
| this.#matchName = nocase ? normalizeNocase(name) : normalize(name); |
| this.#type = type & TYPEMASK; |
| this.nocase = nocase; |
| this.roots = roots; |
| this.root = root || this; |
| this.#children = children; |
| this.#fullpath = opts.fullpath; |
| this.#relative = opts.relative; |
| this.#relativePosix = opts.relativePosix; |
| this.parent = opts.parent; |
| if (this.parent) { |
| this.#fs = this.parent.#fs; |
| } |
| else { |
| this.#fs = fsFromOption(opts.fs); |
| } |
| } |
| /** |
| * Returns the depth of the Path object from its root. |
| * |
| * For example, a path at `/foo/bar` would have a depth of 2. |
| */ |
| depth() { |
| if (this.#depth !== undefined) |
| return this.#depth; |
| if (!this.parent) |
| return (this.#depth = 0); |
| return (this.#depth = this.parent.depth() + 1); |
| } |
| /** |
| * @internal |
| */ |
| childrenCache() { |
| return this.#children; |
| } |
| /** |
| * Get the Path object referenced by the string path, resolved from this Path |
| */ |
| resolve(path) { |
| if (!path) { |
| return this; |
| } |
| const rootPath = this.getRootString(path); |
| const dir = path.substring(rootPath.length); |
| const dirParts = dir.split(this.splitSep); |
| const result = rootPath ? |
| this.getRoot(rootPath).#resolveParts(dirParts) |
| : this.#resolveParts(dirParts); |
| return result; |
| } |
| #resolveParts(dirParts) { |
| let p = this; |
| for (const part of dirParts) { |
| p = p.child(part); |
| } |
| return p; |
| } |
| /** |
| * Returns the cached children Path objects, if still available. If they |
| * have fallen out of the cache, then returns an empty array, and resets the |
| * READDIR_CALLED bit, so that future calls to readdir() will require an fs |
| * lookup. |
| * |
| * @internal |
| */ |
| children() { |
| const cached = this.#children.get(this); |
| if (cached) { |
| return cached; |
| } |
| const children = Object.assign([], { provisional: 0 }); |
| this.#children.set(this, children); |
| this.#type &= ~READDIR_CALLED; |
| return children; |
| } |
| /** |
| * Resolves a path portion and returns or creates the child Path. |
| * |
| * Returns `this` if pathPart is `''` or `'.'`, or `parent` if pathPart is |
| * `'..'`. |
| * |
| * This should not be called directly. If `pathPart` contains any path |
| * separators, it will lead to unsafe undefined behavior. |
| * |
| * Use `Path.resolve()` instead. |
| * |
| * @internal |
| */ |
| child(pathPart, opts) { |
| if (pathPart === '' || pathPart === '.') { |
| return this; |
| } |
| if (pathPart === '..') { |
| return this.parent || this; |
| } |
| // find the child |
| const children = this.children(); |
| const name = this.nocase ? normalizeNocase(pathPart) : normalize(pathPart); |
| for (const p of children) { |
| if (p.#matchName === name) { |
| return p; |
| } |
| } |
| // didn't find it, create provisional child, since it might not |
| // actually exist. If we know the parent isn't a dir, then |
| // in fact it CAN'T exist. |
| const s = this.parent ? this.sep : ''; |
| const fullpath = this.#fullpath ? this.#fullpath + s + pathPart : undefined; |
| const pchild = this.newChild(pathPart, UNKNOWN, { |
| ...opts, |
| parent: this, |
| fullpath, |
| }); |
| if (!this.canReaddir()) { |
| pchild.#type |= ENOENT; |
| } |
| // don't have to update provisional, because if we have real children, |
| // then provisional is set to children.length, otherwise a lower number |
| children.push(pchild); |
| return pchild; |
| } |
| /** |
| * The relative path from the cwd. If it does not share an ancestor with |
| * the cwd, then this ends up being equivalent to the fullpath() |
| */ |
| relative() { |
| if (this.isCWD) |
| return ''; |
| if (this.#relative !== undefined) { |
| return this.#relative; |
| } |
| const name = this.name; |
| const p = this.parent; |
| if (!p) { |
| return (this.#relative = this.name); |
| } |
| const pv = p.relative(); |
| return pv + (!pv || !p.parent ? '' : this.sep) + name; |
| } |
| /** |
| * The relative path from the cwd, using / as the path separator. |
| * If it does not share an ancestor with |
| * the cwd, then this ends up being equivalent to the fullpathPosix() |
| * On posix systems, this is identical to relative(). |
| */ |
| relativePosix() { |
| if (this.sep === '/') |
| return this.relative(); |
| if (this.isCWD) |
| return ''; |
| if (this.#relativePosix !== undefined) |
| return this.#relativePosix; |
| const name = this.name; |
| const p = this.parent; |
| if (!p) { |
| return (this.#relativePosix = this.fullpathPosix()); |
| } |
| const pv = p.relativePosix(); |
| return pv + (!pv || !p.parent ? '' : '/') + name; |
| } |
| /** |
| * The fully resolved path string for this Path entry |
| */ |
| fullpath() { |
| if (this.#fullpath !== undefined) { |
| return this.#fullpath; |
| } |
| const name = this.name; |
| const p = this.parent; |
| if (!p) { |
| return (this.#fullpath = this.name); |
| } |
| const pv = p.fullpath(); |
| const fp = pv + (!p.parent ? '' : this.sep) + name; |
| return (this.#fullpath = fp); |
| } |
| /** |
| * On platforms other than windows, this is identical to fullpath. |
| * |
| * On windows, this is overridden to return the forward-slash form of the |
| * full UNC path. |
| */ |
| fullpathPosix() { |
| if (this.#fullpathPosix !== undefined) |
| return this.#fullpathPosix; |
| if (this.sep === '/') |
| return (this.#fullpathPosix = this.fullpath()); |
| if (!this.parent) { |
| const p = this.fullpath().replace(/\\/g, '/'); |
| if (/^[a-z]:\//i.test(p)) { |
| return (this.#fullpathPosix = `//?/${p}`); |
| } |
| else { |
| return (this.#fullpathPosix = p); |
| } |
| } |
| const p = this.parent; |
| const pfpp = p.fullpathPosix(); |
| const fpp = pfpp + (!pfpp || !p.parent ? '' : '/') + this.name; |
| return (this.#fullpathPosix = fpp); |
| } |
| /** |
| * Is the Path of an unknown type? |
| * |
| * Note that we might know *something* about it if there has been a previous |
| * filesystem operation, for example that it does not exist, or is not a |
| * link, or whether it has child entries. |
| */ |
| isUnknown() { |
| return (this.#type & IFMT) === UNKNOWN; |
| } |
| isType(type) { |
| return this[`is${type}`](); |
| } |
| getType() { |
| return (this.isUnknown() ? 'Unknown' |
| : this.isDirectory() ? 'Directory' |
| : this.isFile() ? 'File' |
| : this.isSymbolicLink() ? 'SymbolicLink' |
| : this.isFIFO() ? 'FIFO' |
| : this.isCharacterDevice() ? 'CharacterDevice' |
| : this.isBlockDevice() ? 'BlockDevice' |
| : /* c8 ignore start */ this.isSocket() ? 'Socket' |
| : 'Unknown'); |
| /* c8 ignore stop */ |
| } |
| /** |
| * Is the Path a regular file? |
| */ |
| isFile() { |
| return (this.#type & IFMT) === IFREG; |
| } |
| /** |
| * Is the Path a directory? |
| */ |
| isDirectory() { |
| return (this.#type & IFMT) === IFDIR; |
| } |
| /** |
| * Is the path a character device? |
| */ |
| isCharacterDevice() { |
| return (this.#type & IFMT) === IFCHR; |
| } |
| /** |
| * Is the path a block device? |
| */ |
| isBlockDevice() { |
| return (this.#type & IFMT) === IFBLK; |
| } |
| /** |
| * Is the path a FIFO pipe? |
| */ |
| isFIFO() { |
| return (this.#type & IFMT) === IFIFO; |
| } |
| /** |
| * Is the path a socket? |
| */ |
| isSocket() { |
| return (this.#type & IFMT) === IFSOCK; |
| } |
| /** |
| * Is the path a symbolic link? |
| */ |
| isSymbolicLink() { |
| return (this.#type & IFLNK) === IFLNK; |
| } |
| /** |
| * Return the entry if it has been subject of a successful lstat, or |
| * undefined otherwise. |
| * |
| * Does not read the filesystem, so an undefined result *could* simply |
| * mean that we haven't called lstat on it. |
| */ |
| lstatCached() { |
| return this.#type & LSTAT_CALLED ? this : undefined; |
| } |
| /** |
| * Return the cached link target if the entry has been the subject of a |
| * successful readlink, or undefined otherwise. |
| * |
| * Does not read the filesystem, so an undefined result *could* just mean we |
| * don't have any cached data. Only use it if you are very sure that a |
| * readlink() has been called at some point. |
| */ |
| readlinkCached() { |
| return this.#linkTarget; |
| } |
| /** |
| * Returns the cached realpath target if the entry has been the subject |
| * of a successful realpath, or undefined otherwise. |
| * |
| * Does not read the filesystem, so an undefined result *could* just mean we |
| * don't have any cached data. Only use it if you are very sure that a |
| * realpath() has been called at some point. |
| */ |
| realpathCached() { |
| return this.#realpath; |
| } |
| /** |
| * Returns the cached child Path entries array if the entry has been the |
| * subject of a successful readdir(), or [] otherwise. |
| * |
| * Does not read the filesystem, so an empty array *could* just mean we |
| * don't have any cached data. Only use it if you are very sure that a |
| * readdir() has been called recently enough to still be valid. |
| */ |
| readdirCached() { |
| const children = this.children(); |
| return children.slice(0, children.provisional); |
| } |
| /** |
| * Return true if it's worth trying to readlink. Ie, we don't (yet) have |
| * any indication that readlink will definitely fail. |
| * |
| * Returns false if the path is known to not be a symlink, if a previous |
| * readlink failed, or if the entry does not exist. |
| */ |
| canReadlink() { |
| if (this.#linkTarget) |
| return true; |
| if (!this.parent) |
| return false; |
| // cases where it cannot possibly succeed |
| const ifmt = this.#type & IFMT; |
| return !((ifmt !== UNKNOWN && ifmt !== IFLNK) || |
| this.#type & ENOREADLINK || |
| this.#type & ENOENT); |
| } |
| /** |
| * Return true if readdir has previously been successfully called on this |
| * path, indicating that cachedReaddir() is likely valid. |
| */ |
| calledReaddir() { |
| return !!(this.#type & READDIR_CALLED); |
| } |
| /** |
| * Returns true if the path is known to not exist. That is, a previous lstat |
| * or readdir failed to verify its existence when that would have been |
| * expected, or a parent entry was marked either enoent or enotdir. |
| */ |
| isENOENT() { |
| return !!(this.#type & ENOENT); |
| } |
| /** |
| * Return true if the path is a match for the given path name. This handles |
| * case sensitivity and unicode normalization. |
| * |
| * Note: even on case-sensitive systems, it is **not** safe to test the |
| * equality of the `.name` property to determine whether a given pathname |
| * matches, due to unicode normalization mismatches. |
| * |
| * Always use this method instead of testing the `path.name` property |
| * directly. |
| */ |
| isNamed(n) { |
| return !this.nocase ? |
| this.#matchName === normalize(n) |
| : this.#matchName === normalizeNocase(n); |
| } |
| /** |
| * Return the Path object corresponding to the target of a symbolic link. |
| * |
| * If the Path is not a symbolic link, or if the readlink call fails for any |
| * reason, `undefined` is returned. |
| * |
| * Result is cached, and thus may be outdated if the filesystem is mutated. |
| */ |
| async readlink() { |
| const target = this.#linkTarget; |
| if (target) { |
| return target; |
| } |
| if (!this.canReadlink()) { |
| return undefined; |
| } |
| /* c8 ignore start */ |
| // already covered by the canReadlink test, here for ts grumples |
| if (!this.parent) { |
| return undefined; |
| } |
| /* c8 ignore stop */ |
| try { |
| const read = await this.#fs.promises.readlink(this.fullpath()); |
| const linkTarget = (await this.parent.realpath())?.resolve(read); |
| if (linkTarget) { |
| return (this.#linkTarget = linkTarget); |
| } |
| } |
| catch (er) { |
| this.#readlinkFail(er.code); |
| return undefined; |
| } |
| } |
| /** |
| * Synchronous {@link PathBase.readlink} |
| */ |
| readlinkSync() { |
| const target = this.#linkTarget; |
| if (target) { |
| return target; |
| } |
| if (!this.canReadlink()) { |
| return undefined; |
| } |
| /* c8 ignore start */ |
| // already covered by the canReadlink test, here for ts grumples |
| if (!this.parent) { |
| return undefined; |
| } |
| /* c8 ignore stop */ |
| try { |
| const read = this.#fs.readlinkSync(this.fullpath()); |
| const linkTarget = this.parent.realpathSync()?.resolve(read); |
| if (linkTarget) { |
| return (this.#linkTarget = linkTarget); |
| } |
| } |
| catch (er) { |
| this.#readlinkFail(er.code); |
| return undefined; |
| } |
| } |
| #readdirSuccess(children) { |
| // succeeded, mark readdir called bit |
| this.#type |= READDIR_CALLED; |
| // mark all remaining provisional children as ENOENT |
| for (let p = children.provisional; p < children.length; p++) { |
| const c = children[p]; |
| if (c) |
| c.#markENOENT(); |
| } |
| } |
| #markENOENT() { |
| // mark as UNKNOWN and ENOENT |
| if (this.#type & ENOENT) |
| return; |
| this.#type = (this.#type | ENOENT) & IFMT_UNKNOWN; |
| this.#markChildrenENOENT(); |
| } |
| #markChildrenENOENT() { |
| // all children are provisional and do not exist |
| const children = this.children(); |
| children.provisional = 0; |
| for (const p of children) { |
| p.#markENOENT(); |
| } |
| } |
| #markENOREALPATH() { |
| this.#type |= ENOREALPATH; |
| this.#markENOTDIR(); |
| } |
| // save the information when we know the entry is not a dir |
| #markENOTDIR() { |
| // entry is not a directory, so any children can't exist. |
| // this *should* be impossible, since any children created |
| // after it's been marked ENOTDIR should be marked ENOENT, |
| // so it won't even get to this point. |
| /* c8 ignore start */ |
| if (this.#type & ENOTDIR) |
| return; |
| /* c8 ignore stop */ |
| let t = this.#type; |
| // this could happen if we stat a dir, then delete it, |
| // then try to read it or one of its children. |
| if ((t & IFMT) === IFDIR) |
| t &= IFMT_UNKNOWN; |
| this.#type = t | ENOTDIR; |
| this.#markChildrenENOENT(); |
| } |
| #readdirFail(code = '') { |
| // markENOTDIR and markENOENT also set provisional=0 |
| if (code === 'ENOTDIR' || code === 'EPERM') { |
| this.#markENOTDIR(); |
| } |
| else if (code === 'ENOENT') { |
| this.#markENOENT(); |
| } |
| else { |
| this.children().provisional = 0; |
| } |
| } |
| #lstatFail(code = '') { |
| // Windows just raises ENOENT in this case, disable for win CI |
| /* c8 ignore start */ |
| if (code === 'ENOTDIR') { |
| // already know it has a parent by this point |
| const p = this.parent; |
| p.#markENOTDIR(); |
| } |
| else if (code === 'ENOENT') { |
| /* c8 ignore stop */ |
| this.#markENOENT(); |
| } |
| } |
| #readlinkFail(code = '') { |
| let ter = this.#type; |
| ter |= ENOREADLINK; |
| if (code === 'ENOENT') |
| ter |= ENOENT; |
| // windows gets a weird error when you try to readlink a file |
| if (code === 'EINVAL' || code === 'UNKNOWN') { |
| // exists, but not a symlink, we don't know WHAT it is, so remove |
| // all IFMT bits. |
| ter &= IFMT_UNKNOWN; |
| } |
| this.#type = ter; |
| // windows just gets ENOENT in this case. We do cover the case, |
| // just disabled because it's impossible on Windows CI |
| /* c8 ignore start */ |
| if (code === 'ENOTDIR' && this.parent) { |
| this.parent.#markENOTDIR(); |
| } |
| /* c8 ignore stop */ |
| } |
| #readdirAddChild(e, c) { |
| return (this.#readdirMaybePromoteChild(e, c) || |
| this.#readdirAddNewChild(e, c)); |
| } |
| #readdirAddNewChild(e, c) { |
| // alloc new entry at head, so it's never provisional |
| const type = entToType(e); |
| const child = this.newChild(e.name, type, { parent: this }); |
| const ifmt = child.#type & IFMT; |
| if (ifmt !== IFDIR && ifmt !== IFLNK && ifmt !== UNKNOWN) { |
| child.#type |= ENOTDIR; |
| } |
| c.unshift(child); |
| c.provisional++; |
| return child; |
| } |
| #readdirMaybePromoteChild(e, c) { |
| for (let p = c.provisional; p < c.length; p++) { |
| const pchild = c[p]; |
| const name = this.nocase ? normalizeNocase(e.name) : normalize(e.name); |
| if (name !== pchild.#matchName) { |
| continue; |
| } |
| return this.#readdirPromoteChild(e, pchild, p, c); |
| } |
| } |
| #readdirPromoteChild(e, p, index, c) { |
| const v = p.name; |
| // retain any other flags, but set ifmt from dirent |
| p.#type = (p.#type & IFMT_UNKNOWN) | entToType(e); |
| // case sensitivity fixing when we learn the true name. |
| if (v !== e.name) |
| p.name = e.name; |
| // just advance provisional index (potentially off the list), |
| // otherwise we have to splice/pop it out and re-insert at head |
| if (index !== c.provisional) { |
| if (index === c.length - 1) |
| c.pop(); |
| else |
| c.splice(index, 1); |
| c.unshift(p); |
| } |
| c.provisional++; |
| return p; |
| } |
| /** |
| * Call lstat() on this Path, and update all known information that can be |
| * determined. |
| * |
| * Note that unlike `fs.lstat()`, the returned value does not contain some |
| * information, such as `mode`, `dev`, `nlink`, and `ino`. If that |
| * information is required, you will need to call `fs.lstat` yourself. |
| * |
| * If the Path refers to a nonexistent file, or if the lstat call fails for |
| * any reason, `undefined` is returned. Otherwise the updated Path object is |
| * returned. |
| * |
| * Results are cached, and thus may be out of date if the filesystem is |
| * mutated. |
| */ |
| async lstat() { |
| if ((this.#type & ENOENT) === 0) { |
| try { |
| this.#applyStat(await this.#fs.promises.lstat(this.fullpath())); |
| return this; |
| } |
| catch (er) { |
| this.#lstatFail(er.code); |
| } |
| } |
| } |
| /** |
| * synchronous {@link PathBase.lstat} |
| */ |
| lstatSync() { |
| if ((this.#type & ENOENT) === 0) { |
| try { |
| this.#applyStat(this.#fs.lstatSync(this.fullpath())); |
| return this; |
| } |
| catch (er) { |
| this.#lstatFail(er.code); |
| } |
| } |
| } |
| #applyStat(st) { |
| const { atime, atimeMs, birthtime, birthtimeMs, blksize, blocks, ctime, ctimeMs, dev, gid, ino, mode, mtime, mtimeMs, nlink, rdev, size, uid, } = st; |
| this.#atime = atime; |
| this.#atimeMs = atimeMs; |
| this.#birthtime = birthtime; |
| this.#birthtimeMs = birthtimeMs; |
| this.#blksize = blksize; |
| this.#blocks = blocks; |
| this.#ctime = ctime; |
| this.#ctimeMs = ctimeMs; |
| this.#dev = dev; |
| this.#gid = gid; |
| this.#ino = ino; |
| this.#mode = mode; |
| this.#mtime = mtime; |
| this.#mtimeMs = mtimeMs; |
| this.#nlink = nlink; |
| this.#rdev = rdev; |
| this.#size = size; |
| this.#uid = uid; |
| const ifmt = entToType(st); |
| // retain any other flags, but set the ifmt |
| this.#type = (this.#type & IFMT_UNKNOWN) | ifmt | LSTAT_CALLED; |
| if (ifmt !== UNKNOWN && ifmt !== IFDIR && ifmt !== IFLNK) { |
| this.#type |= ENOTDIR; |
| } |
| } |
| #onReaddirCB = []; |
| #readdirCBInFlight = false; |
| #callOnReaddirCB(children) { |
| this.#readdirCBInFlight = false; |
| const cbs = this.#onReaddirCB.slice(); |
| this.#onReaddirCB.length = 0; |
| cbs.forEach(cb => cb(null, children)); |
| } |
| /** |
| * Standard node-style callback interface to get list of directory entries. |
| * |
| * If the Path cannot or does not contain any children, then an empty array |
| * is returned. |
| * |
| * Results are cached, and thus may be out of date if the filesystem is |
| * mutated. |
| * |
| * @param cb The callback called with (er, entries). Note that the `er` |
| * param is somewhat extraneous, as all readdir() errors are handled and |
| * simply result in an empty set of entries being returned. |
| * @param allowZalgo Boolean indicating that immediately known results should |
| * *not* be deferred with `queueMicrotask`. Defaults to `false`. Release |
| * zalgo at your peril, the dark pony lord is devious and unforgiving. |
| */ |
| readdirCB(cb, allowZalgo = false) { |
| if (!this.canReaddir()) { |
| if (allowZalgo) |
| cb(null, []); |
| else |
| queueMicrotask(() => cb(null, [])); |
| return; |
| } |
| const children = this.children(); |
| if (this.calledReaddir()) { |
| const c = children.slice(0, children.provisional); |
| if (allowZalgo) |
| cb(null, c); |
| else |
| queueMicrotask(() => cb(null, c)); |
| return; |
| } |
| // don't have to worry about zalgo at this point. |
| this.#onReaddirCB.push(cb); |
| if (this.#readdirCBInFlight) { |
| return; |
| } |
| this.#readdirCBInFlight = true; |
| // else read the directory, fill up children |
| // de-provisionalize any provisional children. |
| const fullpath = this.fullpath(); |
| this.#fs.readdir(fullpath, { withFileTypes: true }, (er, entries) => { |
| if (er) { |
| this.#readdirFail(er.code); |
| children.provisional = 0; |
| } |
| else { |
| // if we didn't get an error, we always get entries. |
| //@ts-ignore |
| for (const e of entries) { |
| this.#readdirAddChild(e, children); |
| } |
| this.#readdirSuccess(children); |
| } |
| this.#callOnReaddirCB(children.slice(0, children.provisional)); |
| return; |
| }); |
| } |
| #asyncReaddirInFlight; |
| /** |
| * Return an array of known child entries. |
| * |
| * If the Path cannot or does not contain any children, then an empty array |
| * is returned. |
| * |
| * Results are cached, and thus may be out of date if the filesystem is |
| * mutated. |
| */ |
| async readdir() { |
| if (!this.canReaddir()) { |
| return []; |
| } |
| const children = this.children(); |
| if (this.calledReaddir()) { |
| return children.slice(0, children.provisional); |
| } |
| // else read the directory, fill up children |
| // de-provisionalize any provisional children. |
| const fullpath = this.fullpath(); |
| if (this.#asyncReaddirInFlight) { |
| await this.#asyncReaddirInFlight; |
| } |
| else { |
| /* c8 ignore start */ |
| let resolve = () => { }; |
| /* c8 ignore stop */ |
| this.#asyncReaddirInFlight = new Promise(res => (resolve = res)); |
| try { |
| for (const e of await this.#fs.promises.readdir(fullpath, { |
| withFileTypes: true, |
| })) { |
| this.#readdirAddChild(e, children); |
| } |
| this.#readdirSuccess(children); |
| } |
| catch (er) { |
| this.#readdirFail(er.code); |
| children.provisional = 0; |
| } |
| this.#asyncReaddirInFlight = undefined; |
| resolve(); |
| } |
| return children.slice(0, children.provisional); |
| } |
| /** |
| * synchronous {@link PathBase.readdir} |
| */ |
| readdirSync() { |
| if (!this.canReaddir()) { |
| return []; |
| } |
| const children = this.children(); |
| if (this.calledReaddir()) { |
| return children.slice(0, children.provisional); |
| } |
| // else read the directory, fill up children |
| // de-provisionalize any provisional children. |
| const fullpath = this.fullpath(); |
| try { |
| for (const e of this.#fs.readdirSync(fullpath, { |
| withFileTypes: true, |
| })) { |
| this.#readdirAddChild(e, children); |
| } |
| this.#readdirSuccess(children); |
| } |
| catch (er) { |
| this.#readdirFail(er.code); |
| children.provisional = 0; |
| } |
| return children.slice(0, children.provisional); |
| } |
| canReaddir() { |
| if (this.#type & ENOCHILD) |
| return false; |
| const ifmt = IFMT & this.#type; |
| // we always set ENOTDIR when setting IFMT, so should be impossible |
| /* c8 ignore start */ |
| if (!(ifmt === UNKNOWN || ifmt === IFDIR || ifmt === IFLNK)) { |
| return false; |
| } |
| /* c8 ignore stop */ |
| return true; |
| } |
| shouldWalk(dirs, walkFilter) { |
| return ((this.#type & IFDIR) === IFDIR && |
| !(this.#type & ENOCHILD) && |
| !dirs.has(this) && |
| (!walkFilter || walkFilter(this))); |
| } |
| /** |
| * Return the Path object corresponding to path as resolved |
| * by realpath(3). |
| * |
| * If the realpath call fails for any reason, `undefined` is returned. |
| * |
| * Result is cached, and thus may be outdated if the filesystem is mutated. |
| * On success, returns a Path object. |
| */ |
| async realpath() { |
| if (this.#realpath) |
| return this.#realpath; |
| if ((ENOREALPATH | ENOREADLINK | ENOENT) & this.#type) |
| return undefined; |
| try { |
| const rp = await this.#fs.promises.realpath(this.fullpath()); |
| return (this.#realpath = this.resolve(rp)); |
| } |
| catch (_) { |
| this.#markENOREALPATH(); |
| } |
| } |
| /** |
| * Synchronous {@link realpath} |
| */ |
| realpathSync() { |
| if (this.#realpath) |
| return this.#realpath; |
| if ((ENOREALPATH | ENOREADLINK | ENOENT) & this.#type) |
| return undefined; |
| try { |
| const rp = this.#fs.realpathSync(this.fullpath()); |
| return (this.#realpath = this.resolve(rp)); |
| } |
| catch (_) { |
| this.#markENOREALPATH(); |
| } |
| } |
| /** |
| * Internal method to mark this Path object as the scurry cwd, |
| * called by {@link PathScurry#chdir} |
| * |
| * @internal |
| */ |
| [setAsCwd](oldCwd) { |
| if (oldCwd === this) |
| return; |
| oldCwd.isCWD = false; |
| this.isCWD = true; |
| const changed = new Set([]); |
| let rp = []; |
| let p = this; |
| while (p && p.parent) { |
| changed.add(p); |
| p.#relative = rp.join(this.sep); |
| p.#relativePosix = rp.join('/'); |
| p = p.parent; |
| rp.push('..'); |
| } |
| // now un-memoize parents of old cwd |
| p = oldCwd; |
| while (p && p.parent && !changed.has(p)) { |
| p.#relative = undefined; |
| p.#relativePosix = undefined; |
| p = p.parent; |
| } |
| } |
| } |
| exports.PathBase = PathBase; |
| /** |
| * Path class used on win32 systems |
| * |
| * Uses `'\\'` as the path separator for returned paths, either `'\\'` or `'/'` |
| * as the path separator for parsing paths. |
| */ |
| class PathWin32 extends PathBase { |
| /** |
| * Separator for generating path strings. |
| */ |
| sep = '\\'; |
| /** |
| * Separator for parsing path strings. |
| */ |
| splitSep = eitherSep; |
| /** |
| * Do not create new Path objects directly. They should always be accessed |
| * via the PathScurry class or other methods on the Path class. |
| * |
| * @internal |
| */ |
| constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) { |
| super(name, type, root, roots, nocase, children, opts); |
| } |
| /** |
| * @internal |
| */ |
| newChild(name, type = UNKNOWN, opts = {}) { |
| return new PathWin32(name, type, this.root, this.roots, this.nocase, this.childrenCache(), opts); |
| } |
| /** |
| * @internal |
| */ |
| getRootString(path) { |
| return node_path_1.win32.parse(path).root; |
| } |
| /** |
| * @internal |
| */ |
| getRoot(rootPath) { |
| rootPath = uncToDrive(rootPath.toUpperCase()); |
| if (rootPath === this.root.name) { |
| return this.root; |
| } |
| // ok, not that one, check if it matches another we know about |
| for (const [compare, root] of Object.entries(this.roots)) { |
| if (this.sameRoot(rootPath, compare)) { |
| return (this.roots[rootPath] = root); |
| } |
| } |
| // otherwise, have to create a new one. |
| return (this.roots[rootPath] = new PathScurryWin32(rootPath, this).root); |
| } |
| /** |
| * @internal |
| */ |
| sameRoot(rootPath, compare = this.root.name) { |
| // windows can (rarely) have case-sensitive filesystem, but |
| // UNC and drive letters are always case-insensitive, and canonically |
| // represented uppercase. |
| rootPath = rootPath |
| .toUpperCase() |
| .replace(/\//g, '\\') |
| .replace(uncDriveRegexp, '$1\\'); |
| return rootPath === compare; |
| } |
| } |
| exports.PathWin32 = PathWin32; |
| /** |
| * Path class used on all posix systems. |
| * |
| * Uses `'/'` as the path separator. |
| */ |
| class PathPosix extends PathBase { |
| /** |
| * separator for parsing path strings |
| */ |
| splitSep = '/'; |
| /** |
| * separator for generating path strings |
| */ |
| sep = '/'; |
| /** |
| * Do not create new Path objects directly. They should always be accessed |
| * via the PathScurry class or other methods on the Path class. |
| * |
| * @internal |
| */ |
| constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) { |
| super(name, type, root, roots, nocase, children, opts); |
| } |
| /** |
| * @internal |
| */ |
| getRootString(path) { |
| return path.startsWith('/') ? '/' : ''; |
| } |
| /** |
| * @internal |
| */ |
| getRoot(_rootPath) { |
| return this.root; |
| } |
| /** |
| * @internal |
| */ |
| newChild(name, type = UNKNOWN, opts = {}) { |
| return new PathPosix(name, type, this.root, this.roots, this.nocase, this.childrenCache(), opts); |
| } |
| } |
| exports.PathPosix = PathPosix; |
| /** |
| * The base class for all PathScurry classes, providing the interface for path |
| * resolution and filesystem operations. |
| * |
| * Typically, you should *not* instantiate this class directly, but rather one |
| * of the platform-specific classes, or the exported {@link PathScurry} which |
| * defaults to the current platform. |
| */ |
| class PathScurryBase { |
| /** |
| * The root Path entry for the current working directory of this Scurry |
| */ |
| root; |
| /** |
| * The string path for the root of this Scurry's current working directory |
| */ |
| rootPath; |
| /** |
| * A collection of all roots encountered, referenced by rootPath |
| */ |
| roots; |
| /** |
| * The Path entry corresponding to this PathScurry's current working directory. |
| */ |
| cwd; |
| #resolveCache; |
| #resolvePosixCache; |
| #children; |
| /** |
| * Perform path comparisons case-insensitively. |
| * |
| * Defaults true on Darwin and Windows systems, false elsewhere. |
| */ |
| nocase; |
| #fs; |
| /** |
| * This class should not be instantiated directly. |
| * |
| * Use PathScurryWin32, PathScurryDarwin, PathScurryPosix, or PathScurry |
| * |
| * @internal |
| */ |
| constructor(cwd = process.cwd(), pathImpl, sep, { nocase, childrenCacheSize = 16 * 1024, fs = defaultFS, } = {}) { |
| this.#fs = fsFromOption(fs); |
| if (cwd instanceof URL || cwd.startsWith('file://')) { |
| cwd = (0, node_url_1.fileURLToPath)(cwd); |
| } |
| // resolve and split root, and then add to the store. |
| // this is the only time we call path.resolve() |
| const cwdPath = pathImpl.resolve(cwd); |
| this.roots = Object.create(null); |
| this.rootPath = this.parseRootPath(cwdPath); |
| this.#resolveCache = new ResolveCache(); |
| this.#resolvePosixCache = new ResolveCache(); |
| this.#children = new ChildrenCache(childrenCacheSize); |
| const split = cwdPath.substring(this.rootPath.length).split(sep); |
| // resolve('/') leaves '', splits to [''], we don't want that. |
| if (split.length === 1 && !split[0]) { |
| split.pop(); |
| } |
| /* c8 ignore start */ |
| if (nocase === undefined) { |
| throw new TypeError('must provide nocase setting to PathScurryBase ctor'); |
| } |
| /* c8 ignore stop */ |
| this.nocase = nocase; |
| this.root = this.newRoot(this.#fs); |
| this.roots[this.rootPath] = this.root; |
| let prev = this.root; |
| let len = split.length - 1; |
| const joinSep = pathImpl.sep; |
| let abs = this.rootPath; |
| let sawFirst = false; |
| for (const part of split) { |
| const l = len--; |
| prev = prev.child(part, { |
| relative: new Array(l).fill('..').join(joinSep), |
| relativePosix: new Array(l).fill('..').join('/'), |
| fullpath: (abs += (sawFirst ? '' : joinSep) + part), |
| }); |
| sawFirst = true; |
| } |
| this.cwd = prev; |
| } |
| /** |
| * Get the depth of a provided path, string, or the cwd |
| */ |
| depth(path = this.cwd) { |
| if (typeof path === 'string') { |
| path = this.cwd.resolve(path); |
| } |
| return path.depth(); |
| } |
| /** |
| * Return the cache of child entries. Exposed so subclasses can create |
| * child Path objects in a platform-specific way. |
| * |
| * @internal |
| */ |
| childrenCache() { |
| return this.#children; |
| } |
| /** |
| * Resolve one or more path strings to a resolved string |
| * |
| * Same interface as require('path').resolve. |
| * |
| * Much faster than path.resolve() when called multiple times for the same |
| * path, because the resolved Path objects are cached. Much slower |
| * otherwise. |
| */ |
| resolve(...paths) { |
| // first figure out the minimum number of paths we have to test |
| // we always start at cwd, but any absolutes will bump the start |
| let r = ''; |
| for (let i = paths.length - 1; i >= 0; i--) { |
| const p = paths[i]; |
| if (!p || p === '.') |
| continue; |
| r = r ? `${p}/${r}` : p; |
| if (this.isAbsolute(p)) { |
| break; |
| } |
| } |
| const cached = this.#resolveCache.get(r); |
| if (cached !== undefined) { |
| return cached; |
| } |
| const result = this.cwd.resolve(r).fullpath(); |
| this.#resolveCache.set(r, result); |
| return result; |
| } |
| /** |
| * Resolve one or more path strings to a resolved string, returning |
| * the posix path. Identical to .resolve() on posix systems, but on |
| * windows will return a forward-slash separated UNC path. |
| * |
| * Same interface as require('path').resolve. |
| * |
| * Much faster than path.resolve() when called multiple times for the same |
| * path, because the resolved Path objects are cached. Much slower |
| * otherwise. |
| */ |
| resolvePosix(...paths) { |
| // first figure out the minimum number of paths we have to test |
| // we always start at cwd, but any absolutes will bump the start |
| let r = ''; |
| for (let i = paths.length - 1; i >= 0; i--) { |
| const p = paths[i]; |
| if (!p || p === '.') |
| continue; |
| r = r ? `${p}/${r}` : p; |
| if (this.isAbsolute(p)) { |
| break; |
| } |
| } |
| const cached = this.#resolvePosixCache.get(r); |
| if (cached !== undefined) { |
| return cached; |
| } |
| const result = this.cwd.resolve(r).fullpathPosix(); |
| this.#resolvePosixCache.set(r, result); |
| return result; |
| } |
| /** |
| * find the relative path from the cwd to the supplied path string or entry |
| */ |
| relative(entry = this.cwd) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| return entry.relative(); |
| } |
| /** |
| * find the relative path from the cwd to the supplied path string or |
| * entry, using / as the path delimiter, even on Windows. |
| */ |
| relativePosix(entry = this.cwd) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| return entry.relativePosix(); |
| } |
| /** |
| * Return the basename for the provided string or Path object |
| */ |
| basename(entry = this.cwd) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| return entry.name; |
| } |
| /** |
| * Return the dirname for the provided string or Path object |
| */ |
| dirname(entry = this.cwd) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| return (entry.parent || entry).fullpath(); |
| } |
| async readdir(entry = this.cwd, opts = { |
| withFileTypes: true, |
| }) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| opts = entry; |
| entry = this.cwd; |
| } |
| const { withFileTypes } = opts; |
| if (!entry.canReaddir()) { |
| return []; |
| } |
| else { |
| const p = await entry.readdir(); |
| return withFileTypes ? p : p.map(e => e.name); |
| } |
| } |
| readdirSync(entry = this.cwd, opts = { |
| withFileTypes: true, |
| }) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| opts = entry; |
| entry = this.cwd; |
| } |
| const { withFileTypes = true } = opts; |
| if (!entry.canReaddir()) { |
| return []; |
| } |
| else if (withFileTypes) { |
| return entry.readdirSync(); |
| } |
| else { |
| return entry.readdirSync().map(e => e.name); |
| } |
| } |
| /** |
| * Call lstat() on the string or Path object, and update all known |
| * information that can be determined. |
| * |
| * Note that unlike `fs.lstat()`, the returned value does not contain some |
| * information, such as `mode`, `dev`, `nlink`, and `ino`. If that |
| * information is required, you will need to call `fs.lstat` yourself. |
| * |
| * If the Path refers to a nonexistent file, or if the lstat call fails for |
| * any reason, `undefined` is returned. Otherwise the updated Path object is |
| * returned. |
| * |
| * Results are cached, and thus may be out of date if the filesystem is |
| * mutated. |
| */ |
| async lstat(entry = this.cwd) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| return entry.lstat(); |
| } |
| /** |
| * synchronous {@link PathScurryBase.lstat} |
| */ |
| lstatSync(entry = this.cwd) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| return entry.lstatSync(); |
| } |
| async readlink(entry = this.cwd, { withFileTypes } = { |
| withFileTypes: false, |
| }) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| withFileTypes = entry.withFileTypes; |
| entry = this.cwd; |
| } |
| const e = await entry.readlink(); |
| return withFileTypes ? e : e?.fullpath(); |
| } |
| readlinkSync(entry = this.cwd, { withFileTypes } = { |
| withFileTypes: false, |
| }) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| withFileTypes = entry.withFileTypes; |
| entry = this.cwd; |
| } |
| const e = entry.readlinkSync(); |
| return withFileTypes ? e : e?.fullpath(); |
| } |
| async realpath(entry = this.cwd, { withFileTypes } = { |
| withFileTypes: false, |
| }) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| withFileTypes = entry.withFileTypes; |
| entry = this.cwd; |
| } |
| const e = await entry.realpath(); |
| return withFileTypes ? e : e?.fullpath(); |
| } |
| realpathSync(entry = this.cwd, { withFileTypes } = { |
| withFileTypes: false, |
| }) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| withFileTypes = entry.withFileTypes; |
| entry = this.cwd; |
| } |
| const e = entry.realpathSync(); |
| return withFileTypes ? e : e?.fullpath(); |
| } |
| async walk(entry = this.cwd, opts = {}) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| opts = entry; |
| entry = this.cwd; |
| } |
| const { withFileTypes = true, follow = false, filter, walkFilter, } = opts; |
| const results = []; |
| if (!filter || filter(entry)) { |
| results.push(withFileTypes ? entry : entry.fullpath()); |
| } |
| const dirs = new Set(); |
| const walk = (dir, cb) => { |
| dirs.add(dir); |
| dir.readdirCB((er, entries) => { |
| /* c8 ignore start */ |
| if (er) { |
| return cb(er); |
| } |
| /* c8 ignore stop */ |
| let len = entries.length; |
| if (!len) |
| return cb(); |
| const next = () => { |
| if (--len === 0) { |
| cb(); |
| } |
| }; |
| for (const e of entries) { |
| if (!filter || filter(e)) { |
| results.push(withFileTypes ? e : e.fullpath()); |
| } |
| if (follow && e.isSymbolicLink()) { |
| e.realpath() |
| .then(r => (r?.isUnknown() ? r.lstat() : r)) |
| .then(r => r?.shouldWalk(dirs, walkFilter) ? walk(r, next) : next()); |
| } |
| else { |
| if (e.shouldWalk(dirs, walkFilter)) { |
| walk(e, next); |
| } |
| else { |
| next(); |
| } |
| } |
| } |
| }, true); // zalgooooooo |
| }; |
| const start = entry; |
| return new Promise((res, rej) => { |
| walk(start, er => { |
| /* c8 ignore start */ |
| if (er) |
| return rej(er); |
| /* c8 ignore stop */ |
| res(results); |
| }); |
| }); |
| } |
| walkSync(entry = this.cwd, opts = {}) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| opts = entry; |
| entry = this.cwd; |
| } |
| const { withFileTypes = true, follow = false, filter, walkFilter, } = opts; |
| const results = []; |
| if (!filter || filter(entry)) { |
| results.push(withFileTypes ? entry : entry.fullpath()); |
| } |
| const dirs = new Set([entry]); |
| for (const dir of dirs) { |
| const entries = dir.readdirSync(); |
| for (const e of entries) { |
| if (!filter || filter(e)) { |
| results.push(withFileTypes ? e : e.fullpath()); |
| } |
| let r = e; |
| if (e.isSymbolicLink()) { |
| if (!(follow && (r = e.realpathSync()))) |
| continue; |
| if (r.isUnknown()) |
| r.lstatSync(); |
| } |
| if (r.shouldWalk(dirs, walkFilter)) { |
| dirs.add(r); |
| } |
| } |
| } |
| return results; |
| } |
| /** |
| * Support for `for await` |
| * |
| * Alias for {@link PathScurryBase.iterate} |
| * |
| * Note: As of Node 19, this is very slow, compared to other methods of |
| * walking. Consider using {@link PathScurryBase.stream} if memory overhead |
| * and backpressure are concerns, or {@link PathScurryBase.walk} if not. |
| */ |
| [Symbol.asyncIterator]() { |
| return this.iterate(); |
| } |
| iterate(entry = this.cwd, options = {}) { |
| // iterating async over the stream is significantly more performant, |
| // especially in the warm-cache scenario, because it buffers up directory |
| // entries in the background instead of waiting for a yield for each one. |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| options = entry; |
| entry = this.cwd; |
| } |
| return this.stream(entry, options)[Symbol.asyncIterator](); |
| } |
| /** |
| * Iterating over a PathScurry performs a synchronous walk. |
| * |
| * Alias for {@link PathScurryBase.iterateSync} |
| */ |
| [Symbol.iterator]() { |
| return this.iterateSync(); |
| } |
| *iterateSync(entry = this.cwd, opts = {}) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| opts = entry; |
| entry = this.cwd; |
| } |
| const { withFileTypes = true, follow = false, filter, walkFilter, } = opts; |
| if (!filter || filter(entry)) { |
| yield withFileTypes ? entry : entry.fullpath(); |
| } |
| const dirs = new Set([entry]); |
| for (const dir of dirs) { |
| const entries = dir.readdirSync(); |
| for (const e of entries) { |
| if (!filter || filter(e)) { |
| yield withFileTypes ? e : e.fullpath(); |
| } |
| let r = e; |
| if (e.isSymbolicLink()) { |
| if (!(follow && (r = e.realpathSync()))) |
| continue; |
| if (r.isUnknown()) |
| r.lstatSync(); |
| } |
| if (r.shouldWalk(dirs, walkFilter)) { |
| dirs.add(r); |
| } |
| } |
| } |
| } |
| stream(entry = this.cwd, opts = {}) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| opts = entry; |
| entry = this.cwd; |
| } |
| const { withFileTypes = true, follow = false, filter, walkFilter, } = opts; |
| const results = new minipass_1.Minipass({ objectMode: true }); |
| if (!filter || filter(entry)) { |
| results.write(withFileTypes ? entry : entry.fullpath()); |
| } |
| const dirs = new Set(); |
| const queue = [entry]; |
| let processing = 0; |
| const process = () => { |
| let paused = false; |
| while (!paused) { |
| const dir = queue.shift(); |
| if (!dir) { |
| if (processing === 0) |
| results.end(); |
| return; |
| } |
| processing++; |
| dirs.add(dir); |
| const onReaddir = (er, entries, didRealpaths = false) => { |
| /* c8 ignore start */ |
| if (er) |
| return results.emit('error', er); |
| /* c8 ignore stop */ |
| if (follow && !didRealpaths) { |
| const promises = []; |
| for (const e of entries) { |
| if (e.isSymbolicLink()) { |
| promises.push(e |
| .realpath() |
| .then((r) => r?.isUnknown() ? r.lstat() : r)); |
| } |
| } |
| if (promises.length) { |
| Promise.all(promises).then(() => onReaddir(null, entries, true)); |
| return; |
| } |
| } |
| for (const e of entries) { |
| if (e && (!filter || filter(e))) { |
| if (!results.write(withFileTypes ? e : e.fullpath())) { |
| paused = true; |
| } |
| } |
| } |
| processing--; |
| for (const e of entries) { |
| const r = e.realpathCached() || e; |
| if (r.shouldWalk(dirs, walkFilter)) { |
| queue.push(r); |
| } |
| } |
| if (paused && !results.flowing) { |
| results.once('drain', process); |
| } |
| else if (!sync) { |
| process(); |
| } |
| }; |
| // zalgo containment |
| let sync = true; |
| dir.readdirCB(onReaddir, true); |
| sync = false; |
| } |
| }; |
| process(); |
| return results; |
| } |
| streamSync(entry = this.cwd, opts = {}) { |
| if (typeof entry === 'string') { |
| entry = this.cwd.resolve(entry); |
| } |
| else if (!(entry instanceof PathBase)) { |
| opts = entry; |
| entry = this.cwd; |
| } |
| const { withFileTypes = true, follow = false, filter, walkFilter, } = opts; |
| const results = new minipass_1.Minipass({ objectMode: true }); |
| const dirs = new Set(); |
| if (!filter || filter(entry)) { |
| results.write(withFileTypes ? entry : entry.fullpath()); |
| } |
| const queue = [entry]; |
| let processing = 0; |
| const process = () => { |
| let paused = false; |
| while (!paused) { |
| const dir = queue.shift(); |
| if (!dir) { |
| if (processing === 0) |
| results.end(); |
| return; |
| } |
| processing++; |
| dirs.add(dir); |
| const entries = dir.readdirSync(); |
| for (const e of entries) { |
| if (!filter || filter(e)) { |
| if (!results.write(withFileTypes ? e : e.fullpath())) { |
| paused = true; |
| } |
| } |
| } |
| processing--; |
| for (const e of entries) { |
| let r = e; |
| if (e.isSymbolicLink()) { |
| if (!(follow && (r = e.realpathSync()))) |
| continue; |
| if (r.isUnknown()) |
| r.lstatSync(); |
| } |
| if (r.shouldWalk(dirs, walkFilter)) { |
| queue.push(r); |
| } |
| } |
| } |
| if (paused && !results.flowing) |
| results.once('drain', process); |
| }; |
| process(); |
| return results; |
| } |
| chdir(path = this.cwd) { |
| const oldCwd = this.cwd; |
| this.cwd = typeof path === 'string' ? this.cwd.resolve(path) : path; |
| this.cwd[setAsCwd](oldCwd); |
| } |
| } |
| exports.PathScurryBase = PathScurryBase; |
| /** |
| * Windows implementation of {@link PathScurryBase} |
| * |
| * Defaults to case insensitve, uses `'\\'` to generate path strings. Uses |
| * {@link PathWin32} for Path objects. |
| */ |
| class PathScurryWin32 extends PathScurryBase { |
| /** |
| * separator for generating path strings |
| */ |
| sep = '\\'; |
| constructor(cwd = process.cwd(), opts = {}) { |
| const { nocase = true } = opts; |
| super(cwd, node_path_1.win32, '\\', { ...opts, nocase }); |
| this.nocase = nocase; |
| for (let p = this.cwd; p; p = p.parent) { |
| p.nocase = this.nocase; |
| } |
| } |
| /** |
| * @internal |
| */ |
| parseRootPath(dir) { |
| // if the path starts with a single separator, it's not a UNC, and we'll |
| // just get separator as the root, and driveFromUNC will return \ |
| // In that case, mount \ on the root from the cwd. |
| return node_path_1.win32.parse(dir).root.toUpperCase(); |
| } |
| /** |
| * @internal |
| */ |
| newRoot(fs) { |
| return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs }); |
| } |
| /** |
| * Return true if the provided path string is an absolute path |
| */ |
| isAbsolute(p) { |
| return (p.startsWith('/') || p.startsWith('\\') || /^[a-z]:(\/|\\)/i.test(p)); |
| } |
| } |
| exports.PathScurryWin32 = PathScurryWin32; |
| /** |
| * {@link PathScurryBase} implementation for all posix systems other than Darwin. |
| * |
| * Defaults to case-sensitive matching, uses `'/'` to generate path strings. |
| * |
| * Uses {@link PathPosix} for Path objects. |
| */ |
| class PathScurryPosix extends PathScurryBase { |
| /** |
| * separator for generating path strings |
| */ |
| sep = '/'; |
| constructor(cwd = process.cwd(), opts = {}) { |
| const { nocase = false } = opts; |
| super(cwd, node_path_1.posix, '/', { ...opts, nocase }); |
| this.nocase = nocase; |
| } |
| /** |
| * @internal |
| */ |
| parseRootPath(_dir) { |
| return '/'; |
| } |
| /** |
| * @internal |
| */ |
| newRoot(fs) { |
| return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs }); |
| } |
| /** |
| * Return true if the provided path string is an absolute path |
| */ |
| isAbsolute(p) { |
| return p.startsWith('/'); |
| } |
| } |
| exports.PathScurryPosix = PathScurryPosix; |
| /** |
| * {@link PathScurryBase} implementation for Darwin (macOS) systems. |
| * |
| * Defaults to case-insensitive matching, uses `'/'` for generating path |
| * strings. |
| * |
| * Uses {@link PathPosix} for Path objects. |
| */ |
| class PathScurryDarwin extends PathScurryPosix { |
| constructor(cwd = process.cwd(), opts = {}) { |
| const { nocase = true } = opts; |
| super(cwd, { ...opts, nocase }); |
| } |
| } |
| exports.PathScurryDarwin = PathScurryDarwin; |
| /** |
| * Default {@link PathBase} implementation for the current platform. |
| * |
| * {@link PathWin32} on Windows systems, {@link PathPosix} on all others. |
| */ |
| exports.Path = process.platform === 'win32' ? PathWin32 : PathPosix; |
| /** |
| * Default {@link PathScurryBase} implementation for the current platform. |
| * |
| * {@link PathScurryWin32} on Windows systems, {@link PathScurryDarwin} on |
| * Darwin (macOS) systems, {@link PathScurryPosix} on all others. |
| */ |
| exports.PathScurry = process.platform === 'win32' ? PathScurryWin32 |
| : process.platform === 'darwin' ? PathScurryDarwin |
| : PathScurryPosix; |
| //# sourceMappingURL=index.js.map |