1099 lines
47 KiB
JavaScript
1099 lines
47 KiB
JavaScript
|
"use strict";
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||
|
// See LICENSE in the project root for license information.
|
||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||
|
if (k2 === undefined) k2 = k;
|
||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||
|
}
|
||
|
Object.defineProperty(o, k2, desc);
|
||
|
}) : (function(o, m, k, k2) {
|
||
|
if (k2 === undefined) k2 = k;
|
||
|
o[k2] = m[k];
|
||
|
}));
|
||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||
|
}) : function(o, v) {
|
||
|
o["default"] = v;
|
||
|
});
|
||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||
|
if (mod && mod.__esModule) return mod;
|
||
|
var result = {};
|
||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||
|
__setModuleDefault(result, mod);
|
||
|
return result;
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.FileSystem = exports.AlreadyExistsBehavior = void 0;
|
||
|
const nodeJsPath = __importStar(require("path"));
|
||
|
const fs = __importStar(require("fs"));
|
||
|
const fsx = __importStar(require("fs-extra"));
|
||
|
const Text_1 = require("./Text");
|
||
|
const PosixModeBits_1 = require("./PosixModeBits");
|
||
|
const LegacyAdapters_1 = require("./LegacyAdapters");
|
||
|
/**
|
||
|
* Specifies the behavior of APIs such as {@link FileSystem.copyFile} or
|
||
|
* {@link FileSystem.createSymbolicLinkFile} when the output file path already exists.
|
||
|
*
|
||
|
* @remarks
|
||
|
* For {@link FileSystem.copyFile} and related APIs, the "output file path" is
|
||
|
* {@link IFileSystemCopyFileOptions.destinationPath}.
|
||
|
*
|
||
|
* For {@link FileSystem.createSymbolicLinkFile} and related APIs, the "output file path" is
|
||
|
* {@link IFileSystemCreateLinkOptions.newLinkPath}.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
var AlreadyExistsBehavior;
|
||
|
(function (AlreadyExistsBehavior) {
|
||
|
/**
|
||
|
* If the output file path already exists, try to overwrite the existing object.
|
||
|
*
|
||
|
* @remarks
|
||
|
* If overwriting the object would require recursively deleting a folder tree,
|
||
|
* then the operation will fail. As an example, suppose {@link FileSystem.copyFile}
|
||
|
* is copying a single file `/a/b/c` to the destination path `/d/e`, and `/d/e` is a
|
||
|
* nonempty folder. In this situation, an error will be reported; specifying
|
||
|
* `AlreadyExistsBehavior.Overwrite` does not help. Empty folders can be overwritten
|
||
|
* depending on the details of the implementation.
|
||
|
*/
|
||
|
AlreadyExistsBehavior["Overwrite"] = "overwrite";
|
||
|
/**
|
||
|
* If the output file path already exists, the operation will fail, and an error
|
||
|
* will be reported.
|
||
|
*/
|
||
|
AlreadyExistsBehavior["Error"] = "error";
|
||
|
/**
|
||
|
* If the output file path already exists, skip this item, and continue the operation.
|
||
|
*/
|
||
|
AlreadyExistsBehavior["Ignore"] = "ignore";
|
||
|
})(AlreadyExistsBehavior = exports.AlreadyExistsBehavior || (exports.AlreadyExistsBehavior = {}));
|
||
|
const MOVE_DEFAULT_OPTIONS = {
|
||
|
overwrite: true,
|
||
|
ensureFolderExists: false
|
||
|
};
|
||
|
const READ_FOLDER_DEFAULT_OPTIONS = {
|
||
|
absolutePaths: false
|
||
|
};
|
||
|
const WRITE_FILE_DEFAULT_OPTIONS = {
|
||
|
ensureFolderExists: false,
|
||
|
convertLineEndings: undefined,
|
||
|
encoding: Text_1.Encoding.Utf8
|
||
|
};
|
||
|
const APPEND_TO_FILE_DEFAULT_OPTIONS = Object.assign({}, WRITE_FILE_DEFAULT_OPTIONS);
|
||
|
const READ_FILE_DEFAULT_OPTIONS = {
|
||
|
encoding: Text_1.Encoding.Utf8,
|
||
|
convertLineEndings: undefined
|
||
|
};
|
||
|
const COPY_FILE_DEFAULT_OPTIONS = {
|
||
|
alreadyExistsBehavior: AlreadyExistsBehavior.Overwrite
|
||
|
};
|
||
|
const COPY_FILES_DEFAULT_OPTIONS = {
|
||
|
alreadyExistsBehavior: AlreadyExistsBehavior.Overwrite
|
||
|
};
|
||
|
const DELETE_FILE_DEFAULT_OPTIONS = {
|
||
|
throwIfNotExists: false
|
||
|
};
|
||
|
/**
|
||
|
* The FileSystem API provides a complete set of recommended operations for interacting with the file system.
|
||
|
*
|
||
|
* @remarks
|
||
|
* We recommend to use this instead of the native `fs` API, because `fs` is a minimal set of low-level
|
||
|
* primitives that must be mapped for each supported operating system. The FileSystem API takes a
|
||
|
* philosophical approach of providing "one obvious way" to do each operation. We also prefer synchronous
|
||
|
* operations except in cases where there would be a clear performance benefit for using async, since synchronous
|
||
|
* code is much easier to read and debug. Also, indiscriminate parallelism has been seen to actually worsen
|
||
|
* performance, versus improving it.
|
||
|
*
|
||
|
* Note that in the documentation, we refer to "filesystem objects", this can be a
|
||
|
* file, folder, symbolic link, hard link, directory junction, etc.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
class FileSystem {
|
||
|
// ===============
|
||
|
// COMMON OPERATIONS
|
||
|
// ===============
|
||
|
/**
|
||
|
* Returns true if the path exists on disk.
|
||
|
* Behind the scenes it uses `fs.existsSync()`.
|
||
|
* @remarks
|
||
|
* There is a debate about the fact that after `fs.existsSync()` returns true,
|
||
|
* the file might be deleted before fs.readSync() is called, which would imply that everybody
|
||
|
* should catch a `readSync()` exception, and nobody should ever use `fs.existsSync()`.
|
||
|
* We find this to be unpersuasive, since "unexceptional exceptions" really hinder the
|
||
|
* break-on-exception debugging experience. Also, throwing/catching is generally slow.
|
||
|
* @param path - The absolute or relative path to the filesystem object.
|
||
|
*/
|
||
|
static exists(path) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
return fsx.existsSync(path);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.exists}.
|
||
|
*/
|
||
|
static async existsAsync(path) {
|
||
|
return await FileSystem._wrapExceptionAsync(() => {
|
||
|
return new Promise((resolve) => {
|
||
|
fsx.exists(path, resolve);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Gets the statistics for a particular filesystem object.
|
||
|
* If the path is a link, this function follows the link and returns statistics about the link target.
|
||
|
* Behind the scenes it uses `fs.statSync()`.
|
||
|
* @param path - The absolute or relative path to the filesystem object.
|
||
|
*/
|
||
|
static getStatistics(path) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
return fsx.statSync(path);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.getStatistics}.
|
||
|
*/
|
||
|
static async getStatisticsAsync(path) {
|
||
|
return await FileSystem._wrapExceptionAsync(() => {
|
||
|
return fsx.stat(path);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Updates the accessed and modified timestamps of the filesystem object referenced by path.
|
||
|
* Behind the scenes it uses `fs.utimesSync()`.
|
||
|
* The caller should specify both times in the `times` parameter.
|
||
|
* @param path - The path of the file that should be modified.
|
||
|
* @param times - The times that the object should be updated to reflect.
|
||
|
*/
|
||
|
static updateTimes(path, times) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
fsx.utimesSync(path, times.accessedTime, times.modifiedTime);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.updateTimes}.
|
||
|
*/
|
||
|
static async updateTimesAsync(path, times) {
|
||
|
await FileSystem._wrapExceptionAsync(() => {
|
||
|
// This cast is needed because the fs-extra typings require both parameters
|
||
|
// to have the same type (number or Date), whereas Node.js does not require that.
|
||
|
return fsx.utimes(path, times.accessedTime, times.modifiedTime);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Changes the permissions (i.e. file mode bits) for a filesystem object.
|
||
|
* Behind the scenes it uses `fs.chmodSync()`.
|
||
|
* @param path - The absolute or relative path to the object that should be updated.
|
||
|
* @param modeBits - POSIX-style file mode bits specified using the {@link PosixModeBits} enum
|
||
|
*/
|
||
|
static changePosixModeBits(path, modeBits) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
fs.chmodSync(path, modeBits);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.changePosixModeBits}.
|
||
|
*/
|
||
|
static async changePosixModeBitsAsync(path, mode) {
|
||
|
await FileSystem._wrapExceptionAsync(() => {
|
||
|
return fsx.chmod(path, mode);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Retrieves the permissions (i.e. file mode bits) for a filesystem object.
|
||
|
* Behind the scenes it uses `fs.chmodSync()`.
|
||
|
* @param path - The absolute or relative path to the object that should be updated.
|
||
|
*
|
||
|
* @remarks
|
||
|
* This calls {@link FileSystem.getStatistics} to get the POSIX mode bits.
|
||
|
* If statistics in addition to the mode bits are needed, it is more efficient
|
||
|
* to call {@link FileSystem.getStatistics} directly instead.
|
||
|
*/
|
||
|
static getPosixModeBits(path) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
return FileSystem.getStatistics(path).mode;
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.getPosixModeBits}.
|
||
|
*/
|
||
|
static async getPosixModeBitsAsync(path) {
|
||
|
return await FileSystem._wrapExceptionAsync(async () => {
|
||
|
return (await FileSystem.getStatisticsAsync(path)).mode;
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Returns a 10-character string representation of a PosixModeBits value similar to what
|
||
|
* would be displayed by a command such as "ls -l" on a POSIX-like operating system.
|
||
|
* @remarks
|
||
|
* For example, `PosixModeBits.AllRead | PosixModeBits.AllWrite` would be formatted as "-rw-rw-rw-".
|
||
|
* @param modeBits - POSIX-style file mode bits specified using the {@link PosixModeBits} enum
|
||
|
*/
|
||
|
static formatPosixModeBits(modeBits) {
|
||
|
let result = '-'; // (later we may add support for additional states such as S_IFDIR or S_ISUID)
|
||
|
result += modeBits & PosixModeBits_1.PosixModeBits.UserRead ? 'r' : '-';
|
||
|
result += modeBits & PosixModeBits_1.PosixModeBits.UserWrite ? 'w' : '-';
|
||
|
result += modeBits & PosixModeBits_1.PosixModeBits.UserExecute ? 'x' : '-';
|
||
|
result += modeBits & PosixModeBits_1.PosixModeBits.GroupRead ? 'r' : '-';
|
||
|
result += modeBits & PosixModeBits_1.PosixModeBits.GroupWrite ? 'w' : '-';
|
||
|
result += modeBits & PosixModeBits_1.PosixModeBits.GroupExecute ? 'x' : '-';
|
||
|
result += modeBits & PosixModeBits_1.PosixModeBits.OthersRead ? 'r' : '-';
|
||
|
result += modeBits & PosixModeBits_1.PosixModeBits.OthersWrite ? 'w' : '-';
|
||
|
result += modeBits & PosixModeBits_1.PosixModeBits.OthersExecute ? 'x' : '-';
|
||
|
return result;
|
||
|
}
|
||
|
/**
|
||
|
* Moves a file. The folder must exist, unless the `ensureFolderExists` option is provided.
|
||
|
* Behind the scenes it uses `fs-extra.moveSync()`
|
||
|
*/
|
||
|
static move(options) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
options = Object.assign(Object.assign({}, MOVE_DEFAULT_OPTIONS), options);
|
||
|
try {
|
||
|
fsx.moveSync(options.sourcePath, options.destinationPath, { overwrite: options.overwrite });
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (options.ensureFolderExists) {
|
||
|
if (!FileSystem.isNotExistError(error)) {
|
||
|
throw error;
|
||
|
}
|
||
|
const folderPath = nodeJsPath.dirname(options.destinationPath);
|
||
|
FileSystem.ensureFolder(folderPath);
|
||
|
fsx.moveSync(options.sourcePath, options.destinationPath, { overwrite: options.overwrite });
|
||
|
}
|
||
|
else {
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.move}.
|
||
|
*/
|
||
|
static async moveAsync(options) {
|
||
|
await FileSystem._wrapExceptionAsync(async () => {
|
||
|
options = Object.assign(Object.assign({}, MOVE_DEFAULT_OPTIONS), options);
|
||
|
try {
|
||
|
await fsx.move(options.sourcePath, options.destinationPath, { overwrite: options.overwrite });
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (options.ensureFolderExists) {
|
||
|
if (!FileSystem.isNotExistError(error)) {
|
||
|
throw error;
|
||
|
}
|
||
|
const folderPath = nodeJsPath.dirname(options.destinationPath);
|
||
|
await FileSystem.ensureFolderAsync(nodeJsPath.dirname(folderPath));
|
||
|
await fsx.move(options.sourcePath, options.destinationPath, { overwrite: options.overwrite });
|
||
|
}
|
||
|
else {
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
// ===============
|
||
|
// FOLDER OPERATIONS
|
||
|
// ===============
|
||
|
/**
|
||
|
* Recursively creates a folder at a given path.
|
||
|
* Behind the scenes is uses `fs-extra.ensureDirSync()`.
|
||
|
* @remarks
|
||
|
* Throws an exception if anything in the folderPath is not a folder.
|
||
|
* @param folderPath - The absolute or relative path of the folder which should be created.
|
||
|
*/
|
||
|
static ensureFolder(folderPath) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
fsx.ensureDirSync(folderPath);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.ensureFolder}.
|
||
|
*/
|
||
|
static async ensureFolderAsync(folderPath) {
|
||
|
await FileSystem._wrapExceptionAsync(() => {
|
||
|
return fsx.ensureDir(folderPath);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* @deprecated
|
||
|
* Use {@link FileSystem.readFolderItemNames} instead.
|
||
|
*/
|
||
|
static readFolder(folderPath, options) {
|
||
|
return FileSystem.readFolderItemNames(folderPath, options);
|
||
|
}
|
||
|
/**
|
||
|
* @deprecated
|
||
|
* Use {@link FileSystem.readFolderItemNamesAsync} instead.
|
||
|
*/
|
||
|
static async readFolderAsync(folderPath, options) {
|
||
|
return await FileSystem.readFolderItemNamesAsync(folderPath, options);
|
||
|
}
|
||
|
/**
|
||
|
* Reads the names of folder entries, not including "." or "..".
|
||
|
* Behind the scenes it uses `fs.readdirSync()`.
|
||
|
* @param folderPath - The absolute or relative path to the folder which should be read.
|
||
|
* @param options - Optional settings that can change the behavior. Type: `IReadFolderOptions`
|
||
|
*/
|
||
|
static readFolderItemNames(folderPath, options) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
options = Object.assign(Object.assign({}, READ_FOLDER_DEFAULT_OPTIONS), options);
|
||
|
const fileNames = fsx.readdirSync(folderPath);
|
||
|
if (options.absolutePaths) {
|
||
|
return fileNames.map((fileName) => nodeJsPath.resolve(folderPath, fileName));
|
||
|
}
|
||
|
else {
|
||
|
return fileNames;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.readFolderItemNames}.
|
||
|
*/
|
||
|
static async readFolderItemNamesAsync(folderPath, options) {
|
||
|
return await FileSystem._wrapExceptionAsync(async () => {
|
||
|
options = Object.assign(Object.assign({}, READ_FOLDER_DEFAULT_OPTIONS), options);
|
||
|
const fileNames = await fsx.readdir(folderPath);
|
||
|
if (options.absolutePaths) {
|
||
|
return fileNames.map((fileName) => nodeJsPath.resolve(folderPath, fileName));
|
||
|
}
|
||
|
else {
|
||
|
return fileNames;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Reads the contents of the folder, not including "." or "..", returning objects including the
|
||
|
* entry names and types.
|
||
|
* Behind the scenes it uses `fs.readdirSync()`.
|
||
|
* @param folderPath - The absolute or relative path to the folder which should be read.
|
||
|
* @param options - Optional settings that can change the behavior. Type: `IReadFolderOptions`
|
||
|
*/
|
||
|
static readFolderItems(folderPath, options) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
options = Object.assign(Object.assign({}, READ_FOLDER_DEFAULT_OPTIONS), options);
|
||
|
const folderEntries = fsx.readdirSync(folderPath, { withFileTypes: true });
|
||
|
if (options.absolutePaths) {
|
||
|
return folderEntries.map((folderEntry) => {
|
||
|
folderEntry.name = nodeJsPath.resolve(folderPath, folderEntry.name);
|
||
|
return folderEntry;
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
return folderEntries;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.readFolderItems}.
|
||
|
*/
|
||
|
static async readFolderItemsAsync(folderPath, options) {
|
||
|
return await FileSystem._wrapExceptionAsync(async () => {
|
||
|
options = Object.assign(Object.assign({}, READ_FOLDER_DEFAULT_OPTIONS), options);
|
||
|
const folderEntries = await LegacyAdapters_1.LegacyAdapters.convertCallbackToPromise(fs.readdir, folderPath, { withFileTypes: true });
|
||
|
if (options.absolutePaths) {
|
||
|
return folderEntries.map((folderEntry) => {
|
||
|
folderEntry.name = nodeJsPath.resolve(folderPath, folderEntry.name);
|
||
|
return folderEntry;
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
return folderEntries;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Deletes a folder, including all of its contents.
|
||
|
* Behind the scenes is uses `fs-extra.removeSync()`.
|
||
|
* @remarks
|
||
|
* Does not throw if the folderPath does not exist.
|
||
|
* @param folderPath - The absolute or relative path to the folder which should be deleted.
|
||
|
*/
|
||
|
static deleteFolder(folderPath) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
fsx.removeSync(folderPath);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.deleteFolder}.
|
||
|
*/
|
||
|
static async deleteFolderAsync(folderPath) {
|
||
|
await FileSystem._wrapExceptionAsync(() => {
|
||
|
return fsx.remove(folderPath);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Deletes the content of a folder, but not the folder itself. Also ensures the folder exists.
|
||
|
* Behind the scenes it uses `fs-extra.emptyDirSync()`.
|
||
|
* @remarks
|
||
|
* This is a workaround for a common race condition, where the virus scanner holds a lock on the folder
|
||
|
* for a brief period after it was deleted, causing EBUSY errors for any code that tries to recreate the folder.
|
||
|
* @param folderPath - The absolute or relative path to the folder which should have its contents deleted.
|
||
|
*/
|
||
|
static ensureEmptyFolder(folderPath) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
fsx.emptyDirSync(folderPath);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.ensureEmptyFolder}.
|
||
|
*/
|
||
|
static async ensureEmptyFolderAsync(folderPath) {
|
||
|
await FileSystem._wrapExceptionAsync(() => {
|
||
|
return fsx.emptyDir(folderPath);
|
||
|
});
|
||
|
}
|
||
|
// ===============
|
||
|
// FILE OPERATIONS
|
||
|
// ===============
|
||
|
/**
|
||
|
* Writes a text string to a file on disk, overwriting the file if it already exists.
|
||
|
* Behind the scenes it uses `fs.writeFileSync()`.
|
||
|
* @remarks
|
||
|
* Throws an error if the folder doesn't exist, unless ensureFolder=true.
|
||
|
* @param filePath - The absolute or relative path of the file.
|
||
|
* @param contents - The text that should be written to the file.
|
||
|
* @param options - Optional settings that can change the behavior. Type: `IWriteFileOptions`
|
||
|
*/
|
||
|
static writeFile(filePath, contents, options) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
options = Object.assign(Object.assign({}, WRITE_FILE_DEFAULT_OPTIONS), options);
|
||
|
if (options.convertLineEndings) {
|
||
|
contents = Text_1.Text.convertTo(contents.toString(), options.convertLineEndings);
|
||
|
}
|
||
|
try {
|
||
|
fsx.writeFileSync(filePath, contents, { encoding: options.encoding });
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (options.ensureFolderExists) {
|
||
|
if (!FileSystem.isNotExistError(error)) {
|
||
|
throw error;
|
||
|
}
|
||
|
const folderPath = nodeJsPath.dirname(filePath);
|
||
|
FileSystem.ensureFolder(folderPath);
|
||
|
fsx.writeFileSync(filePath, contents, { encoding: options.encoding });
|
||
|
}
|
||
|
else {
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.writeFile}.
|
||
|
*/
|
||
|
static async writeFileAsync(filePath, contents, options) {
|
||
|
await FileSystem._wrapExceptionAsync(async () => {
|
||
|
options = Object.assign(Object.assign({}, WRITE_FILE_DEFAULT_OPTIONS), options);
|
||
|
if (options.convertLineEndings) {
|
||
|
contents = Text_1.Text.convertTo(contents.toString(), options.convertLineEndings);
|
||
|
}
|
||
|
try {
|
||
|
await fsx.writeFile(filePath, contents, { encoding: options.encoding });
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (options.ensureFolderExists) {
|
||
|
if (!FileSystem.isNotExistError(error)) {
|
||
|
throw error;
|
||
|
}
|
||
|
const folderPath = nodeJsPath.dirname(filePath);
|
||
|
await FileSystem.ensureFolderAsync(folderPath);
|
||
|
await fsx.writeFile(filePath, contents, { encoding: options.encoding });
|
||
|
}
|
||
|
else {
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Writes a text string to a file on disk, appending to the file if it already exists.
|
||
|
* Behind the scenes it uses `fs.appendFileSync()`.
|
||
|
* @remarks
|
||
|
* Throws an error if the folder doesn't exist, unless ensureFolder=true.
|
||
|
* @param filePath - The absolute or relative path of the file.
|
||
|
* @param contents - The text that should be written to the file.
|
||
|
* @param options - Optional settings that can change the behavior. Type: `IWriteFileOptions`
|
||
|
*/
|
||
|
static appendToFile(filePath, contents, options) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
options = Object.assign(Object.assign({}, APPEND_TO_FILE_DEFAULT_OPTIONS), options);
|
||
|
if (options.convertLineEndings) {
|
||
|
contents = Text_1.Text.convertTo(contents.toString(), options.convertLineEndings);
|
||
|
}
|
||
|
try {
|
||
|
fsx.appendFileSync(filePath, contents, { encoding: options.encoding });
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (options.ensureFolderExists) {
|
||
|
if (!FileSystem.isNotExistError(error)) {
|
||
|
throw error;
|
||
|
}
|
||
|
const folderPath = nodeJsPath.dirname(filePath);
|
||
|
FileSystem.ensureFolder(folderPath);
|
||
|
fsx.appendFileSync(filePath, contents, { encoding: options.encoding });
|
||
|
}
|
||
|
else {
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.appendToFile}.
|
||
|
*/
|
||
|
static async appendToFileAsync(filePath, contents, options) {
|
||
|
await FileSystem._wrapExceptionAsync(async () => {
|
||
|
options = Object.assign(Object.assign({}, APPEND_TO_FILE_DEFAULT_OPTIONS), options);
|
||
|
if (options.convertLineEndings) {
|
||
|
contents = Text_1.Text.convertTo(contents.toString(), options.convertLineEndings);
|
||
|
}
|
||
|
try {
|
||
|
await fsx.appendFile(filePath, contents, { encoding: options.encoding });
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (options.ensureFolderExists) {
|
||
|
if (!FileSystem.isNotExistError(error)) {
|
||
|
throw error;
|
||
|
}
|
||
|
const folderPath = nodeJsPath.dirname(filePath);
|
||
|
await FileSystem.ensureFolderAsync(folderPath);
|
||
|
await fsx.appendFile(filePath, contents, { encoding: options.encoding });
|
||
|
}
|
||
|
else {
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Reads the contents of a file into a string.
|
||
|
* Behind the scenes it uses `fs.readFileSync()`.
|
||
|
* @param filePath - The relative or absolute path to the file whose contents should be read.
|
||
|
* @param options - Optional settings that can change the behavior. Type: `IReadFileOptions`
|
||
|
*/
|
||
|
static readFile(filePath, options) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
options = Object.assign(Object.assign({}, READ_FILE_DEFAULT_OPTIONS), options);
|
||
|
let contents = FileSystem.readFileToBuffer(filePath).toString(options.encoding);
|
||
|
if (options.convertLineEndings) {
|
||
|
contents = Text_1.Text.convertTo(contents, options.convertLineEndings);
|
||
|
}
|
||
|
return contents;
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.readFile}.
|
||
|
*/
|
||
|
static async readFileAsync(filePath, options) {
|
||
|
return await FileSystem._wrapExceptionAsync(async () => {
|
||
|
options = Object.assign(Object.assign({}, READ_FILE_DEFAULT_OPTIONS), options);
|
||
|
let contents = (await FileSystem.readFileToBufferAsync(filePath)).toString(options.encoding);
|
||
|
if (options.convertLineEndings) {
|
||
|
contents = Text_1.Text.convertTo(contents, options.convertLineEndings);
|
||
|
}
|
||
|
return contents;
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Reads the contents of a file into a buffer.
|
||
|
* Behind the scenes is uses `fs.readFileSync()`.
|
||
|
* @param filePath - The relative or absolute path to the file whose contents should be read.
|
||
|
*/
|
||
|
static readFileToBuffer(filePath) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
return fsx.readFileSync(filePath);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.readFileToBuffer}.
|
||
|
*/
|
||
|
static async readFileToBufferAsync(filePath) {
|
||
|
return await FileSystem._wrapExceptionAsync(() => {
|
||
|
return fsx.readFile(filePath);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Copies a single file from one location to another.
|
||
|
* By default, destinationPath is overwritten if it already exists.
|
||
|
*
|
||
|
* @remarks
|
||
|
* The `copyFile()` API cannot be used to copy folders. It copies at most one file.
|
||
|
* Use {@link FileSystem.copyFiles} if you need to recursively copy a tree of folders.
|
||
|
*
|
||
|
* The implementation is based on `copySync()` from the `fs-extra` package.
|
||
|
*/
|
||
|
static copyFile(options) {
|
||
|
options = Object.assign(Object.assign({}, COPY_FILE_DEFAULT_OPTIONS), options);
|
||
|
if (FileSystem.getStatistics(options.sourcePath).isDirectory()) {
|
||
|
throw new Error('The specified path refers to a folder; this operation expects a file object:\n' + options.sourcePath);
|
||
|
}
|
||
|
FileSystem._wrapException(() => {
|
||
|
fsx.copySync(options.sourcePath, options.destinationPath, {
|
||
|
errorOnExist: options.alreadyExistsBehavior === AlreadyExistsBehavior.Error,
|
||
|
overwrite: options.alreadyExistsBehavior === AlreadyExistsBehavior.Overwrite
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.copyFile}.
|
||
|
*/
|
||
|
static async copyFileAsync(options) {
|
||
|
options = Object.assign(Object.assign({}, COPY_FILE_DEFAULT_OPTIONS), options);
|
||
|
if ((await FileSystem.getStatisticsAsync(options.sourcePath)).isDirectory()) {
|
||
|
throw new Error('The specified path refers to a folder; this operation expects a file object:\n' + options.sourcePath);
|
||
|
}
|
||
|
await FileSystem._wrapExceptionAsync(() => {
|
||
|
return fsx.copy(options.sourcePath, options.destinationPath, {
|
||
|
errorOnExist: options.alreadyExistsBehavior === AlreadyExistsBehavior.Error,
|
||
|
overwrite: options.alreadyExistsBehavior === AlreadyExistsBehavior.Overwrite
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Copies a file or folder from one location to another, recursively copying any folder contents.
|
||
|
* By default, destinationPath is overwritten if it already exists.
|
||
|
*
|
||
|
* @remarks
|
||
|
* If you only intend to copy a single file, it is recommended to use {@link FileSystem.copyFile}
|
||
|
* instead to more clearly communicate the intended operation.
|
||
|
*
|
||
|
* The implementation is based on `copySync()` from the `fs-extra` package.
|
||
|
*/
|
||
|
static copyFiles(options) {
|
||
|
options = Object.assign(Object.assign({}, COPY_FILES_DEFAULT_OPTIONS), options);
|
||
|
FileSystem._wrapException(() => {
|
||
|
fsx.copySync(options.sourcePath, options.destinationPath, {
|
||
|
dereference: !!options.dereferenceSymlinks,
|
||
|
errorOnExist: options.alreadyExistsBehavior === AlreadyExistsBehavior.Error,
|
||
|
overwrite: options.alreadyExistsBehavior === AlreadyExistsBehavior.Overwrite,
|
||
|
preserveTimestamps: !!options.preserveTimestamps,
|
||
|
filter: options.filter
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.copyFiles}.
|
||
|
*/
|
||
|
static async copyFilesAsync(options) {
|
||
|
options = Object.assign(Object.assign({}, COPY_FILES_DEFAULT_OPTIONS), options);
|
||
|
await FileSystem._wrapExceptionAsync(async () => {
|
||
|
await fsx.copy(options.sourcePath, options.destinationPath, {
|
||
|
dereference: !!options.dereferenceSymlinks,
|
||
|
errorOnExist: options.alreadyExistsBehavior === AlreadyExistsBehavior.Error,
|
||
|
overwrite: options.alreadyExistsBehavior === AlreadyExistsBehavior.Overwrite,
|
||
|
preserveTimestamps: !!options.preserveTimestamps,
|
||
|
filter: options.filter
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Deletes a file. Can optionally throw if the file doesn't exist.
|
||
|
* Behind the scenes it uses `fs.unlinkSync()`.
|
||
|
* @param filePath - The absolute or relative path to the file that should be deleted.
|
||
|
* @param options - Optional settings that can change the behavior. Type: `IDeleteFileOptions`
|
||
|
*/
|
||
|
static deleteFile(filePath, options) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
options = Object.assign(Object.assign({}, DELETE_FILE_DEFAULT_OPTIONS), options);
|
||
|
try {
|
||
|
fsx.unlinkSync(filePath);
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (options.throwIfNotExists || !FileSystem.isNotExistError(error)) {
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.deleteFile}.
|
||
|
*/
|
||
|
static async deleteFileAsync(filePath, options) {
|
||
|
await FileSystem._wrapExceptionAsync(async () => {
|
||
|
options = Object.assign(Object.assign({}, DELETE_FILE_DEFAULT_OPTIONS), options);
|
||
|
try {
|
||
|
await fsx.unlink(filePath);
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (options.throwIfNotExists || !FileSystem.isNotExistError(error)) {
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
// ===============
|
||
|
// LINK OPERATIONS
|
||
|
// ===============
|
||
|
/**
|
||
|
* Gets the statistics of a filesystem object. Does NOT follow the link to its target.
|
||
|
* Behind the scenes it uses `fs.lstatSync()`.
|
||
|
* @param path - The absolute or relative path to the filesystem object.
|
||
|
*/
|
||
|
static getLinkStatistics(path) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
return fsx.lstatSync(path);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.getLinkStatistics}.
|
||
|
*/
|
||
|
static async getLinkStatisticsAsync(path) {
|
||
|
return await FileSystem._wrapExceptionAsync(() => {
|
||
|
return fsx.lstat(path);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* If `path` refers to a symbolic link, this returns the path of the link target, which may be
|
||
|
* an absolute or relative path.
|
||
|
*
|
||
|
* @remarks
|
||
|
* If `path` refers to a filesystem object that is not a symbolic link, then an `ErrnoException` is thrown
|
||
|
* with code 'UNKNOWN'. If `path` does not exist, then an `ErrnoException` is thrown with code `ENOENT`.
|
||
|
*
|
||
|
* @param path - The absolute or relative path to the symbolic link.
|
||
|
* @returns the path of the link target
|
||
|
*/
|
||
|
static readLink(path) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
return fsx.readlinkSync(path);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.readLink}.
|
||
|
*/
|
||
|
static async readLinkAsync(path) {
|
||
|
return await FileSystem._wrapExceptionAsync(() => {
|
||
|
return fsx.readlink(path);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Creates an NTFS "directory junction" on Windows operating systems; for other operating systems, it
|
||
|
* creates a regular symbolic link. The link target must be a folder, not a file.
|
||
|
* Behind the scenes it uses `fs.symlinkSync()`.
|
||
|
*
|
||
|
* @remarks
|
||
|
* For security reasons, Windows operating systems by default require administrator elevation to create
|
||
|
* symbolic links. As a result, on Windows it's generally recommended for Node.js tools to use hard links
|
||
|
* (for files) or NTFS directory junctions (for folders), since regular users are allowed to create them.
|
||
|
* Hard links and junctions are less vulnerable to symlink attacks because they cannot reference a network share,
|
||
|
* and their target must exist at the time of link creation. Non-Windows operating systems generally don't
|
||
|
* restrict symlink creation, and as such are more vulnerable to symlink attacks. Note that Windows can be
|
||
|
* configured to permit regular users to create symlinks, for example by enabling Windows 10 "developer mode."
|
||
|
*
|
||
|
* A directory junction requires the link source and target to both be located on local disk volumes;
|
||
|
* if not, use a symbolic link instead.
|
||
|
*/
|
||
|
static createSymbolicLinkJunction(options) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
return FileSystem._handleLink(() => {
|
||
|
// For directories, we use a Windows "junction". On POSIX operating systems, this produces a regular symlink.
|
||
|
return fsx.symlinkSync(options.linkTargetPath, options.newLinkPath, 'junction');
|
||
|
}, options);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.createSymbolicLinkJunction}.
|
||
|
*/
|
||
|
static async createSymbolicLinkJunctionAsync(options) {
|
||
|
await FileSystem._wrapExceptionAsync(() => {
|
||
|
return FileSystem._handleLinkAsync(() => {
|
||
|
// For directories, we use a Windows "junction". On POSIX operating systems, this produces a regular symlink.
|
||
|
return fsx.symlink(options.linkTargetPath, options.newLinkPath, 'junction');
|
||
|
}, options);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Creates a symbolic link to a file. On Windows operating systems, this may require administrator elevation.
|
||
|
* Behind the scenes it uses `fs.symlinkSync()`.
|
||
|
*
|
||
|
* @remarks
|
||
|
* To avoid administrator elevation on Windows, use {@link FileSystem.createHardLink} instead.
|
||
|
*
|
||
|
* On Windows operating systems, the NTFS file system distinguishes file symlinks versus directory symlinks:
|
||
|
* If the target is not the correct type, the symlink will be created successfully, but will fail to resolve.
|
||
|
* Other operating systems do not make this distinction, in which case {@link FileSystem.createSymbolicLinkFile}
|
||
|
* and {@link FileSystem.createSymbolicLinkFolder} can be used interchangeably, but doing so will make your
|
||
|
* tool incompatible with Windows.
|
||
|
*/
|
||
|
static createSymbolicLinkFile(options) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
return FileSystem._handleLink(() => {
|
||
|
return fsx.symlinkSync(options.linkTargetPath, options.newLinkPath, 'file');
|
||
|
}, options);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.createSymbolicLinkFile}.
|
||
|
*/
|
||
|
static async createSymbolicLinkFileAsync(options) {
|
||
|
await FileSystem._wrapExceptionAsync(() => {
|
||
|
return FileSystem._handleLinkAsync(() => {
|
||
|
return fsx.symlink(options.linkTargetPath, options.newLinkPath, 'file');
|
||
|
}, options);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Creates a symbolic link to a folder. On Windows operating systems, this may require administrator elevation.
|
||
|
* Behind the scenes it uses `fs.symlinkSync()`.
|
||
|
*
|
||
|
* @remarks
|
||
|
* To avoid administrator elevation on Windows, use {@link FileSystem.createSymbolicLinkJunction} instead.
|
||
|
*
|
||
|
* On Windows operating systems, the NTFS file system distinguishes file symlinks versus directory symlinks:
|
||
|
* If the target is not the correct type, the symlink will be created successfully, but will fail to resolve.
|
||
|
* Other operating systems do not make this distinction, in which case {@link FileSystem.createSymbolicLinkFile}
|
||
|
* and {@link FileSystem.createSymbolicLinkFolder} can be used interchangeably, but doing so will make your
|
||
|
* tool incompatible with Windows.
|
||
|
*/
|
||
|
static createSymbolicLinkFolder(options) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
return FileSystem._handleLink(() => {
|
||
|
return fsx.symlinkSync(options.linkTargetPath, options.newLinkPath, 'dir');
|
||
|
}, options);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.createSymbolicLinkFolder}.
|
||
|
*/
|
||
|
static async createSymbolicLinkFolderAsync(options) {
|
||
|
await FileSystem._wrapExceptionAsync(() => {
|
||
|
return FileSystem._handleLinkAsync(() => {
|
||
|
return fsx.symlink(options.linkTargetPath, options.newLinkPath, 'dir');
|
||
|
}, options);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Creates a hard link. The link target must be a file, not a folder.
|
||
|
* Behind the scenes it uses `fs.linkSync()`.
|
||
|
*
|
||
|
* @remarks
|
||
|
* For security reasons, Windows operating systems by default require administrator elevation to create
|
||
|
* symbolic links. As a result, on Windows it's generally recommended for Node.js tools to use hard links
|
||
|
* (for files) or NTFS directory junctions (for folders), since regular users are allowed to create them.
|
||
|
* Hard links and junctions are less vulnerable to symlink attacks because they cannot reference a network share,
|
||
|
* and their target must exist at the time of link creation. Non-Windows operating systems generally don't
|
||
|
* restrict symlink creation, and as such are more vulnerable to symlink attacks. Note that Windows can be
|
||
|
* configured to permit regular users to create symlinks, for example by enabling Windows 10 "developer mode."
|
||
|
*
|
||
|
* A hard link requires the link source and target to both be located on same disk volume;
|
||
|
* if not, use a symbolic link instead.
|
||
|
*/
|
||
|
static createHardLink(options) {
|
||
|
FileSystem._wrapException(() => {
|
||
|
return FileSystem._handleLink(() => {
|
||
|
return fsx.linkSync(options.linkTargetPath, options.newLinkPath);
|
||
|
}, Object.assign(Object.assign({}, options), { linkTargetMustExist: true }));
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.createHardLink}.
|
||
|
*/
|
||
|
static async createHardLinkAsync(options) {
|
||
|
await FileSystem._wrapExceptionAsync(() => {
|
||
|
return FileSystem._handleLinkAsync(() => {
|
||
|
return fsx.link(options.linkTargetPath, options.newLinkPath);
|
||
|
}, Object.assign(Object.assign({}, options), { linkTargetMustExist: true }));
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Follows a link to its destination and returns the absolute path to the final target of the link.
|
||
|
* Behind the scenes it uses `fs.realpathSync()`.
|
||
|
* @param linkPath - The path to the link.
|
||
|
*/
|
||
|
static getRealPath(linkPath) {
|
||
|
return FileSystem._wrapException(() => {
|
||
|
return fsx.realpathSync(linkPath);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* An async version of {@link FileSystem.getRealPath}.
|
||
|
*/
|
||
|
static async getRealPathAsync(linkPath) {
|
||
|
return await FileSystem._wrapExceptionAsync(() => {
|
||
|
return fsx.realpath(linkPath);
|
||
|
});
|
||
|
}
|
||
|
// ===============
|
||
|
// UTILITY FUNCTIONS
|
||
|
// ===============
|
||
|
/**
|
||
|
* Returns true if the error object indicates the file or folder already exists (`EEXIST`).
|
||
|
*/
|
||
|
static isExistError(error) {
|
||
|
return FileSystem.isErrnoException(error) && error.code === 'EEXIST';
|
||
|
}
|
||
|
/**
|
||
|
* Returns true if the error object indicates the file or folder does not exist (`ENOENT` or `ENOTDIR`)
|
||
|
*/
|
||
|
static isNotExistError(error) {
|
||
|
return FileSystem.isFileDoesNotExistError(error) || FileSystem.isFolderDoesNotExistError(error);
|
||
|
}
|
||
|
/**
|
||
|
* Returns true if the error object indicates the file does not exist (`ENOENT`).
|
||
|
*/
|
||
|
static isFileDoesNotExistError(error) {
|
||
|
return FileSystem.isErrnoException(error) && error.code === 'ENOENT';
|
||
|
}
|
||
|
/**
|
||
|
* Returns true if the error object indicates the folder does not exist (`ENOTDIR`).
|
||
|
*/
|
||
|
static isFolderDoesNotExistError(error) {
|
||
|
return FileSystem.isErrnoException(error) && error.code === 'ENOTDIR';
|
||
|
}
|
||
|
/**
|
||
|
* Returns true if the error object indicates the target is a directory (`EISDIR`).
|
||
|
*/
|
||
|
static isDirectoryError(error) {
|
||
|
return FileSystem.isErrnoException(error) && error.code === 'EISDIR';
|
||
|
}
|
||
|
/**
|
||
|
* Returns true if the error object indicates the target is not a directory (`ENOTDIR`).
|
||
|
*/
|
||
|
static isNotDirectoryError(error) {
|
||
|
return FileSystem.isErrnoException(error) && error.code === 'ENOTDIR';
|
||
|
}
|
||
|
/**
|
||
|
* Returns true if the error object indicates that the `unlink` system call failed
|
||
|
* due to a permissions issue (`EPERM`).
|
||
|
*/
|
||
|
static isUnlinkNotPermittedError(error) {
|
||
|
return FileSystem.isErrnoException(error) && error.code === 'EPERM' && error.syscall === 'unlink';
|
||
|
}
|
||
|
/**
|
||
|
* Detects if the provided error object is a `NodeJS.ErrnoException`
|
||
|
*/
|
||
|
static isErrnoException(error) {
|
||
|
const typedError = error;
|
||
|
return (typeof typedError.code === 'string' &&
|
||
|
typeof typedError.errno === 'number' &&
|
||
|
typeof typedError.path === 'string' &&
|
||
|
typeof typedError.syscall === 'string');
|
||
|
}
|
||
|
static _handleLink(linkFn, options) {
|
||
|
try {
|
||
|
linkFn();
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (FileSystem.isExistError(error)) {
|
||
|
// Link exists, handle it
|
||
|
switch (options.alreadyExistsBehavior) {
|
||
|
case AlreadyExistsBehavior.Ignore:
|
||
|
break;
|
||
|
case AlreadyExistsBehavior.Overwrite:
|
||
|
// fsx.linkSync does not allow overwriting so we must manually delete. If it's
|
||
|
// a folder, it will throw an error.
|
||
|
this.deleteFile(options.newLinkPath);
|
||
|
linkFn();
|
||
|
break;
|
||
|
case AlreadyExistsBehavior.Error:
|
||
|
default:
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// When attempting to create a link in a directory that does not exist, an ENOENT
|
||
|
// or ENOTDIR error is thrown, so we should ensure the directory exists before
|
||
|
// retrying. There are also cases where the target file must exist, so validate in
|
||
|
// those cases to avoid confusing the missing directory with the missing target file.
|
||
|
if (FileSystem.isNotExistError(error) &&
|
||
|
(!options.linkTargetMustExist || FileSystem.exists(options.linkTargetPath))) {
|
||
|
this.ensureFolder(nodeJsPath.dirname(options.newLinkPath));
|
||
|
linkFn();
|
||
|
}
|
||
|
else {
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
static async _handleLinkAsync(linkFn, options) {
|
||
|
try {
|
||
|
await linkFn();
|
||
|
}
|
||
|
catch (error) {
|
||
|
if (FileSystem.isExistError(error)) {
|
||
|
// Link exists, handle it
|
||
|
switch (options.alreadyExistsBehavior) {
|
||
|
case AlreadyExistsBehavior.Ignore:
|
||
|
break;
|
||
|
case AlreadyExistsBehavior.Overwrite:
|
||
|
// fsx.linkSync does not allow overwriting so we must manually delete. If it's
|
||
|
// a folder, it will throw an error.
|
||
|
await this.deleteFileAsync(options.newLinkPath);
|
||
|
await linkFn();
|
||
|
break;
|
||
|
case AlreadyExistsBehavior.Error:
|
||
|
default:
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// When attempting to create a link in a directory that does not exist, an ENOENT
|
||
|
// or ENOTDIR error is thrown, so we should ensure the directory exists before
|
||
|
// retrying. There are also cases where the target file must exist, so validate in
|
||
|
// those cases to avoid confusing the missing directory with the missing target file.
|
||
|
if (FileSystem.isNotExistError(error) &&
|
||
|
(!options.linkTargetMustExist || (await FileSystem.existsAsync(options.linkTargetPath)))) {
|
||
|
await this.ensureFolderAsync(nodeJsPath.dirname(options.newLinkPath));
|
||
|
await linkFn();
|
||
|
}
|
||
|
else {
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
static _wrapException(fn) {
|
||
|
try {
|
||
|
return fn();
|
||
|
}
|
||
|
catch (error) {
|
||
|
FileSystem._updateErrorMessage(error);
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
static async _wrapExceptionAsync(fn) {
|
||
|
try {
|
||
|
return await fn();
|
||
|
}
|
||
|
catch (error) {
|
||
|
FileSystem._updateErrorMessage(error);
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
static _updateErrorMessage(error) {
|
||
|
if (FileSystem.isErrnoException(error)) {
|
||
|
if (FileSystem.isFileDoesNotExistError(error)) {
|
||
|
// eslint-disable-line @typescript-eslint/no-use-before-define
|
||
|
error.message = `File does not exist: ${error.path}\n${error.message}`;
|
||
|
}
|
||
|
else if (FileSystem.isFolderDoesNotExistError(error)) {
|
||
|
// eslint-disable-line @typescript-eslint/no-use-before-define
|
||
|
error.message = `Folder does not exist: ${error.path}\n${error.message}`;
|
||
|
}
|
||
|
else if (FileSystem.isExistError(error)) {
|
||
|
// Oddly, the typing does not include the `dest` property even though the documentation
|
||
|
// indicates it is there: https://nodejs.org/docs/latest-v10.x/api/errors.html#errors_error_dest
|
||
|
const extendedError = error;
|
||
|
// eslint-disable-line @typescript-eslint/no-use-before-define
|
||
|
error.message = `File or folder already exists: ${extendedError.dest}\n${error.message}`;
|
||
|
}
|
||
|
else if (FileSystem.isUnlinkNotPermittedError(error)) {
|
||
|
// eslint-disable-line @typescript-eslint/no-use-before-define
|
||
|
error.message = `File or folder could not be deleted: ${error.path}\n${error.message}`;
|
||
|
}
|
||
|
else if (FileSystem.isDirectoryError(error)) {
|
||
|
// eslint-disable-line @typescript-eslint/no-use-before-define
|
||
|
error.message = `Target is a folder, not a file: ${error.path}\n${error.message}`;
|
||
|
}
|
||
|
else if (FileSystem.isNotDirectoryError(error)) {
|
||
|
// eslint-disable-line @typescript-eslint/no-use-before-define
|
||
|
error.message = `Target is not a folder: ${error.path}\n${error.message}`;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
exports.FileSystem = FileSystem;
|
||
|
//# sourceMappingURL=FileSystem.js.map
|