/*---------------------------------------------------------------------------------------------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(error => {/* 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// TODO@bpasero workaround for #252361 that should be removed110// once the upstream issue in node.js is resolved. Adds a trailing111// dot to a root drive letter path (G:\ => G:\.) as a workaround.112if (error.code === 'ENOENT' && isWindows && isRootOrDriveLetter(path)) {113try {114return await doReaddir(`${path}.`, options);115} catch (e) {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) {131console.warn('[node.js fs] readdir with filetypes failed with error: ', error);132}133134// Fallback to manually reading and resolving each135// children of the folder in case we hit an error136// previously.137// This can only really happen on exotic file systems138// such as explained in #115645 where we get entries139// from `readdir` that we can later not `lstat`.140const result: IDirent[] = [];141const children = await readdir(path);142for (const child of children) {143let isFile = false;144let isDirectory = false;145let isSymbolicLink = false;146147try {148const lstat = await fs.promises.lstat(join(path, child));149150isFile = lstat.isFile();151isDirectory = lstat.isDirectory();152isSymbolicLink = lstat.isSymbolicLink();153} catch (error) {154console.warn('[node.js fs] unexpected error from lstat after readdir: ', error);155}156157result.push({158name: child,159isFile: () => isFile,160isDirectory: () => isDirectory,161isSymbolicLink: () => isSymbolicLink162});163}164165return result;166}167168function handleDirectoryChildren(children: string[]): string[];169function handleDirectoryChildren(children: IDirent[]): IDirent[];170function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[];171function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[] {172return children.map(child => {173174// Mac: uses NFD unicode form on disk, but we want NFC175// See also https://github.com/nodejs/node/issues/2165176177if (typeof child === 'string') {178return isMacintosh ? normalizeNFC(child) : child;179}180181child.name = isMacintosh ? normalizeNFC(child.name) : child.name;182183return child;184});185}186187/**188* A convenience method to read all children of a path that189* are directories.190*/191async function readDirsInDir(dirPath: string): Promise<string[]> {192const children = await readdir(dirPath);193const directories: string[] = [];194195for (const child of children) {196if (await SymlinkSupport.existsDirectory(join(dirPath, child))) {197directories.push(child);198}199}200201return directories;202}203204//#endregion205206//#region whenDeleted()207208/**209* A `Promise` that resolves when the provided `path`210* is deleted from disk.211*/212export function whenDeleted(path: string, intervalMs = 1000): Promise<void> {213return new Promise<void>(resolve => {214let running = false;215const interval = setInterval(() => {216if (!running) {217running = true;218fs.access(path, err => {219running = false;220221if (err) {222clearInterval(interval);223resolve(undefined);224}225});226}227}, intervalMs);228});229}230231//#endregion232233//#region Methods with symbolic links support234235export namespace SymlinkSupport {236237export interface IStats {238239// The stats of the file. If the file is a symbolic240// link, the stats will be of that target file and241// not the link itself.242// If the file is a symbolic link pointing to a non243// existing file, the stat will be of the link and244// the `dangling` flag will indicate this.245stat: fs.Stats;246247// Will be provided if the resource is a symbolic link248// on disk. Use the `dangling` flag to find out if it249// points to a resource that does not exist on disk.250symbolicLink?: { dangling: boolean };251}252253/**254* Resolves the `fs.Stats` of the provided path. If the path is a255* symbolic link, the `fs.Stats` will be from the target it points256* to. If the target does not exist, `dangling: true` will be returned257* as `symbolicLink` value.258*/259export async function stat(path: string): Promise<IStats> {260261// First stat the link262let lstats: fs.Stats | undefined;263try {264lstats = await fs.promises.lstat(path);265266// Return early if the stat is not a symbolic link at all267if (!lstats.isSymbolicLink()) {268return { stat: lstats };269}270} catch (error) {271/* ignore - use stat() instead */272}273274// If the stat is a symbolic link or failed to stat, use fs.stat()275// which for symbolic links will stat the target they point to276try {277const stats = await fs.promises.stat(path);278279return { stat: stats, symbolicLink: lstats?.isSymbolicLink() ? { dangling: false } : undefined };280} catch (error) {281282// If the link points to a nonexistent file we still want283// to return it as result while setting dangling: true flag284if (error.code === 'ENOENT' && lstats) {285return { stat: lstats, symbolicLink: { dangling: true } };286}287288// Windows: workaround a node.js bug where reparse points289// are not supported (https://github.com/nodejs/node/issues/36790)290if (isWindows && error.code === 'EACCES') {291try {292const stats = await fs.promises.stat(await fs.promises.readlink(path));293294return { stat: stats, symbolicLink: { dangling: false } };295} catch (error) {296297// If the link points to a nonexistent file we still want298// to return it as result while setting dangling: true flag299if (error.code === 'ENOENT' && lstats) {300return { stat: lstats, symbolicLink: { dangling: true } };301}302303throw error;304}305}306307throw error;308}309}310311/**312* Figures out if the `path` exists and is a file with support313* for symlinks.314*315* Note: this will return `false` for a symlink that exists on316* disk but is dangling (pointing to a nonexistent path).317*318* Use `exists` if you only care about the path existing on disk319* or not without support for symbolic links.320*/321export async function existsFile(path: string): Promise<boolean> {322try {323const { stat, symbolicLink } = await SymlinkSupport.stat(path);324325return stat.isFile() && symbolicLink?.dangling !== true;326} catch (error) {327// Ignore, path might not exist328}329330return false;331}332333/**334* Figures out if the `path` exists and is a directory with support for335* symlinks.336*337* Note: this will return `false` for a symlink that exists on338* disk but is dangling (pointing to a nonexistent path).339*340* Use `exists` if you only care about the path existing on disk341* or not without support for symbolic links.342*/343export async function existsDirectory(path: string): Promise<boolean> {344try {345const { stat, symbolicLink } = await SymlinkSupport.stat(path);346347return stat.isDirectory() && symbolicLink?.dangling !== true;348} catch (error) {349// Ignore, path might not exist350}351352return false;353}354}355356//#endregion357358//#region Write File359360// According to node.js docs (https://nodejs.org/docs/v14.16.0/api/fs.html#fs_fs_writefile_file_data_options_callback)361// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.362// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.363const writeQueues = new ResourceQueue();364365/**366* Same as `fs.writeFile` but with an additional call to367* `fs.fdatasync` after writing to ensure changes are368* flushed to disk.369*370* In addition, multiple writes to the same path are queued.371*/372function writeFile(path: string, data: string, options?: IWriteFileOptions): Promise<void>;373function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Promise<void>;374function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise<void>;375function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void>;376function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void> {377return writeQueues.queueFor(URI.file(path), () => {378const ensuredOptions = ensureWriteOptions(options);379380return new Promise((resolve, reject) => doWriteFileAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve()));381}, extUriBiasedIgnorePathCase);382}383384interface IWriteFileOptions {385mode?: number;386flag?: string;387}388389interface IEnsuredWriteFileOptions extends IWriteFileOptions {390mode: number;391flag: string;392}393394let canFlush = true;395export function configureFlushOnWrite(enabled: boolean): void {396canFlush = enabled;397}398399// Calls fs.writeFile() followed by a fs.sync() call to flush the changes to disk400// We do this in cases where we want to make sure the data is really on disk and401// not in some cache.402//403// See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194404function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error: Error | null) => void): void {405if (!canFlush) {406return fs.writeFile(path, data, { mode: options.mode, flag: options.flag }, callback);407}408409// Open the file with same flags and mode as fs.writeFile()410fs.open(path, options.flag, options.mode, (openError, fd) => {411if (openError) {412return callback(openError);413}414415// It is valid to pass a fd handle to fs.writeFile() and this will keep the handle open!416fs.writeFile(fd, data, writeError => {417if (writeError) {418return fs.close(fd, () => callback(writeError)); // still need to close the handle on error!419}420421// Flush contents (not metadata) of the file to disk422// https://github.com/microsoft/vscode/issues/9589423fs.fdatasync(fd, (syncError: Error | null) => {424425// In some exotic setups it is well possible that node fails to sync426// In that case we disable flushing and warn to the console427if (syncError) {428console.warn('[node.js fs] fdatasync is now disabled for this session because it failed: ', syncError);429configureFlushOnWrite(false);430}431432return fs.close(fd, closeError => callback(closeError));433});434});435});436}437438/**439* Same as `fs.writeFileSync` but with an additional call to440* `fs.fdatasyncSync` after writing to ensure changes are441* flushed to disk.442*443* @deprecated always prefer async variants over sync!444*/445export function writeFileSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void {446const ensuredOptions = ensureWriteOptions(options);447448if (!canFlush) {449return fs.writeFileSync(path, data, { mode: ensuredOptions.mode, flag: ensuredOptions.flag });450}451452// Open the file with same flags and mode as fs.writeFile()453const fd = fs.openSync(path, ensuredOptions.flag, ensuredOptions.mode);454455try {456457// It is valid to pass a fd handle to fs.writeFile() and this will keep the handle open!458fs.writeFileSync(fd, data);459460// Flush contents (not metadata) of the file to disk461try {462fs.fdatasyncSync(fd); // https://github.com/microsoft/vscode/issues/9589463} catch (syncError) {464console.warn('[node.js fs] fdatasyncSync is now disabled for this session because it failed: ', syncError);465configureFlushOnWrite(false);466}467} finally {468fs.closeSync(fd);469}470}471472function ensureWriteOptions(options?: IWriteFileOptions): IEnsuredWriteFileOptions {473if (!options) {474return { mode: 0o666 /* default node.js mode for files */, flag: 'w' };475}476477return {478mode: typeof options.mode === 'number' ? options.mode : 0o666 /* default node.js mode for files */,479flag: typeof options.flag === 'string' ? options.flag : 'w'480};481}482483//#endregion484485//#region Move / Copy486487/**488* A drop-in replacement for `fs.rename` that:489* - allows to move across multiple disks490* - attempts to retry the operation for certain error codes on Windows491*/492async function rename(source: string, target: string, windowsRetryTimeout: number | false = 60000): Promise<void> {493if (source === target) {494return; // simulate node.js behaviour here and do a no-op if paths match495}496497try {498if (isWindows && typeof windowsRetryTimeout === 'number') {499// On Windows, a rename can fail when either source or target500// is locked by AV software.501await renameWithRetry(source, target, Date.now(), windowsRetryTimeout);502} else {503await fs.promises.rename(source, target);504}505} catch (error) {506// In two cases we fallback to classic copy and delete:507//508// 1.) The EXDEV error indicates that source and target are on different devices509// In this case, fallback to using a copy() operation as there is no way to510// rename() between different devices.511//512// 2.) The user tries to rename a file/folder that ends with a dot. This is not513// really possible to move then, at least on UNC devices.514if (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || source.endsWith('.')) {515await copy(source, target, { preserveSymlinks: false /* copying to another device */ });516await rimraf(source, RimRafMode.MOVE);517} else {518throw error;519}520}521}522523async function renameWithRetry(source: string, target: string, startTime: number, retryTimeout: number, attempt = 0): Promise<void> {524try {525return await fs.promises.rename(source, target);526} catch (error) {527if (error.code !== 'EACCES' && error.code !== 'EPERM' && error.code !== 'EBUSY') {528throw error; // only for errors we think are temporary529}530531if (Date.now() - startTime >= retryTimeout) {532console.error(`[node.js fs] rename failed after ${attempt} retries with error: ${error}`);533534throw error; // give up after configurable timeout535}536537if (attempt === 0) {538let abortRetry = false;539try {540const { stat } = await SymlinkSupport.stat(target);541if (!stat.isFile()) {542abortRetry = true; // if target is not a file, EPERM error may be raised and we should not attempt to retry543}544} catch (error) {545// Ignore546}547548if (abortRetry) {549throw error;550}551}552553// Delay with incremental backoff up to 100ms554await timeout(Math.min(100, attempt * 10));555556// Attempt again557return renameWithRetry(source, target, startTime, retryTimeout, attempt + 1);558}559}560561interface ICopyPayload {562readonly root: { source: string; target: string };563readonly options: { preserveSymlinks: boolean };564readonly handledSourcePaths: Set<string>;565}566567/**568* Recursively copies all of `source` to `target`.569*570* The options `preserveSymlinks` configures how symbolic571* links should be handled when encountered. Set to572* `false` to not preserve them and `true` otherwise.573*/574async function copy(source: string, target: string, options: { preserveSymlinks: boolean }): Promise<void> {575return doCopy(source, target, { root: { source, target }, options, handledSourcePaths: new Set<string>() });576}577578// When copying a file or folder, we want to preserve the mode579// it had and as such provide it when creating. However, modes580// can go beyond what we expect (see link below), so we mask it.581// (https://github.com/nodejs/node-v0.x-archive/issues/3045#issuecomment-4862588)582const COPY_MODE_MASK = 0o777;583584async function doCopy(source: string, target: string, payload: ICopyPayload): Promise<void> {585586// Keep track of paths already copied to prevent587// cycles from symbolic links to cause issues588if (payload.handledSourcePaths.has(source)) {589return;590} else {591payload.handledSourcePaths.add(source);592}593594const { stat, symbolicLink } = await SymlinkSupport.stat(source);595596// Symlink597if (symbolicLink) {598599// Try to re-create the symlink unless `preserveSymlinks: false`600if (payload.options.preserveSymlinks) {601try {602return await doCopySymlink(source, target, payload);603} catch (error) {604// in any case of an error fallback to normal copy via dereferencing605}606}607608if (symbolicLink.dangling) {609return; // skip dangling symbolic links from here on (https://github.com/microsoft/vscode/issues/111621)610}611}612613// Folder614if (stat.isDirectory()) {615return doCopyDirectory(source, target, stat.mode & COPY_MODE_MASK, payload);616}617618// File or file-like619else {620return doCopyFile(source, target, stat.mode & COPY_MODE_MASK);621}622}623624async function doCopyDirectory(source: string, target: string, mode: number, payload: ICopyPayload): Promise<void> {625626// Create folder627await fs.promises.mkdir(target, { recursive: true, mode });628629// Copy each file recursively630const files = await readdir(source);631for (const file of files) {632await doCopy(join(source, file), join(target, file), payload);633}634}635636async function doCopyFile(source: string, target: string, mode: number): Promise<void> {637638// Copy file639await fs.promises.copyFile(source, target);640641// restore mode (https://github.com/nodejs/node/issues/1104)642await fs.promises.chmod(target, mode);643}644645async function doCopySymlink(source: string, target: string, payload: ICopyPayload): Promise<void> {646647// Figure out link target648let linkTarget = await fs.promises.readlink(source);649650// Special case: the symlink points to a target that is651// actually within the path that is being copied. In that652// case we want the symlink to point to the target and653// not the source654if (isEqualOrParent(linkTarget, payload.root.source, !isLinux)) {655linkTarget = join(payload.root.target, linkTarget.substr(payload.root.source.length + 1));656}657658// Create symlink659await fs.promises.symlink(linkTarget, target);660}661662//#endregion663664//#region Path resolvers665666/**667* Given an absolute, normalized, and existing file path 'realcase' returns the668* exact path that the file has on disk.669* On a case insensitive file system, the returned path might differ from the original670* path by character casing.671* On a case sensitive file system, the returned path will always be identical to the672* original path.673* In case of errors, null is returned. But you cannot use this function to verify that674* a path exists.675*676* realcase does not handle '..' or '.' path segments and it does not take the locale into account.677*/678export async function realcase(path: string, token?: CancellationToken): Promise<string | null> {679if (isLinux) {680// This method is unsupported on OS that have case sensitive681// file system where the same path can exist in different forms682// (see also https://github.com/microsoft/vscode/issues/139709)683return path;684}685686const dir = dirname(path);687if (path === dir) { // end recursion688return path;689}690691const name = (basename(path) /* can be '' for windows drive letters */ || path).toLowerCase();692try {693if (token?.isCancellationRequested) {694return null;695}696697const entries = await Promises.readdir(dir);698const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search699if (found.length === 1) {700// on a case sensitive filesystem we cannot determine here, whether the file exists or not, hence we need the 'file exists' precondition701const prefix = await realcase(dir, token); // recurse702if (prefix) {703return join(prefix, found[0]);704}705} else if (found.length > 1) {706// must be a case sensitive $filesystem707const ix = found.indexOf(name);708if (ix >= 0) { // case sensitive709const prefix = await realcase(dir, token); // recurse710if (prefix) {711return join(prefix, found[ix]);712}713}714}715} catch (error) {716// silently ignore error717}718719return null;720}721722async function realpath(path: string): Promise<string> {723try {724// DO NOT USE `fs.promises.realpath` here as it internally725// calls `fs.native.realpath` which will result in subst726// drives to be resolved to their target on Windows727// https://github.com/microsoft/vscode/issues/118562728return await promisify(fs.realpath)(path);729} catch (error) {730731// We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization732// we now do a similar normalization and then try again if we can access the path with read733// permissions at least. If that succeeds, we return that path.734// fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is735// to not resolve links but to simply see if the path is read accessible or not.736const normalizedPath = normalizePath(path);737738await fs.promises.access(normalizedPath, fs.constants.R_OK);739740return normalizedPath;741}742}743744/**745* @deprecated always prefer async variants over sync!746*/747export function realpathSync(path: string): string {748try {749return fs.realpathSync(path);750} catch (error) {751752// We hit an error calling fs.realpathSync(). Since fs.realpathSync() is doing some path normalization753// we now do a similar normalization and then try again if we can access the path with read754// permissions at least. If that succeeds, we return that path.755// fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is756// to not resolve links but to simply see if the path is read accessible or not.757const normalizedPath = normalizePath(path);758759fs.accessSync(normalizedPath, fs.constants.R_OK); // throws in case of an error760761return normalizedPath;762}763}764765function normalizePath(path: string): string {766return rtrim(normalize(path), sep);767}768769//#endregion770771//#region Promise based fs methods772773/**774* Some low level `fs` methods provided as `Promises` similar to775* `fs.promises` but with notable differences, either implemented776* by us or by restoring the original callback based behavior.777*778* At least `realpath` is implemented differently in the promise779* based implementation compared to the callback based one. The780* promise based implementation actually calls `fs.realpath.native`.781* (https://github.com/microsoft/vscode/issues/118562)782*/783export const Promises = new class {784785//#region Implemented by node.js786787get read() {788789// Not using `promisify` here for a reason: the return790// type is not an object as indicated by TypeScript but791// just the bytes read, so we create our own wrapper.792793return (fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null) => {794return new Promise<{ bytesRead: number; buffer: Uint8Array }>((resolve, reject) => {795fs.read(fd, buffer, offset, length, position, (err, bytesRead, buffer) => {796if (err) {797return reject(err);798}799800return resolve({ bytesRead, buffer });801});802});803};804}805806get write() {807808// Not using `promisify` here for a reason: the return809// type is not an object as indicated by TypeScript but810// just the bytes written, so we create our own wrapper.811812return (fd: number, buffer: Uint8Array, offset: number | undefined | null, length: number | undefined | null, position: number | undefined | null) => {813return new Promise<{ bytesWritten: number; buffer: Uint8Array }>((resolve, reject) => {814fs.write(fd, buffer, offset, length, position, (err, bytesWritten, buffer) => {815if (err) {816return reject(err);817}818819return resolve({ bytesWritten, buffer });820});821});822};823}824825get fdatasync() { return promisify(fs.fdatasync); } // not exposed as API in 22.x yet826827get open() { return promisify(fs.open); } // changed to return `FileHandle` in promise API828get close() { return promisify(fs.close); } // not exposed as API due to the `FileHandle` return type of `open`829830get ftruncate() { return promisify(fs.ftruncate); } // not exposed as API in 22.x yet831832//#endregion833834//#region Implemented by us835836async exists(path: string): Promise<boolean> {837try {838await fs.promises.access(path);839840return true;841} catch {842return false;843}844}845846get readdir() { return readdir; }847get readDirsInDir() { return readDirsInDir; }848849get writeFile() { return writeFile; }850851get rm() { return rimraf; }852853get rename() { return rename; }854get copy() { return copy; }855856get realpath() { return realpath; } // `fs.promises.realpath` will use `fs.realpath.native` which we do not want857858//#endregion859};860861//#endregion862863864