index.js 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960
  1. import { LRUCache } from 'lru-cache';
  2. import { posix, win32 } from 'path';
  3. import { fileURLToPath } from 'url';
  4. import * as actualFS from 'fs';
  5. import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSync as rps, } from 'fs';
  6. const realpathSync = rps.native;
  7. // TODO: test perf of fs/promises realpath vs realpathCB,
  8. // since the promises one uses realpath.native
  9. import { lstat, readdir, readlink, realpath } from 'fs/promises';
  10. import { Minipass } from 'minipass';
  11. const 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
  25. const 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:\
  36. const uncDriveRegexp = /^\\\\\?\\([a-z]:)\\?$/i;
  37. const uncToDrive = (rootPath) => rootPath.replace(/\//g, '\\').replace(uncDriveRegexp, '$1\\');
  38. // windows paths are separated by either / or \
  39. const eitherSep = /[\\\/]/;
  40. const UNKNOWN = 0; // may not even exist, for all we know
  41. const IFIFO = 0b0001;
  42. const IFCHR = 0b0010;
  43. const IFDIR = 0b0100;
  44. const IFBLK = 0b0110;
  45. const IFREG = 0b1000;
  46. const IFLNK = 0b1010;
  47. const IFSOCK = 0b1100;
  48. const IFMT = 0b1111;
  49. // mask to unset low 4 bits
  50. const IFMT_UNKNOWN = ~IFMT;
  51. // set after successfully calling readdir() and getting entries.
  52. const READDIR_CALLED = 16;
  53. // set after a successful lstat()
  54. const LSTAT_CALLED = 32;
  55. // set if an entry (or one of its parents) is definitely not a dir
  56. const 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)
  59. const ENOENT = 128;
  60. // cannot have child entries -- also verify &IFMT is either IFDIR or IFLNK
  61. // set if we fail to readlink
  62. const ENOREADLINK = 256;
  63. // set if we know realpath() will fail
  64. const ENOREALPATH = 512;
  65. const ENOCHILD = ENOTDIR | ENOENT | ENOREALPATH;
  66. const TYPEMASK = 1023;
  67. const 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
  83. const normalizeCache = new Map();
  84. const 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. };
  92. const normalizeNocaseCache = new Map();
  93. const 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. */
  105. export 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. */
  125. export 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. }
  134. const 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. */
  148. export 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. /**
  497. * Is the Path a regular file?
  498. */
  499. isFile() {
  500. return (this.#type & IFMT) === IFREG;
  501. }
  502. /**
  503. * Is the Path a directory?
  504. */
  505. isDirectory() {
  506. return (this.#type & IFMT) === IFDIR;
  507. }
  508. /**
  509. * Is the path a character device?
  510. */
  511. isCharacterDevice() {
  512. return (this.#type & IFMT) === IFCHR;
  513. }
  514. /**
  515. * Is the path a block device?
  516. */
  517. isBlockDevice() {
  518. return (this.#type & IFMT) === IFBLK;
  519. }
  520. /**
  521. * Is the path a FIFO pipe?
  522. */
  523. isFIFO() {
  524. return (this.#type & IFMT) === IFIFO;
  525. }
  526. /**
  527. * Is the path a socket?
  528. */
  529. isSocket() {
  530. return (this.#type & IFMT) === IFSOCK;
  531. }
  532. /**
  533. * Is the path a symbolic link?
  534. */
  535. isSymbolicLink() {
  536. return (this.#type & IFLNK) === IFLNK;
  537. }
  538. /**
  539. * Return the entry if it has been subject of a successful lstat, or
  540. * undefined otherwise.
  541. *
  542. * Does not read the filesystem, so an undefined result *could* simply
  543. * mean that we haven't called lstat on it.
  544. */
  545. lstatCached() {
  546. return this.#type & LSTAT_CALLED ? this : undefined;
  547. }
  548. /**
  549. * Return the cached link target if the entry has been the subject of a
  550. * successful readlink, or undefined otherwise.
  551. *
  552. * Does not read the filesystem, so an undefined result *could* just mean we
  553. * don't have any cached data. Only use it if you are very sure that a
  554. * readlink() has been called at some point.
  555. */
  556. readlinkCached() {
  557. return this.#linkTarget;
  558. }
  559. /**
  560. * Returns the cached realpath target if the entry has been the subject
  561. * of a successful realpath, or undefined otherwise.
  562. *
  563. * Does not read the filesystem, so an undefined result *could* just mean we
  564. * don't have any cached data. Only use it if you are very sure that a
  565. * realpath() has been called at some point.
  566. */
  567. realpathCached() {
  568. return this.#realpath;
  569. }
  570. /**
  571. * Returns the cached child Path entries array if the entry has been the
  572. * subject of a successful readdir(), or [] otherwise.
  573. *
  574. * Does not read the filesystem, so an empty array *could* just mean we
  575. * don't have any cached data. Only use it if you are very sure that a
  576. * readdir() has been called recently enough to still be valid.
  577. */
  578. readdirCached() {
  579. const children = this.children();
  580. return children.slice(0, children.provisional);
  581. }
  582. /**
  583. * Return true if it's worth trying to readlink. Ie, we don't (yet) have
  584. * any indication that readlink will definitely fail.
  585. *
  586. * Returns false if the path is known to not be a symlink, if a previous
  587. * readlink failed, or if the entry does not exist.
  588. */
  589. canReadlink() {
  590. if (this.#linkTarget)
  591. return true;
  592. if (!this.parent)
  593. return false;
  594. // cases where it cannot possibly succeed
  595. const ifmt = this.#type & IFMT;
  596. return !((ifmt !== UNKNOWN && ifmt !== IFLNK) ||
  597. this.#type & ENOREADLINK ||
  598. this.#type & ENOENT);
  599. }
  600. /**
  601. * Return true if readdir has previously been successfully called on this
  602. * path, indicating that cachedReaddir() is likely valid.
  603. */
  604. calledReaddir() {
  605. return !!(this.#type & READDIR_CALLED);
  606. }
  607. /**
  608. * Returns true if the path is known to not exist. That is, a previous lstat
  609. * or readdir failed to verify its existence when that would have been
  610. * expected, or a parent entry was marked either enoent or enotdir.
  611. */
  612. isENOENT() {
  613. return !!(this.#type & ENOENT);
  614. }
  615. /**
  616. * Return true if the path is a match for the given path name. This handles
  617. * case sensitivity and unicode normalization.
  618. *
  619. * Note: even on case-sensitive systems, it is **not** safe to test the
  620. * equality of the `.name` property to determine whether a given pathname
  621. * matches, due to unicode normalization mismatches.
  622. *
  623. * Always use this method instead of testing the `path.name` property
  624. * directly.
  625. */
  626. isNamed(n) {
  627. return !this.nocase
  628. ? this.#matchName === normalize(n)
  629. : this.#matchName === normalizeNocase(n);
  630. }
  631. /**
  632. * Return the Path object corresponding to the target of a symbolic link.
  633. *
  634. * If the Path is not a symbolic link, or if the readlink call fails for any
  635. * reason, `undefined` is returned.
  636. *
  637. * Result is cached, and thus may be outdated if the filesystem is mutated.
  638. */
  639. async readlink() {
  640. const target = this.#linkTarget;
  641. if (target) {
  642. return target;
  643. }
  644. if (!this.canReadlink()) {
  645. return undefined;
  646. }
  647. /* c8 ignore start */
  648. // already covered by the canReadlink test, here for ts grumples
  649. if (!this.parent) {
  650. return undefined;
  651. }
  652. /* c8 ignore stop */
  653. try {
  654. const read = await this.#fs.promises.readlink(this.fullpath());
  655. const linkTarget = this.parent.resolve(read);
  656. if (linkTarget) {
  657. return (this.#linkTarget = linkTarget);
  658. }
  659. }
  660. catch (er) {
  661. this.#readlinkFail(er.code);
  662. return undefined;
  663. }
  664. }
  665. /**
  666. * Synchronous {@link PathBase.readlink}
  667. */
  668. readlinkSync() {
  669. const target = this.#linkTarget;
  670. if (target) {
  671. return target;
  672. }
  673. if (!this.canReadlink()) {
  674. return undefined;
  675. }
  676. /* c8 ignore start */
  677. // already covered by the canReadlink test, here for ts grumples
  678. if (!this.parent) {
  679. return undefined;
  680. }
  681. /* c8 ignore stop */
  682. try {
  683. const read = this.#fs.readlinkSync(this.fullpath());
  684. const linkTarget = this.parent.resolve(read);
  685. if (linkTarget) {
  686. return (this.#linkTarget = linkTarget);
  687. }
  688. }
  689. catch (er) {
  690. this.#readlinkFail(er.code);
  691. return undefined;
  692. }
  693. }
  694. #readdirSuccess(children) {
  695. // succeeded, mark readdir called bit
  696. this.#type |= READDIR_CALLED;
  697. // mark all remaining provisional children as ENOENT
  698. for (let p = children.provisional; p < children.length; p++) {
  699. children[p].#markENOENT();
  700. }
  701. }
  702. #markENOENT() {
  703. // mark as UNKNOWN and ENOENT
  704. if (this.#type & ENOENT)
  705. return;
  706. this.#type = (this.#type | ENOENT) & IFMT_UNKNOWN;
  707. this.#markChildrenENOENT();
  708. }
  709. #markChildrenENOENT() {
  710. // all children are provisional and do not exist
  711. const children = this.children();
  712. children.provisional = 0;
  713. for (const p of children) {
  714. p.#markENOENT();
  715. }
  716. }
  717. #markENOREALPATH() {
  718. this.#type |= ENOREALPATH;
  719. this.#markENOTDIR();
  720. }
  721. // save the information when we know the entry is not a dir
  722. #markENOTDIR() {
  723. // entry is not a directory, so any children can't exist.
  724. // this *should* be impossible, since any children created
  725. // after it's been marked ENOTDIR should be marked ENOENT,
  726. // so it won't even get to this point.
  727. /* c8 ignore start */
  728. if (this.#type & ENOTDIR)
  729. return;
  730. /* c8 ignore stop */
  731. let t = this.#type;
  732. // this could happen if we stat a dir, then delete it,
  733. // then try to read it or one of its children.
  734. if ((t & IFMT) === IFDIR)
  735. t &= IFMT_UNKNOWN;
  736. this.#type = t | ENOTDIR;
  737. this.#markChildrenENOENT();
  738. }
  739. #readdirFail(code = '') {
  740. // markENOTDIR and markENOENT also set provisional=0
  741. if (code === 'ENOTDIR' || code === 'EPERM') {
  742. this.#markENOTDIR();
  743. }
  744. else if (code === 'ENOENT') {
  745. this.#markENOENT();
  746. }
  747. else {
  748. this.children().provisional = 0;
  749. }
  750. }
  751. #lstatFail(code = '') {
  752. // Windows just raises ENOENT in this case, disable for win CI
  753. /* c8 ignore start */
  754. if (code === 'ENOTDIR') {
  755. // already know it has a parent by this point
  756. const p = this.parent;
  757. p.#markENOTDIR();
  758. }
  759. else if (code === 'ENOENT') {
  760. /* c8 ignore stop */
  761. this.#markENOENT();
  762. }
  763. }
  764. #readlinkFail(code = '') {
  765. let ter = this.#type;
  766. ter |= ENOREADLINK;
  767. if (code === 'ENOENT')
  768. ter |= ENOENT;
  769. // windows gets a weird error when you try to readlink a file
  770. if (code === 'EINVAL' || code === 'UNKNOWN') {
  771. // exists, but not a symlink, we don't know WHAT it is, so remove
  772. // all IFMT bits.
  773. ter &= IFMT_UNKNOWN;
  774. }
  775. this.#type = ter;
  776. // windows just gets ENOENT in this case. We do cover the case,
  777. // just disabled because it's impossible on Windows CI
  778. /* c8 ignore start */
  779. if (code === 'ENOTDIR' && this.parent) {
  780. this.parent.#markENOTDIR();
  781. }
  782. /* c8 ignore stop */
  783. }
  784. #readdirAddChild(e, c) {
  785. return (this.#readdirMaybePromoteChild(e, c) ||
  786. this.#readdirAddNewChild(e, c));
  787. }
  788. #readdirAddNewChild(e, c) {
  789. // alloc new entry at head, so it's never provisional
  790. const type = entToType(e);
  791. const child = this.newChild(e.name, type, { parent: this });
  792. const ifmt = child.#type & IFMT;
  793. if (ifmt !== IFDIR && ifmt !== IFLNK && ifmt !== UNKNOWN) {
  794. child.#type |= ENOTDIR;
  795. }
  796. c.unshift(child);
  797. c.provisional++;
  798. return child;
  799. }
  800. #readdirMaybePromoteChild(e, c) {
  801. for (let p = c.provisional; p < c.length; p++) {
  802. const pchild = c[p];
  803. const name = this.nocase
  804. ? normalizeNocase(e.name)
  805. : normalize(e.name);
  806. if (name !== pchild.#matchName) {
  807. continue;
  808. }
  809. return this.#readdirPromoteChild(e, pchild, p, c);
  810. }
  811. }
  812. #readdirPromoteChild(e, p, index, c) {
  813. const v = p.name;
  814. // retain any other flags, but set ifmt from dirent
  815. p.#type = (p.#type & IFMT_UNKNOWN) | entToType(e);
  816. // case sensitivity fixing when we learn the true name.
  817. if (v !== e.name)
  818. p.name = e.name;
  819. // just advance provisional index (potentially off the list),
  820. // otherwise we have to splice/pop it out and re-insert at head
  821. if (index !== c.provisional) {
  822. if (index === c.length - 1)
  823. c.pop();
  824. else
  825. c.splice(index, 1);
  826. c.unshift(p);
  827. }
  828. c.provisional++;
  829. return p;
  830. }
  831. /**
  832. * Call lstat() on this Path, and update all known information that can be
  833. * determined.
  834. *
  835. * Note that unlike `fs.lstat()`, the returned value does not contain some
  836. * information, such as `mode`, `dev`, `nlink`, and `ino`. If that
  837. * information is required, you will need to call `fs.lstat` yourself.
  838. *
  839. * If the Path refers to a nonexistent file, or if the lstat call fails for
  840. * any reason, `undefined` is returned. Otherwise the updated Path object is
  841. * returned.
  842. *
  843. * Results are cached, and thus may be out of date if the filesystem is
  844. * mutated.
  845. */
  846. async lstat() {
  847. if ((this.#type & ENOENT) === 0) {
  848. try {
  849. this.#applyStat(await this.#fs.promises.lstat(this.fullpath()));
  850. return this;
  851. }
  852. catch (er) {
  853. this.#lstatFail(er.code);
  854. }
  855. }
  856. }
  857. /**
  858. * synchronous {@link PathBase.lstat}
  859. */
  860. lstatSync() {
  861. if ((this.#type & ENOENT) === 0) {
  862. try {
  863. this.#applyStat(this.#fs.lstatSync(this.fullpath()));
  864. return this;
  865. }
  866. catch (er) {
  867. this.#lstatFail(er.code);
  868. }
  869. }
  870. }
  871. #applyStat(st) {
  872. const { atime, atimeMs, birthtime, birthtimeMs, blksize, blocks, ctime, ctimeMs, dev, gid, ino, mode, mtime, mtimeMs, nlink, rdev, size, uid, } = st;
  873. this.#atime = atime;
  874. this.#atimeMs = atimeMs;
  875. this.#birthtime = birthtime;
  876. this.#birthtimeMs = birthtimeMs;
  877. this.#blksize = blksize;
  878. this.#blocks = blocks;
  879. this.#ctime = ctime;
  880. this.#ctimeMs = ctimeMs;
  881. this.#dev = dev;
  882. this.#gid = gid;
  883. this.#ino = ino;
  884. this.#mode = mode;
  885. this.#mtime = mtime;
  886. this.#mtimeMs = mtimeMs;
  887. this.#nlink = nlink;
  888. this.#rdev = rdev;
  889. this.#size = size;
  890. this.#uid = uid;
  891. const ifmt = entToType(st);
  892. // retain any other flags, but set the ifmt
  893. this.#type = (this.#type & IFMT_UNKNOWN) | ifmt | LSTAT_CALLED;
  894. if (ifmt !== UNKNOWN && ifmt !== IFDIR && ifmt !== IFLNK) {
  895. this.#type |= ENOTDIR;
  896. }
  897. }
  898. #onReaddirCB = [];
  899. #readdirCBInFlight = false;
  900. #callOnReaddirCB(children) {
  901. this.#readdirCBInFlight = false;
  902. const cbs = this.#onReaddirCB.slice();
  903. this.#onReaddirCB.length = 0;
  904. cbs.forEach(cb => cb(null, children));
  905. }
  906. /**
  907. * Standard node-style callback interface to get list of directory entries.
  908. *
  909. * If the Path cannot or does not contain any children, then an empty array
  910. * is returned.
  911. *
  912. * Results are cached, and thus may be out of date if the filesystem is
  913. * mutated.
  914. *
  915. * @param cb The callback called with (er, entries). Note that the `er`
  916. * param is somewhat extraneous, as all readdir() errors are handled and
  917. * simply result in an empty set of entries being returned.
  918. * @param allowZalgo Boolean indicating that immediately known results should
  919. * *not* be deferred with `queueMicrotask`. Defaults to `false`. Release
  920. * zalgo at your peril, the dark pony lord is devious and unforgiving.
  921. */
  922. readdirCB(cb, allowZalgo = false) {
  923. if (!this.canReaddir()) {
  924. if (allowZalgo)
  925. cb(null, []);
  926. else
  927. queueMicrotask(() => cb(null, []));
  928. return;
  929. }
  930. const children = this.children();
  931. if (this.calledReaddir()) {
  932. const c = children.slice(0, children.provisional);
  933. if (allowZalgo)
  934. cb(null, c);
  935. else
  936. queueMicrotask(() => cb(null, c));
  937. return;
  938. }
  939. // don't have to worry about zalgo at this point.
  940. this.#onReaddirCB.push(cb);
  941. if (this.#readdirCBInFlight) {
  942. return;
  943. }
  944. this.#readdirCBInFlight = true;
  945. // else read the directory, fill up children
  946. // de-provisionalize any provisional children.
  947. const fullpath = this.fullpath();
  948. this.#fs.readdir(fullpath, { withFileTypes: true }, (er, entries) => {
  949. if (er) {
  950. this.#readdirFail(er.code);
  951. children.provisional = 0;
  952. }
  953. else {
  954. // if we didn't get an error, we always get entries.
  955. //@ts-ignore
  956. for (const e of entries) {
  957. this.#readdirAddChild(e, children);
  958. }
  959. this.#readdirSuccess(children);
  960. }
  961. this.#callOnReaddirCB(children.slice(0, children.provisional));
  962. return;
  963. });
  964. }
  965. #asyncReaddirInFlight;
  966. /**
  967. * Return an array of known child entries.
  968. *
  969. * If the Path cannot or does not contain any children, then an empty array
  970. * is returned.
  971. *
  972. * Results are cached, and thus may be out of date if the filesystem is
  973. * mutated.
  974. */
  975. async readdir() {
  976. if (!this.canReaddir()) {
  977. return [];
  978. }
  979. const children = this.children();
  980. if (this.calledReaddir()) {
  981. return children.slice(0, children.provisional);
  982. }
  983. // else read the directory, fill up children
  984. // de-provisionalize any provisional children.
  985. const fullpath = this.fullpath();
  986. if (this.#asyncReaddirInFlight) {
  987. await this.#asyncReaddirInFlight;
  988. }
  989. else {
  990. /* c8 ignore start */
  991. let resolve = () => { };
  992. /* c8 ignore stop */
  993. this.#asyncReaddirInFlight = new Promise(res => (resolve = res));
  994. try {
  995. for (const e of await this.#fs.promises.readdir(fullpath, {
  996. withFileTypes: true,
  997. })) {
  998. this.#readdirAddChild(e, children);
  999. }
  1000. this.#readdirSuccess(children);
  1001. }
  1002. catch (er) {
  1003. this.#readdirFail(er.code);
  1004. children.provisional = 0;
  1005. }
  1006. this.#asyncReaddirInFlight = undefined;
  1007. resolve();
  1008. }
  1009. return children.slice(0, children.provisional);
  1010. }
  1011. /**
  1012. * synchronous {@link PathBase.readdir}
  1013. */
  1014. readdirSync() {
  1015. if (!this.canReaddir()) {
  1016. return [];
  1017. }
  1018. const children = this.children();
  1019. if (this.calledReaddir()) {
  1020. return children.slice(0, children.provisional);
  1021. }
  1022. // else read the directory, fill up children
  1023. // de-provisionalize any provisional children.
  1024. const fullpath = this.fullpath();
  1025. try {
  1026. for (const e of this.#fs.readdirSync(fullpath, {
  1027. withFileTypes: true,
  1028. })) {
  1029. this.#readdirAddChild(e, children);
  1030. }
  1031. this.#readdirSuccess(children);
  1032. }
  1033. catch (er) {
  1034. this.#readdirFail(er.code);
  1035. children.provisional = 0;
  1036. }
  1037. return children.slice(0, children.provisional);
  1038. }
  1039. canReaddir() {
  1040. if (this.#type & ENOCHILD)
  1041. return false;
  1042. const ifmt = IFMT & this.#type;
  1043. // we always set ENOTDIR when setting IFMT, so should be impossible
  1044. /* c8 ignore start */
  1045. if (!(ifmt === UNKNOWN || ifmt === IFDIR || ifmt === IFLNK)) {
  1046. return false;
  1047. }
  1048. /* c8 ignore stop */
  1049. return true;
  1050. }
  1051. shouldWalk(dirs, walkFilter) {
  1052. return ((this.#type & IFDIR) === IFDIR &&
  1053. !(this.#type & ENOCHILD) &&
  1054. !dirs.has(this) &&
  1055. (!walkFilter || walkFilter(this)));
  1056. }
  1057. /**
  1058. * Return the Path object corresponding to path as resolved
  1059. * by realpath(3).
  1060. *
  1061. * If the realpath call fails for any reason, `undefined` is returned.
  1062. *
  1063. * Result is cached, and thus may be outdated if the filesystem is mutated.
  1064. * On success, returns a Path object.
  1065. */
  1066. async realpath() {
  1067. if (this.#realpath)
  1068. return this.#realpath;
  1069. if ((ENOREALPATH | ENOREADLINK | ENOENT) & this.#type)
  1070. return undefined;
  1071. try {
  1072. const rp = await this.#fs.promises.realpath(this.fullpath());
  1073. return (this.#realpath = this.resolve(rp));
  1074. }
  1075. catch (_) {
  1076. this.#markENOREALPATH();
  1077. }
  1078. }
  1079. /**
  1080. * Synchronous {@link realpath}
  1081. */
  1082. realpathSync() {
  1083. if (this.#realpath)
  1084. return this.#realpath;
  1085. if ((ENOREALPATH | ENOREADLINK | ENOENT) & this.#type)
  1086. return undefined;
  1087. try {
  1088. const rp = this.#fs.realpathSync(this.fullpath());
  1089. return (this.#realpath = this.resolve(rp));
  1090. }
  1091. catch (_) {
  1092. this.#markENOREALPATH();
  1093. }
  1094. }
  1095. /**
  1096. * Internal method to mark this Path object as the scurry cwd,
  1097. * called by {@link PathScurry#chdir}
  1098. *
  1099. * @internal
  1100. */
  1101. [setAsCwd](oldCwd) {
  1102. if (oldCwd === this)
  1103. return;
  1104. const changed = new Set([]);
  1105. let rp = [];
  1106. let p = this;
  1107. while (p && p.parent) {
  1108. changed.add(p);
  1109. p.#relative = rp.join(this.sep);
  1110. p.#relativePosix = rp.join('/');
  1111. p = p.parent;
  1112. rp.push('..');
  1113. }
  1114. // now un-memoize parents of old cwd
  1115. p = oldCwd;
  1116. while (p && p.parent && !changed.has(p)) {
  1117. p.#relative = undefined;
  1118. p.#relativePosix = undefined;
  1119. p = p.parent;
  1120. }
  1121. }
  1122. }
  1123. /**
  1124. * Path class used on win32 systems
  1125. *
  1126. * Uses `'\\'` as the path separator for returned paths, either `'\\'` or `'/'`
  1127. * as the path separator for parsing paths.
  1128. */
  1129. export class PathWin32 extends PathBase {
  1130. /**
  1131. * Separator for generating path strings.
  1132. */
  1133. sep = '\\';
  1134. /**
  1135. * Separator for parsing path strings.
  1136. */
  1137. splitSep = eitherSep;
  1138. /**
  1139. * Do not create new Path objects directly. They should always be accessed
  1140. * via the PathScurry class or other methods on the Path class.
  1141. *
  1142. * @internal
  1143. */
  1144. constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
  1145. super(name, type, root, roots, nocase, children, opts);
  1146. }
  1147. /**
  1148. * @internal
  1149. */
  1150. newChild(name, type = UNKNOWN, opts = {}) {
  1151. return new PathWin32(name, type, this.root, this.roots, this.nocase, this.childrenCache(), opts);
  1152. }
  1153. /**
  1154. * @internal
  1155. */
  1156. getRootString(path) {
  1157. return win32.parse(path).root;
  1158. }
  1159. /**
  1160. * @internal
  1161. */
  1162. getRoot(rootPath) {
  1163. rootPath = uncToDrive(rootPath.toUpperCase());
  1164. if (rootPath === this.root.name) {
  1165. return this.root;
  1166. }
  1167. // ok, not that one, check if it matches another we know about
  1168. for (const [compare, root] of Object.entries(this.roots)) {
  1169. if (this.sameRoot(rootPath, compare)) {
  1170. return (this.roots[rootPath] = root);
  1171. }
  1172. }
  1173. // otherwise, have to create a new one.
  1174. return (this.roots[rootPath] = new PathScurryWin32(rootPath, this).root);
  1175. }
  1176. /**
  1177. * @internal
  1178. */
  1179. sameRoot(rootPath, compare = this.root.name) {
  1180. // windows can (rarely) have case-sensitive filesystem, but
  1181. // UNC and drive letters are always case-insensitive, and canonically
  1182. // represented uppercase.
  1183. rootPath = rootPath
  1184. .toUpperCase()
  1185. .replace(/\//g, '\\')
  1186. .replace(uncDriveRegexp, '$1\\');
  1187. return rootPath === compare;
  1188. }
  1189. }
  1190. /**
  1191. * Path class used on all posix systems.
  1192. *
  1193. * Uses `'/'` as the path separator.
  1194. */
  1195. export class PathPosix extends PathBase {
  1196. /**
  1197. * separator for parsing path strings
  1198. */
  1199. splitSep = '/';
  1200. /**
  1201. * separator for generating path strings
  1202. */
  1203. sep = '/';
  1204. /**
  1205. * Do not create new Path objects directly. They should always be accessed
  1206. * via the PathScurry class or other methods on the Path class.
  1207. *
  1208. * @internal
  1209. */
  1210. constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
  1211. super(name, type, root, roots, nocase, children, opts);
  1212. }
  1213. /**
  1214. * @internal
  1215. */
  1216. getRootString(path) {
  1217. return path.startsWith('/') ? '/' : '';
  1218. }
  1219. /**
  1220. * @internal
  1221. */
  1222. getRoot(_rootPath) {
  1223. return this.root;
  1224. }
  1225. /**
  1226. * @internal
  1227. */
  1228. newChild(name, type = UNKNOWN, opts = {}) {
  1229. return new PathPosix(name, type, this.root, this.roots, this.nocase, this.childrenCache(), opts);
  1230. }
  1231. }
  1232. /**
  1233. * The base class for all PathScurry classes, providing the interface for path
  1234. * resolution and filesystem operations.
  1235. *
  1236. * Typically, you should *not* instantiate this class directly, but rather one
  1237. * of the platform-specific classes, or the exported {@link PathScurry} which
  1238. * defaults to the current platform.
  1239. */
  1240. export class PathScurryBase {
  1241. /**
  1242. * The root Path entry for the current working directory of this Scurry
  1243. */
  1244. root;
  1245. /**
  1246. * The string path for the root of this Scurry's current working directory
  1247. */
  1248. rootPath;
  1249. /**
  1250. * A collection of all roots encountered, referenced by rootPath
  1251. */
  1252. roots;
  1253. /**
  1254. * The Path entry corresponding to this PathScurry's current working directory.
  1255. */
  1256. cwd;
  1257. #resolveCache;
  1258. #resolvePosixCache;
  1259. #children;
  1260. /**
  1261. * Perform path comparisons case-insensitively.
  1262. *
  1263. * Defaults true on Darwin and Windows systems, false elsewhere.
  1264. */
  1265. nocase;
  1266. #fs;
  1267. /**
  1268. * This class should not be instantiated directly.
  1269. *
  1270. * Use PathScurryWin32, PathScurryDarwin, PathScurryPosix, or PathScurry
  1271. *
  1272. * @internal
  1273. */
  1274. constructor(cwd = process.cwd(), pathImpl, sep, { nocase, childrenCacheSize = 16 * 1024, fs = defaultFS, } = {}) {
  1275. this.#fs = fsFromOption(fs);
  1276. if (cwd instanceof URL || cwd.startsWith('file://')) {
  1277. cwd = fileURLToPath(cwd);
  1278. }
  1279. // resolve and split root, and then add to the store.
  1280. // this is the only time we call path.resolve()
  1281. const cwdPath = pathImpl.resolve(cwd);
  1282. this.roots = Object.create(null);
  1283. this.rootPath = this.parseRootPath(cwdPath);
  1284. this.#resolveCache = new ResolveCache();
  1285. this.#resolvePosixCache = new ResolveCache();
  1286. this.#children = new ChildrenCache(childrenCacheSize);
  1287. const split = cwdPath.substring(this.rootPath.length).split(sep);
  1288. // resolve('/') leaves '', splits to [''], we don't want that.
  1289. if (split.length === 1 && !split[0]) {
  1290. split.pop();
  1291. }
  1292. /* c8 ignore start */
  1293. if (nocase === undefined) {
  1294. throw new TypeError('must provide nocase setting to PathScurryBase ctor');
  1295. }
  1296. /* c8 ignore stop */
  1297. this.nocase = nocase;
  1298. this.root = this.newRoot(this.#fs);
  1299. this.roots[this.rootPath] = this.root;
  1300. let prev = this.root;
  1301. let len = split.length - 1;
  1302. const joinSep = pathImpl.sep;
  1303. let abs = this.rootPath;
  1304. let sawFirst = false;
  1305. for (const part of split) {
  1306. const l = len--;
  1307. prev = prev.child(part, {
  1308. relative: new Array(l).fill('..').join(joinSep),
  1309. relativePosix: new Array(l).fill('..').join('/'),
  1310. fullpath: (abs += (sawFirst ? '' : joinSep) + part),
  1311. });
  1312. sawFirst = true;
  1313. }
  1314. this.cwd = prev;
  1315. }
  1316. /**
  1317. * Get the depth of a provided path, string, or the cwd
  1318. */
  1319. depth(path = this.cwd) {
  1320. if (typeof path === 'string') {
  1321. path = this.cwd.resolve(path);
  1322. }
  1323. return path.depth();
  1324. }
  1325. /**
  1326. * Return the cache of child entries. Exposed so subclasses can create
  1327. * child Path objects in a platform-specific way.
  1328. *
  1329. * @internal
  1330. */
  1331. childrenCache() {
  1332. return this.#children;
  1333. }
  1334. /**
  1335. * Resolve one or more path strings to a resolved string
  1336. *
  1337. * Same interface as require('path').resolve.
  1338. *
  1339. * Much faster than path.resolve() when called multiple times for the same
  1340. * path, because the resolved Path objects are cached. Much slower
  1341. * otherwise.
  1342. */
  1343. resolve(...paths) {
  1344. // first figure out the minimum number of paths we have to test
  1345. // we always start at cwd, but any absolutes will bump the start
  1346. let r = '';
  1347. for (let i = paths.length - 1; i >= 0; i--) {
  1348. const p = paths[i];
  1349. if (!p || p === '.')
  1350. continue;
  1351. r = r ? `${p}/${r}` : p;
  1352. if (this.isAbsolute(p)) {
  1353. break;
  1354. }
  1355. }
  1356. const cached = this.#resolveCache.get(r);
  1357. if (cached !== undefined) {
  1358. return cached;
  1359. }
  1360. const result = this.cwd.resolve(r).fullpath();
  1361. this.#resolveCache.set(r, result);
  1362. return result;
  1363. }
  1364. /**
  1365. * Resolve one or more path strings to a resolved string, returning
  1366. * the posix path. Identical to .resolve() on posix systems, but on
  1367. * windows will return a forward-slash separated UNC path.
  1368. *
  1369. * Same interface as require('path').resolve.
  1370. *
  1371. * Much faster than path.resolve() when called multiple times for the same
  1372. * path, because the resolved Path objects are cached. Much slower
  1373. * otherwise.
  1374. */
  1375. resolvePosix(...paths) {
  1376. // first figure out the minimum number of paths we have to test
  1377. // we always start at cwd, but any absolutes will bump the start
  1378. let r = '';
  1379. for (let i = paths.length - 1; i >= 0; i--) {
  1380. const p = paths[i];
  1381. if (!p || p === '.')
  1382. continue;
  1383. r = r ? `${p}/${r}` : p;
  1384. if (this.isAbsolute(p)) {
  1385. break;
  1386. }
  1387. }
  1388. const cached = this.#resolvePosixCache.get(r);
  1389. if (cached !== undefined) {
  1390. return cached;
  1391. }
  1392. const result = this.cwd.resolve(r).fullpathPosix();
  1393. this.#resolvePosixCache.set(r, result);
  1394. return result;
  1395. }
  1396. /**
  1397. * find the relative path from the cwd to the supplied path string or entry
  1398. */
  1399. relative(entry = this.cwd) {
  1400. if (typeof entry === 'string') {
  1401. entry = this.cwd.resolve(entry);
  1402. }
  1403. return entry.relative();
  1404. }
  1405. /**
  1406. * find the relative path from the cwd to the supplied path string or
  1407. * entry, using / as the path delimiter, even on Windows.
  1408. */
  1409. relativePosix(entry = this.cwd) {
  1410. if (typeof entry === 'string') {
  1411. entry = this.cwd.resolve(entry);
  1412. }
  1413. return entry.relativePosix();
  1414. }
  1415. /**
  1416. * Return the basename for the provided string or Path object
  1417. */
  1418. basename(entry = this.cwd) {
  1419. if (typeof entry === 'string') {
  1420. entry = this.cwd.resolve(entry);
  1421. }
  1422. return entry.name;
  1423. }
  1424. /**
  1425. * Return the dirname for the provided string or Path object
  1426. */
  1427. dirname(entry = this.cwd) {
  1428. if (typeof entry === 'string') {
  1429. entry = this.cwd.resolve(entry);
  1430. }
  1431. return (entry.parent || entry).fullpath();
  1432. }
  1433. async readdir(entry = this.cwd, opts = {
  1434. withFileTypes: true,
  1435. }) {
  1436. if (typeof entry === 'string') {
  1437. entry = this.cwd.resolve(entry);
  1438. }
  1439. else if (!(entry instanceof PathBase)) {
  1440. opts = entry;
  1441. entry = this.cwd;
  1442. }
  1443. const { withFileTypes } = opts;
  1444. if (!entry.canReaddir()) {
  1445. return [];
  1446. }
  1447. else {
  1448. const p = await entry.readdir();
  1449. return withFileTypes ? p : p.map(e => e.name);
  1450. }
  1451. }
  1452. readdirSync(entry = this.cwd, opts = {
  1453. withFileTypes: true,
  1454. }) {
  1455. if (typeof entry === 'string') {
  1456. entry = this.cwd.resolve(entry);
  1457. }
  1458. else if (!(entry instanceof PathBase)) {
  1459. opts = entry;
  1460. entry = this.cwd;
  1461. }
  1462. const { withFileTypes = true } = opts;
  1463. if (!entry.canReaddir()) {
  1464. return [];
  1465. }
  1466. else if (withFileTypes) {
  1467. return entry.readdirSync();
  1468. }
  1469. else {
  1470. return entry.readdirSync().map(e => e.name);
  1471. }
  1472. }
  1473. /**
  1474. * Call lstat() on the string or Path object, and update all known
  1475. * information that can be determined.
  1476. *
  1477. * Note that unlike `fs.lstat()`, the returned value does not contain some
  1478. * information, such as `mode`, `dev`, `nlink`, and `ino`. If that
  1479. * information is required, you will need to call `fs.lstat` yourself.
  1480. *
  1481. * If the Path refers to a nonexistent file, or if the lstat call fails for
  1482. * any reason, `undefined` is returned. Otherwise the updated Path object is
  1483. * returned.
  1484. *
  1485. * Results are cached, and thus may be out of date if the filesystem is
  1486. * mutated.
  1487. */
  1488. async lstat(entry = this.cwd) {
  1489. if (typeof entry === 'string') {
  1490. entry = this.cwd.resolve(entry);
  1491. }
  1492. return entry.lstat();
  1493. }
  1494. /**
  1495. * synchronous {@link PathScurryBase.lstat}
  1496. */
  1497. lstatSync(entry = this.cwd) {
  1498. if (typeof entry === 'string') {
  1499. entry = this.cwd.resolve(entry);
  1500. }
  1501. return entry.lstatSync();
  1502. }
  1503. async readlink(entry = this.cwd, { withFileTypes } = {
  1504. withFileTypes: false,
  1505. }) {
  1506. if (typeof entry === 'string') {
  1507. entry = this.cwd.resolve(entry);
  1508. }
  1509. else if (!(entry instanceof PathBase)) {
  1510. withFileTypes = entry.withFileTypes;
  1511. entry = this.cwd;
  1512. }
  1513. const e = await entry.readlink();
  1514. return withFileTypes ? e : e?.fullpath();
  1515. }
  1516. readlinkSync(entry = this.cwd, { withFileTypes } = {
  1517. withFileTypes: false,
  1518. }) {
  1519. if (typeof entry === 'string') {
  1520. entry = this.cwd.resolve(entry);
  1521. }
  1522. else if (!(entry instanceof PathBase)) {
  1523. withFileTypes = entry.withFileTypes;
  1524. entry = this.cwd;
  1525. }
  1526. const e = entry.readlinkSync();
  1527. return withFileTypes ? e : e?.fullpath();
  1528. }
  1529. async realpath(entry = this.cwd, { withFileTypes } = {
  1530. withFileTypes: false,
  1531. }) {
  1532. if (typeof entry === 'string') {
  1533. entry = this.cwd.resolve(entry);
  1534. }
  1535. else if (!(entry instanceof PathBase)) {
  1536. withFileTypes = entry.withFileTypes;
  1537. entry = this.cwd;
  1538. }
  1539. const e = await entry.realpath();
  1540. return withFileTypes ? e : e?.fullpath();
  1541. }
  1542. realpathSync(entry = this.cwd, { withFileTypes } = {
  1543. withFileTypes: false,
  1544. }) {
  1545. if (typeof entry === 'string') {
  1546. entry = this.cwd.resolve(entry);
  1547. }
  1548. else if (!(entry instanceof PathBase)) {
  1549. withFileTypes = entry.withFileTypes;
  1550. entry = this.cwd;
  1551. }
  1552. const e = entry.realpathSync();
  1553. return withFileTypes ? e : e?.fullpath();
  1554. }
  1555. async walk(entry = this.cwd, opts = {}) {
  1556. if (typeof entry === 'string') {
  1557. entry = this.cwd.resolve(entry);
  1558. }
  1559. else if (!(entry instanceof PathBase)) {
  1560. opts = entry;
  1561. entry = this.cwd;
  1562. }
  1563. const { withFileTypes = true, follow = false, filter, walkFilter, } = opts;
  1564. const results = [];
  1565. if (!filter || filter(entry)) {
  1566. results.push(withFileTypes ? entry : entry.fullpath());
  1567. }
  1568. const dirs = new Set();
  1569. const walk = (dir, cb) => {
  1570. dirs.add(dir);
  1571. dir.readdirCB((er, entries) => {
  1572. /* c8 ignore start */
  1573. if (er) {
  1574. return cb(er);
  1575. }
  1576. /* c8 ignore stop */
  1577. let len = entries.length;
  1578. if (!len)
  1579. return cb();
  1580. const next = () => {
  1581. if (--len === 0) {
  1582. cb();
  1583. }
  1584. };
  1585. for (const e of entries) {
  1586. if (!filter || filter(e)) {
  1587. results.push(withFileTypes ? e : e.fullpath());
  1588. }
  1589. if (follow && e.isSymbolicLink()) {
  1590. e.realpath()
  1591. .then(r => (r?.isUnknown() ? r.lstat() : r))
  1592. .then(r => r?.shouldWalk(dirs, walkFilter) ? walk(r, next) : next());
  1593. }
  1594. else {
  1595. if (e.shouldWalk(dirs, walkFilter)) {
  1596. walk(e, next);
  1597. }
  1598. else {
  1599. next();
  1600. }
  1601. }
  1602. }
  1603. }, true); // zalgooooooo
  1604. };
  1605. const start = entry;
  1606. return new Promise((res, rej) => {
  1607. walk(start, er => {
  1608. /* c8 ignore start */
  1609. if (er)
  1610. return rej(er);
  1611. /* c8 ignore stop */
  1612. res(results);
  1613. });
  1614. });
  1615. }
  1616. walkSync(entry = this.cwd, opts = {}) {
  1617. if (typeof entry === 'string') {
  1618. entry = this.cwd.resolve(entry);
  1619. }
  1620. else if (!(entry instanceof PathBase)) {
  1621. opts = entry;
  1622. entry = this.cwd;
  1623. }
  1624. const { withFileTypes = true, follow = false, filter, walkFilter, } = opts;
  1625. const results = [];
  1626. if (!filter || filter(entry)) {
  1627. results.push(withFileTypes ? entry : entry.fullpath());
  1628. }
  1629. const dirs = new Set([entry]);
  1630. for (const dir of dirs) {
  1631. const entries = dir.readdirSync();
  1632. for (const e of entries) {
  1633. if (!filter || filter(e)) {
  1634. results.push(withFileTypes ? e : e.fullpath());
  1635. }
  1636. let r = e;
  1637. if (e.isSymbolicLink()) {
  1638. if (!(follow && (r = e.realpathSync())))
  1639. continue;
  1640. if (r.isUnknown())
  1641. r.lstatSync();
  1642. }
  1643. if (r.shouldWalk(dirs, walkFilter)) {
  1644. dirs.add(r);
  1645. }
  1646. }
  1647. }
  1648. return results;
  1649. }
  1650. /**
  1651. * Support for `for await`
  1652. *
  1653. * Alias for {@link PathScurryBase.iterate}
  1654. *
  1655. * Note: As of Node 19, this is very slow, compared to other methods of
  1656. * walking. Consider using {@link PathScurryBase.stream} if memory overhead
  1657. * and backpressure are concerns, or {@link PathScurryBase.walk} if not.
  1658. */
  1659. [Symbol.asyncIterator]() {
  1660. return this.iterate();
  1661. }
  1662. iterate(entry = this.cwd, options = {}) {
  1663. // iterating async over the stream is significantly more performant,
  1664. // especially in the warm-cache scenario, because it buffers up directory
  1665. // entries in the background instead of waiting for a yield for each one.
  1666. if (typeof entry === 'string') {
  1667. entry = this.cwd.resolve(entry);
  1668. }
  1669. else if (!(entry instanceof PathBase)) {
  1670. options = entry;
  1671. entry = this.cwd;
  1672. }
  1673. return this.stream(entry, options)[Symbol.asyncIterator]();
  1674. }
  1675. /**
  1676. * Iterating over a PathScurry performs a synchronous walk.
  1677. *
  1678. * Alias for {@link PathScurryBase.iterateSync}
  1679. */
  1680. [Symbol.iterator]() {
  1681. return this.iterateSync();
  1682. }
  1683. *iterateSync(entry = this.cwd, opts = {}) {
  1684. if (typeof entry === 'string') {
  1685. entry = this.cwd.resolve(entry);
  1686. }
  1687. else if (!(entry instanceof PathBase)) {
  1688. opts = entry;
  1689. entry = this.cwd;
  1690. }
  1691. const { withFileTypes = true, follow = false, filter, walkFilter, } = opts;
  1692. if (!filter || filter(entry)) {
  1693. yield withFileTypes ? entry : entry.fullpath();
  1694. }
  1695. const dirs = new Set([entry]);
  1696. for (const dir of dirs) {
  1697. const entries = dir.readdirSync();
  1698. for (const e of entries) {
  1699. if (!filter || filter(e)) {
  1700. yield withFileTypes ? e : e.fullpath();
  1701. }
  1702. let r = e;
  1703. if (e.isSymbolicLink()) {
  1704. if (!(follow && (r = e.realpathSync())))
  1705. continue;
  1706. if (r.isUnknown())
  1707. r.lstatSync();
  1708. }
  1709. if (r.shouldWalk(dirs, walkFilter)) {
  1710. dirs.add(r);
  1711. }
  1712. }
  1713. }
  1714. }
  1715. stream(entry = this.cwd, opts = {}) {
  1716. if (typeof entry === 'string') {
  1717. entry = this.cwd.resolve(entry);
  1718. }
  1719. else if (!(entry instanceof PathBase)) {
  1720. opts = entry;
  1721. entry = this.cwd;
  1722. }
  1723. const { withFileTypes = true, follow = false, filter, walkFilter, } = opts;
  1724. const results = new Minipass({ objectMode: true });
  1725. if (!filter || filter(entry)) {
  1726. results.write(withFileTypes ? entry : entry.fullpath());
  1727. }
  1728. const dirs = new Set();
  1729. const queue = [entry];
  1730. let processing = 0;
  1731. const process = () => {
  1732. let paused = false;
  1733. while (!paused) {
  1734. const dir = queue.shift();
  1735. if (!dir) {
  1736. if (processing === 0)
  1737. results.end();
  1738. return;
  1739. }
  1740. processing++;
  1741. dirs.add(dir);
  1742. const onReaddir = (er, entries, didRealpaths = false) => {
  1743. /* c8 ignore start */
  1744. if (er)
  1745. return results.emit('error', er);
  1746. /* c8 ignore stop */
  1747. if (follow && !didRealpaths) {
  1748. const promises = [];
  1749. for (const e of entries) {
  1750. if (e.isSymbolicLink()) {
  1751. promises.push(e
  1752. .realpath()
  1753. .then((r) => r?.isUnknown() ? r.lstat() : r));
  1754. }
  1755. }
  1756. if (promises.length) {
  1757. Promise.all(promises).then(() => onReaddir(null, entries, true));
  1758. return;
  1759. }
  1760. }
  1761. for (const e of entries) {
  1762. if (e && (!filter || filter(e))) {
  1763. if (!results.write(withFileTypes ? e : e.fullpath())) {
  1764. paused = true;
  1765. }
  1766. }
  1767. }
  1768. processing--;
  1769. for (const e of entries) {
  1770. const r = e.realpathCached() || e;
  1771. if (r.shouldWalk(dirs, walkFilter)) {
  1772. queue.push(r);
  1773. }
  1774. }
  1775. if (paused && !results.flowing) {
  1776. results.once('drain', process);
  1777. }
  1778. else if (!sync) {
  1779. process();
  1780. }
  1781. };
  1782. // zalgo containment
  1783. let sync = true;
  1784. dir.readdirCB(onReaddir, true);
  1785. sync = false;
  1786. }
  1787. };
  1788. process();
  1789. return results;
  1790. }
  1791. streamSync(entry = this.cwd, opts = {}) {
  1792. if (typeof entry === 'string') {
  1793. entry = this.cwd.resolve(entry);
  1794. }
  1795. else if (!(entry instanceof PathBase)) {
  1796. opts = entry;
  1797. entry = this.cwd;
  1798. }
  1799. const { withFileTypes = true, follow = false, filter, walkFilter, } = opts;
  1800. const results = new Minipass({ objectMode: true });
  1801. const dirs = new Set();
  1802. if (!filter || filter(entry)) {
  1803. results.write(withFileTypes ? entry : entry.fullpath());
  1804. }
  1805. const queue = [entry];
  1806. let processing = 0;
  1807. const process = () => {
  1808. let paused = false;
  1809. while (!paused) {
  1810. const dir = queue.shift();
  1811. if (!dir) {
  1812. if (processing === 0)
  1813. results.end();
  1814. return;
  1815. }
  1816. processing++;
  1817. dirs.add(dir);
  1818. const entries = dir.readdirSync();
  1819. for (const e of entries) {
  1820. if (!filter || filter(e)) {
  1821. if (!results.write(withFileTypes ? e : e.fullpath())) {
  1822. paused = true;
  1823. }
  1824. }
  1825. }
  1826. processing--;
  1827. for (const e of entries) {
  1828. let r = e;
  1829. if (e.isSymbolicLink()) {
  1830. if (!(follow && (r = e.realpathSync())))
  1831. continue;
  1832. if (r.isUnknown())
  1833. r.lstatSync();
  1834. }
  1835. if (r.shouldWalk(dirs, walkFilter)) {
  1836. queue.push(r);
  1837. }
  1838. }
  1839. }
  1840. if (paused && !results.flowing)
  1841. results.once('drain', process);
  1842. };
  1843. process();
  1844. return results;
  1845. }
  1846. chdir(path = this.cwd) {
  1847. const oldCwd = this.cwd;
  1848. this.cwd = typeof path === 'string' ? this.cwd.resolve(path) : path;
  1849. this.cwd[setAsCwd](oldCwd);
  1850. }
  1851. }
  1852. /**
  1853. * Windows implementation of {@link PathScurryBase}
  1854. *
  1855. * Defaults to case insensitve, uses `'\\'` to generate path strings. Uses
  1856. * {@link PathWin32} for Path objects.
  1857. */
  1858. export class PathScurryWin32 extends PathScurryBase {
  1859. /**
  1860. * separator for generating path strings
  1861. */
  1862. sep = '\\';
  1863. constructor(cwd = process.cwd(), opts = {}) {
  1864. const { nocase = true } = opts;
  1865. super(cwd, win32, '\\', { ...opts, nocase });
  1866. this.nocase = nocase;
  1867. for (let p = this.cwd; p; p = p.parent) {
  1868. p.nocase = this.nocase;
  1869. }
  1870. }
  1871. /**
  1872. * @internal
  1873. */
  1874. parseRootPath(dir) {
  1875. // if the path starts with a single separator, it's not a UNC, and we'll
  1876. // just get separator as the root, and driveFromUNC will return \
  1877. // In that case, mount \ on the root from the cwd.
  1878. return win32.parse(dir).root.toUpperCase();
  1879. }
  1880. /**
  1881. * @internal
  1882. */
  1883. newRoot(fs) {
  1884. return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs });
  1885. }
  1886. /**
  1887. * Return true if the provided path string is an absolute path
  1888. */
  1889. isAbsolute(p) {
  1890. return (p.startsWith('/') || p.startsWith('\\') || /^[a-z]:(\/|\\)/i.test(p));
  1891. }
  1892. }
  1893. /**
  1894. * {@link PathScurryBase} implementation for all posix systems other than Darwin.
  1895. *
  1896. * Defaults to case-sensitive matching, uses `'/'` to generate path strings.
  1897. *
  1898. * Uses {@link PathPosix} for Path objects.
  1899. */
  1900. export class PathScurryPosix extends PathScurryBase {
  1901. /**
  1902. * separator for generating path strings
  1903. */
  1904. sep = '/';
  1905. constructor(cwd = process.cwd(), opts = {}) {
  1906. const { nocase = false } = opts;
  1907. super(cwd, posix, '/', { ...opts, nocase });
  1908. this.nocase = nocase;
  1909. }
  1910. /**
  1911. * @internal
  1912. */
  1913. parseRootPath(_dir) {
  1914. return '/';
  1915. }
  1916. /**
  1917. * @internal
  1918. */
  1919. newRoot(fs) {
  1920. return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs });
  1921. }
  1922. /**
  1923. * Return true if the provided path string is an absolute path
  1924. */
  1925. isAbsolute(p) {
  1926. return p.startsWith('/');
  1927. }
  1928. }
  1929. /**
  1930. * {@link PathScurryBase} implementation for Darwin (macOS) systems.
  1931. *
  1932. * Defaults to case-insensitive matching, uses `'/'` for generating path
  1933. * strings.
  1934. *
  1935. * Uses {@link PathPosix} for Path objects.
  1936. */
  1937. export class PathScurryDarwin extends PathScurryPosix {
  1938. constructor(cwd = process.cwd(), opts = {}) {
  1939. const { nocase = true } = opts;
  1940. super(cwd, { ...opts, nocase });
  1941. }
  1942. }
  1943. /**
  1944. * Default {@link PathBase} implementation for the current platform.
  1945. *
  1946. * {@link PathWin32} on Windows systems, {@link PathPosix} on all others.
  1947. */
  1948. export const Path = process.platform === 'win32' ? PathWin32 : PathPosix;
  1949. /**
  1950. * Default {@link PathScurryBase} implementation for the current platform.
  1951. *
  1952. * {@link PathScurryWin32} on Windows systems, {@link PathScurryDarwin} on
  1953. * Darwin (macOS) systems, {@link PathScurryPosix} on all others.
  1954. */
  1955. export const PathScurry = process.platform === 'win32'
  1956. ? PathScurryWin32
  1957. : process.platform === 'darwin'
  1958. ? PathScurryDarwin
  1959. : PathScurryPosix;
  1960. //# sourceMappingURL=index.js.map