/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as fs from 'fs';6import { tmpdir } from 'os';7import { promisify } from 'util';8import { ResourceQueue, timeout } from '../common/async.js';9import { isEqualOrParent, isRootOrDriveLetter, randomPath } from '../common/extpath.js';10import { normalizeNFC } from '../common/normalization.js';11import { basename, dirname, join, normalize, sep } from '../common/path.js';12import { isLinux, isMacintosh, isWindows } from '../common/platform.js';13import { extUriBiasedIgnorePathCase } from '../common/resources.js';14import { URI } from '../common/uri.js';15import { CancellationToken } from '../common/cancellation.js';16import { rtrim } from '../common/strings.js';1718//#region rimraf1920export enum RimRafMode {2122/**23* Slow version that unlinks each file and folder.24*/25UNLINK,2627/**28* Fast version that first moves the file/folder29* into a temp directory and then deletes that30* without waiting for it.31*/32MOVE33}3435/**36* Allows to delete the provided path (either file or folder) recursively37* with the options:38* - `UNLINK`: direct removal from disk39* - `MOVE`: faster variant that first moves the target to temp dir and then40* deletes it in the background without waiting for that to finish.41* the optional `moveToPath` allows to override where to rename the42* path to before deleting it.43*/44async function rimraf(path: string, mode: RimRafMode.UNLINK): Promise<void>;45async function rimraf(path: string, mode: RimRafMode.MOVE, moveToPath?: string): Promise<void>;46async function rimraf(path: string, mode?: RimRafMode, moveToPath?: string): Promise<void>;47async function rimraf(path: string, mode = RimRafMode.UNLINK, moveToPath?: string): Promise<void> {48if (isRootOrDriveLetter(path)) {49throw new Error('rimraf - will refuse to recursively delete root');50}5152// delete: via rm53if (mode === RimRafMode.UNLINK) {54return rimrafUnlink(path);55}5657// delete: via move58return rimrafMove(path, moveToPath);59}6061async function rimrafMove(path: string, moveToPath = randomPath(tmpdir())): Promise<void> {62try {63try {64await fs.promises.rename(path, moveToPath);65} catch (error) {66if (error.code === 'ENOENT') {67return; // ignore - path to delete did not exist68}6970return rimrafUnlink(path); // otherwise fallback to unlink71}7273// Delete but do not return as promise74rimrafUnlink(moveToPath).catch(() => {/* ignore */ });75} catch (error) {76if (error.code !== 'ENOENT') {77throw error;78}79}80}8182async function rimrafUnlink(path: string): Promise<void> {83return fs.promises.rm(path, { recursive: true, force: true, maxRetries: 3 });84}8586//#endregion8788//#region readdir with NFC support (macos)8990export interface IDirent {91name: string;9293isFile(): boolean;94isDirectory(): boolean;95isSymbolicLink(): boolean;96}9798/**99* Drop-in replacement of `fs.readdir` with support100* for converting from macOS NFD unicon form to NFC101* (https://github.com/nodejs/node/issues/2165)102*/103async function readdir(path: string): Promise<string[]>;104async function readdir(path: string, options: { withFileTypes: true }): Promise<IDirent[]>;105async function readdir(path: string, options?: { withFileTypes: true }): Promise<(string | IDirent)[]> {106try {107return await doReaddir(path, options);108} catch (error) {109// Workaround for #252361 that should be removed once the upstream issue110// in node.js is resolved. Adds a trailing dot to a root drive letter path111// (G:\ => G:\.) as a workaround.112if (error.code === 'ENOENT' && isWindows && isRootOrDriveLetter(path)) {113try {114return await doReaddir(`${path}.`, options);115} catch {116// ignore117}118}119throw error;120}121}122123async function doReaddir(path: string, options?: { withFileTypes: true }): Promise<(string | IDirent)[]> {124return handleDirectoryChildren(await (options ? safeReaddirWithFileTypes(path) : fs.promises.readdir(path)));125}126127async function safeReaddirWithFileTypes(path: string): Promise<IDirent[]> {128try {129return await fs.promises.readdir(path, { withFileTypes: true });130} catch (error) {131if (error.code !== 'ENOENT') {132console.warn('[node.js fs] readdir with filetypes failed with error: ', error);133}134}135136// Fallback to manually reading and resolving each137// children of the folder in case we hit an error138// previously.139// This can only really happen on exotic file systems140// such as explained in #115645 where we get entries141// from `readdir` that we can later not `lstat`.142const result: IDirent[] = [];143const children = await readdir(path);144for (const child of children) {145let isFile = false;146let isDirectory = false;147let isSymbolicLink = false;148149try {150const lstat = await fs.promises.lstat(join(path, child));151152isFile = lstat.isFile();153isDirectory = lstat.isDirectory();154isSymbolicLink = lstat.isSymbolicLink();155} catch (error) {156if (error.code !== 'ENOENT') {157console.warn('[node.js fs] unexpected error from lstat after readdir: ', error);158}159}160161result.push({162name: child,163isFile: () => isFile,164isDirectory: () => isDirectory,165isSymbolicLink: () => isSymbolicLink166});167}168169return result;170}171172function handleDirectoryChildren(children: string[]): string[];173function handleDirectoryChildren(children: IDirent[]): IDirent[];174function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[];175function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[] {176return children.map(child => {177178// Mac: uses NFD unicode form on disk, but we want NFC179// See also https://github.com/nodejs/node/issues/2165180181if (typeof child === 'string') {182return isMacintosh ? normalizeNFC(child) : child;183}184185child.name = isMacintosh ? normalizeNFC(child.name) : child.name;186187return child;188});189}190191/**192* A convenience method to read all children of a path that193* are directories.194*/195async function readDirsInDir(dirPath: string): Promise<string[]> {196const children = await readdir(dirPath);197const directories: string[] = [];198199for (const child of children) {200if (await SymlinkSupport.existsDirectory(join(dirPath, child))) {201directories.push(child);202}203}204205return directories;206}207208//#endregion209210//#region whenDeleted()211212/**213* A `Promise` that resolves when the provided `path`214* is deleted from disk.215*/216export function whenDeleted(path: string, intervalMs = 1000): Promise<void> {217return new Promise<void>(resolve => {218let running = false;219const interval = setInterval(() => {220if (!running) {221running = true;222fs.access(path, err => {223running = false;224225if (err) {226clearInterval(interval);227resolve(undefined);228}229});230}231}, intervalMs);232});233}234235//#endregion236237//#region Methods with symbolic links support238239export namespace SymlinkSupport {240241export interface IStats {242243// The stats of the file. If the file is a symbolic244// link, the stats will be of that target file and245// not the link itself.246// If the file is a symbolic link pointing to a non247// existing file, the stat will be of the link and248// the `dangling` flag will indicate this.249stat: fs.Stats;250251// Will be provided if the resource is a symbolic link252// on disk. Use the `dangling` flag to find out if it253// points to a resource that does not exist on disk.254symbolicLink?: { dangling: boolean };255}256257/**258* Resolves the `fs.Stats` of the provided path. If the path is a259* symbolic link, the `fs.Stats` will be from the target it points260* to. If the target does not exist, `dangling: true` will be returned261* as `symbolicLink` value.262*/263export async function stat(path: string): Promise<IStats> {264265// First stat the link266let lstats: fs.Stats | undefined;267try {268lstats = await fs.promises.lstat(path);269270// Return early if the stat is not a symbolic link at all271if (!lstats.isSymbolicLink()) {272return { stat: lstats };273}274} catch {275/* ignore - use stat() instead */276}277278// If the stat is a symbolic link or failed to stat, use fs.stat()279// which for symbolic links will stat the target they point to280try {281const stats = await fs.promises.stat(path);282283return { stat: stats, symbolicLink: lstats?.isSymbolicLink() ? { dangling: false } : undefined };284} catch (error) {285286// If the link points to a nonexistent file we still want287// to return it as result while setting dangling: true flag288if (error.code === 'ENOENT' && lstats) {289return { stat: lstats, symbolicLink: { dangling: true } };290}291292// Windows: workaround a node.js bug where reparse points293// are not supported (https://github.com/nodejs/node/issues/36790)294if (isWindows && error.code === 'EACCES') {295try {296const stats = await fs.promises.stat(await fs.promises.readlink(path));297298return { stat: stats, symbolicLink: { dangling: false } };299} catch (error) {300301// If the link points to a nonexistent file we still want302// to return it as result while setting dangling: true flag303if (error.code === 'ENOENT' && lstats) {304return { stat: lstats, symbolicLink: { dangling: true } };305}306307throw error;308}309}310311throw error;312}313}314315/**316* Figures out if the `path` exists and is a file with support317* for symlinks.318*319* Note: this will return `false` for a symlink that exists on320* disk but is dangling (pointing to a nonexistent path).321*322* Use `exists` if you only care about the path existing on disk323* or not without support for symbolic links.324*/325export async function existsFile(path: string): Promise<boolean> {326try {327const { stat, symbolicLink } = await SymlinkSupport.stat(path);328329return stat.isFile() && symbolicLink?.dangling !== true;330} catch {331// Ignore, path might not exist332}333334return false;335}336337/**338* Figures out if the `path` exists and is a directory with support for339* symlinks.340*341* Note: this will return `false` for a symlink that exists on342* disk but is dangling (pointing to a nonexistent path).343*344* Use `exists` if you only care about the path existing on disk345* or not without support for symbolic links.346*/347export async function existsDirectory(path: string): Promise<boolean> {348try {349const { stat, symbolicLink } = await SymlinkSupport.stat(path);350351return stat.isDirectory() && symbolicLink?.dangling !== true;352} catch {353// Ignore, path might not exist354}355356return false;357}358}359360//#endregion361362//#region Write File363364// According to node.js docs (https://nodejs.org/docs/v14.16.0/api/fs.html#fs_fs_writefile_file_data_options_callback)365// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.366// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.367const writeQueues = new ResourceQueue();368369/**370* Same as `fs.writeFile` but with an additional call to371* `fs.fdatasync` after writing to ensure changes are372* flushed to disk.373*374* In addition, multiple writes to the same path are queued.375*/376function writeFile(path: string, data: string, options?: IWriteFileOptions): Promise<void>;377function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Promise<void>;378function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise<void>;379function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void>;380function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void> {381return writeQueues.queueFor(URI.file(path), () => {382const ensuredOptions = ensureWriteOptions(options);383384return new Promise((resolve, reject) => doWriteFileAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve()));385}, extUriBiasedIgnorePathCase);386}387388interface IWriteFileOptions {389mode?: number;390flag?: string;391}392393interface IEnsuredWriteFileOptions extends IWriteFileOptions {394mode: number;395flag: string;396}397398let canFlush = true;399export function configureFlushOnWrite(enabled: boolean): void {400canFlush = enabled;401}402403// Calls fs.writeFile() followed by a fs.sync() call to flush the changes to disk404// We do this in cases where we want to make sure the data is really on disk and405// not in some cache.406//407// See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194408function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error: Error | null) => void): void {409if (!canFlush) {410return fs.writeFile(path, data, { mode: options.mode, flag: options.flag }, callback);411}412413// Open the file with same flags and mode as fs.writeFile()414fs.open(path, options.flag, options.mode, (openError, fd) => {415if (openError) {416return callback(openError);417}418419// It is valid to pass a fd handle to fs.writeFile() and this will keep the handle open!420fs.writeFile(fd, data, writeError => {421if (writeError) {422return fs.close(fd, () => callback(writeError)); // still need to close the handle on error!423}424425// Flush contents (not metadata) of the file to disk426// https://github.com/microsoft/vscode/issues/9589427fs.fdatasync(fd, (syncError: Error | null) => {428429// In some exotic setups it is well possible that node fails to sync430// In that case we disable flushing and warn to the console431if (syncError) {432console.warn('[node.js fs] fdatasync is now disabled for this session because it failed: ', syncError);433configureFlushOnWrite(false);434}435436return fs.close(fd, closeError => callback(closeError));437});438});439});440}441442/**443* Same as `fs.writeFileSync` but with an additional call to444* `fs.fdatasyncSync` after writing to ensure changes are445* flushed to disk.446*447* @deprecated always prefer async variants over sync!448*/449export function writeFileSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void {450const ensuredOptions = ensureWriteOptions(options);451452if (!canFlush) {453return fs.writeFileSync(path, data, { mode: ensuredOptions.mode, flag: ensuredOptions.flag });454}455456// Open the file with same flags and mode as fs.writeFile()457const fd = fs.openSync(path, ensuredOptions.flag, ensuredOptions.mode);458459try {460461// It is valid to pass a fd handle to fs.writeFile() and this will keep the handle open!462fs.writeFileSync(fd, data);463464// Flush contents (not metadata) of the file to disk465try {466fs.fdatasyncSync(fd); // https://github.com/microsoft/vscode/issues/9589467} catch (syncError) {468console.warn('[node.js fs] fdatasyncSync is now disabled for this session because it failed: ', syncError);469configureFlushOnWrite(false);470}471} finally {472fs.closeSync(fd);473}474}475476function ensureWriteOptions(options?: IWriteFileOptions): IEnsuredWriteFileOptions {477if (!options) {478return { mode: 0o666 /* default node.js mode for files */, flag: 'w' };479}480481return {482mode: typeof options.mode === 'number' ? options.mode : 0o666 /* default node.js mode for files */,483flag: typeof options.flag === 'string' ? options.flag : 'w'484};485}486487//#endregion488489//#region Move / Copy490491/**492* A drop-in replacement for `fs.rename` that:493* - allows to move across multiple disks494* - attempts to retry the operation for certain error codes on Windows495*/496async function rename(source: string, target: string, windowsRetryTimeout: number | false = 60000): Promise<void> {497if (source === target) {498return; // simulate node.js behaviour here and do a no-op if paths match499}500501try {502if (isWindows && typeof windowsRetryTimeout === 'number') {503// On Windows, a rename can fail when either source or target504// is locked by AV software.505await renameWithRetry(source, target, Date.now(), windowsRetryTimeout);506} else {507await fs.promises.rename(source, target);508}509} catch (error) {510// In two cases we fallback to classic copy and delete:511//512// 1.) The EXDEV error indicates that source and target are on different devices513// In this case, fallback to using a copy() operation as there is no way to514// rename() between different devices.515//516// 2.) The user tries to rename a file/folder that ends with a dot. This is not517// really possible to move then, at least on UNC devices.518if (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || source.endsWith('.')) {519await copy(source, target, { preserveSymlinks: false /* copying to another device */ });520await rimraf(source, RimRafMode.MOVE);521} else {522throw error;523}524}525}526527async function renameWithRetry(source: string, target: string, startTime: number, retryTimeout: number, attempt = 0): Promise<void> {528try {529return await fs.promises.rename(source, target);530} catch (error) {531if (error.code !== 'EACCES' && error.code !== 'EPERM' && error.code !== 'EBUSY') {532throw error; // only for errors we think are temporary533}534535if (Date.now() - startTime >= retryTimeout) {536console.error(`[node.js fs] rename failed after ${attempt} retries with error: ${error}`);537538throw error; // give up after configurable timeout539}540541if (attempt === 0) {542let abortRetry = false;543try {544const { stat } = await SymlinkSupport.stat(target);545if (!stat.isFile()) {546abortRetry = true; // if target is not a file, EPERM error may be raised and we should not attempt to retry547}548} catch {549// Ignore550}551552if (abortRetry) {553throw error;554}555}556557// Delay with incremental backoff up to 100ms558await timeout(Math.min(100, attempt * 10));559560// Attempt again561return renameWithRetry(source, target, startTime, retryTimeout, attempt + 1);562}563}564565interface ICopyPayload {566readonly root: { source: string; target: string };567readonly options: { preserveSymlinks: boolean };568readonly handledSourcePaths: Set<string>;569}570571/**572* Recursively copies all of `source` to `target`.573*574* The options `preserveSymlinks` configures how symbolic575* links should be handled when encountered. Set to576* `false` to not preserve them and `true` otherwise.577*/578async function copy(source: string, target: string, options: { preserveSymlinks: boolean }): Promise<void> {579return doCopy(source, target, { root: { source, target }, options, handledSourcePaths: new Set<string>() });580}581582// When copying a file or folder, we want to preserve the mode583// it had and as such provide it when creating. However, modes584// can go beyond what we expect (see link below), so we mask it.585// (https://github.com/nodejs/node-v0.x-archive/issues/3045#issuecomment-4862588)586const COPY_MODE_MASK = 0o777;587588async function doCopy(source: string, target: string, payload: ICopyPayload): Promise<void> {589590// Keep track of paths already copied to prevent591// cycles from symbolic links to cause issues592if (payload.handledSourcePaths.has(source)) {593return;594} else {595payload.handledSourcePaths.add(source);596}597598const { stat, symbolicLink } = await SymlinkSupport.stat(source);599600// Symlink601if (symbolicLink) {602603// Try to re-create the symlink unless `preserveSymlinks: false`604if (payload.options.preserveSymlinks) {605try {606return await doCopySymlink(source, target, payload);607} catch {608// in any case of an error fallback to normal copy via dereferencing609}610}611612if (symbolicLink.dangling) {613return; // skip dangling symbolic links from here on (https://github.com/microsoft/vscode/issues/111621)614}615}616617// Folder618if (stat.isDirectory()) {619return doCopyDirectory(source, target, stat.mode & COPY_MODE_MASK, payload);620}621622// File or file-like623else {624return doCopyFile(source, target, stat.mode & COPY_MODE_MASK);625}626}627628async function doCopyDirectory(source: string, target: string, mode: number, payload: ICopyPayload): Promise<void> {629630// Create folder631await fs.promises.mkdir(target, { recursive: true, mode });632633// Copy each file recursively634const files = await readdir(source);635for (const file of files) {636await doCopy(join(source, file), join(target, file), payload);637}638}639640async function doCopyFile(source: string, target: string, mode: number): Promise<void> {641642// Copy file643await fs.promises.copyFile(source, target);644645// restore mode (https://github.com/nodejs/node/issues/1104)646await fs.promises.chmod(target, mode);647}648649async function doCopySymlink(source: string, target: string, payload: ICopyPayload): Promise<void> {650651// Figure out link target652let linkTarget = await fs.promises.readlink(source);653654// Special case: the symlink points to a target that is655// actually within the path that is being copied. In that656// case we want the symlink to point to the target and657// not the source658if (isEqualOrParent(linkTarget, payload.root.source, !isLinux)) {659linkTarget = join(payload.root.target, linkTarget.substr(payload.root.source.length + 1));660}661662// Create symlink663await fs.promises.symlink(linkTarget, target);664}665666//#endregion667668//#region Path resolvers669670/**671* Given an absolute, normalized, and existing file path 'realcase' returns the672* exact path that the file has on disk.673* On a case insensitive file system, the returned path might differ from the original674* path by character casing.675* On a case sensitive file system, the returned path will always be identical to the676* original path.677* In case of errors, null is returned. But you cannot use this function to verify that678* a path exists.679*680* realcase does not handle '..' or '.' path segments and it does not take the locale into account.681*/682export async function realcase(path: string, token?: CancellationToken): Promise<string | null> {683if (isLinux) {684// This method is unsupported on OS that have case sensitive685// file system where the same path can exist in different forms686// (see also https://github.com/microsoft/vscode/issues/139709)687return path;688}689690const dir = dirname(path);691if (path === dir) { // end recursion692return path;693}694695const name = (basename(path) /* can be '' for windows drive letters */ || path).toLowerCase();696try {697if (token?.isCancellationRequested) {698return null;699}700701const entries = await Promises.readdir(dir);702const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search703if (found.length === 1) {704// on a case sensitive filesystem we cannot determine here, whether the file exists or not, hence we need the 'file exists' precondition705const prefix = await realcase(dir, token); // recurse706if (prefix) {707return join(prefix, found[0]);708}709} else if (found.length > 1) {710// must be a case sensitive $filesystem711const ix = found.indexOf(name);712if (ix >= 0) { // case sensitive713const prefix = await realcase(dir, token); // recurse714if (prefix) {715return join(prefix, found[ix]);716}717}718}719} catch {720// silently ignore error721}722723return null;724}725726async function realpath(path: string): Promise<string> {727try {728// DO NOT USE `fs.promises.realpath` here as it internally729// calls `fs.native.realpath` which will result in subst730// drives to be resolved to their target on Windows731// https://github.com/microsoft/vscode/issues/118562732return await promisify(fs.realpath)(path);733} catch {734735// We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization736// we now do a similar normalization and then try again if we can access the path with read737// permissions at least. If that succeeds, we return that path.738// fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is739// to not resolve links but to simply see if the path is read accessible or not.740const normalizedPath = normalizePath(path);741742await fs.promises.access(normalizedPath, fs.constants.R_OK);743744return normalizedPath;745}746}747748/**749* @deprecated always prefer async variants over sync!750*/751export function realpathSync(path: string): string {752try {753return fs.realpathSync(path);754} catch {755756// We hit an error calling fs.realpathSync(). Since fs.realpathSync() is doing some path normalization757// we now do a similar normalization and then try again if we can access the path with read758// permissions at least. If that succeeds, we return that path.759// fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is760// to not resolve links but to simply see if the path is read accessible or not.761const normalizedPath = normalizePath(path);762763fs.accessSync(normalizedPath, fs.constants.R_OK); // throws in case of an error764765return normalizedPath;766}767}768769function normalizePath(path: string): string {770return rtrim(normalize(path), sep);771}772773//#endregion774775//#region Promise based fs methods776777/**778* Some low level `fs` methods provided as `Promises` similar to779* `fs.promises` but with notable differences, either implemented780* by us or by restoring the original callback based behavior.781*782* At least `realpath` is implemented differently in the promise783* based implementation compared to the callback based one. The784* promise based implementation actually calls `fs.realpath.native`.785* (https://github.com/microsoft/vscode/issues/118562)786*/787export const Promises = new class {788789//#region Implemented by node.js790791get read() {792793// Not using `promisify` here for a reason: the return794// type is not an object as indicated by TypeScript but795// just the bytes read, so we create our own wrapper.796797return (fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null) => {798return new Promise<{ bytesRead: number; buffer: Uint8Array }>((resolve, reject) => {799fs.read(fd, buffer, offset, length, position, (err, bytesRead, buffer) => {800if (err) {801return reject(err);802}803804return resolve({ bytesRead, buffer });805});806});807};808}809810get write() {811812// Not using `promisify` here for a reason: the return813// type is not an object as indicated by TypeScript but814// just the bytes written, so we create our own wrapper.815816return (fd: number, buffer: Uint8Array, offset: number | undefined | null, length: number | undefined | null, position: number | undefined | null) => {817return new Promise<{ bytesWritten: number; buffer: Uint8Array }>((resolve, reject) => {818fs.write(fd, buffer, offset, length, position, (err, bytesWritten, buffer) => {819if (err) {820return reject(err);821}822823return resolve({ bytesWritten, buffer });824});825});826};827}828829get fdatasync() { return promisify(fs.fdatasync); } // not exposed as API in 22.x yet830831get open() { return promisify(fs.open); } // changed to return `FileHandle` in promise API832get close() { return promisify(fs.close); } // not exposed as API due to the `FileHandle` return type of `open`833834get ftruncate() { return promisify(fs.ftruncate); } // not exposed as API in 22.x yet835836//#endregion837838//#region Implemented by us839840async exists(path: string): Promise<boolean> {841try {842await fs.promises.access(path);843844return true;845} catch {846return false;847}848}849850get readdir() { return readdir; }851get readDirsInDir() { return readDirsInDir; }852853get writeFile() { return writeFile; }854855get rm() { return rimraf; }856857get rename() { return rename; }858get copy() { return copy; }859860get realpath() { return realpath; } // `fs.promises.realpath` will use `fs.realpath.native` which we do not want861862//#endregion863};864865//#endregion866867868