@salesforce/core
Version:
Core libraries to interact with SFDX projects, orgs, and APIs.
378 lines • 14.7 kB
JavaScript
;
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.fs = void 0;
const crypto = require("crypto");
const path = require("path");
const util_1 = require("util");
const kit_1 = require("@salesforce/kit");
const fsLib = require("graceful-fs");
const mkdirpLib = require("mkdirp");
const sfdxError_1 = require("../sfdxError");
/**
* @deprecated Use fs/promises instead
*/
exports.fs = Object.assign({}, fsLib, {
/**
* The default file system mode to use when creating directories.
*/
DEFAULT_USER_DIR_MODE: '700',
/**
* The default file system mode to use when creating files.
*/
DEFAULT_USER_FILE_MODE: '600',
/**
* A convenience reference to {@link https://nodejs.org/api/fsLib.html#fs_fs_constants}
* to reduce the need to import multiple `fs` modules.
*/
constants: fsLib.constants,
/**
* Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_readfile_path_options_callback|fsLib.readFile}.
*/
readFile: util_1.promisify(fsLib.readFile),
/**
* Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_readdir_path_options_callback|fsLib.readdir}.
*/
readdir: util_1.promisify(fsLib.readdir),
/**
* Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_writefile_file_data_options_callback|fsLib.writeFile}.
*/
writeFile: util_1.promisify(fsLib.writeFile),
/**
* Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_access_path_mode_callback|fsLib.access}.
*/
access: util_1.promisify(fsLib.access),
/**
* Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_open_path_flags_mode_callback|fsLib.open}.
*/
open: util_1.promisify(fsLib.open),
/**
* Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_unlink_path_callback|fsLib.unlink}.
*/
unlink: util_1.promisify(fsLib.unlink),
/**
* Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_readdir_path_options_callback|fsLib.rmdir}.
*/
rmdir: util_1.promisify(fsLib.rmdir),
/**
* Promisified version of {@link https://nodejs.org/api/fsLib.html#fs_fs_fstat_fd_callback|fsLib.stat}.
*/
stat: util_1.promisify(fsLib.stat),
/**
* Promisified version of {@link https://npmjs.com/package/mkdirp|mkdirp}.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
mkdirp: (folderPath, mode) => mkdirpLib(folderPath, mode),
mkdirpSync: mkdirpLib.sync,
/**
* Deletes a folder recursively, removing all descending files and folders.
*
* **Throws** *PathIsNullOrUndefined* The path is not defined.
* **Throws** *DirMissingOrNoAccess* The folder or any sub-folder is missing or has no access.
*
* @param {string} dirPath The path to remove.
*/
remove: async (dirPath) => {
if (!dirPath) {
throw new sfdxError_1.SfdxError('Path is null or undefined.', 'PathIsNullOrUndefined');
}
try {
await exports.fs.access(dirPath, fsLib.constants.R_OK);
}
catch (err) {
throw new sfdxError_1.SfdxError(`The path: ${dirPath} doesn't exist or access is denied.`, 'DirMissingOrNoAccess');
}
const files = await exports.fs.readdir(dirPath);
const stats = await Promise.all(files.map((file) => exports.fs.stat(path.join(dirPath, file))));
const metas = stats.map((value, index) => Object.assign(value, { path: path.join(dirPath, files[index]) }));
await Promise.all(metas.map((meta) => (meta.isDirectory() ? exports.fs.remove(meta.path) : exports.fs.unlink(meta.path))));
await exports.fs.rmdir(dirPath);
},
/**
* Deletes a folder recursively, removing all descending files and folders.
*
* NOTE: It is recommended to call the asynchronous `remove` when possible as it will remove all files in parallel rather than serially.
*
* **Throws** *PathIsNullOrUndefined* The path is not defined.
* **Throws** *DirMissingOrNoAccess* The folder or any sub-folder is missing or has no access.
*
* @param {string} dirPath The path to remove.
*/
removeSync: (dirPath) => {
if (!dirPath) {
throw new sfdxError_1.SfdxError('Path is null or undefined.', 'PathIsNullOrUndefined');
}
try {
exports.fs.accessSync(dirPath, fsLib.constants.R_OK);
}
catch (err) {
throw new sfdxError_1.SfdxError(`The path: ${dirPath} doesn't exist or access is denied.`, 'DirMissingOrNoAccess');
}
exports.fs.actOnSync(dirPath, (fullPath, file) => {
if (file) {
exports.fs.unlinkSync(fullPath);
}
else {
// All files in this directory will be acted on before the directory.
exports.fs.rmdirSync(fullPath);
}
}, 'all');
// Remove the top level
exports.fs.rmdirSync(dirPath);
},
/**
* Searches a file path in an ascending manner (until reaching the filesystem root) for the first occurrence a
* specific file name. Resolves with the directory path containing the located file, or `null` if the file was
* not found.
*
* @param dir The directory path in which to start the upward search.
* @param file The file name to look for.
*/
traverseForFile: async (dir, file) => {
let foundProjectDir;
try {
await exports.fs.stat(path.join(dir, file));
foundProjectDir = dir;
}
catch (err) {
if (err && err.code === 'ENOENT') {
const nextDir = path.resolve(dir, '..');
if (nextDir !== dir) {
// stop at root
foundProjectDir = await exports.fs.traverseForFile(nextDir, file);
}
}
}
return foundProjectDir;
},
/**
* Searches a file path synchronously in an ascending manner (until reaching the filesystem root) for the first occurrence a
* specific file name. Resolves with the directory path containing the located file, or `null` if the file was
* not found.
*
* @param dir The directory path in which to start the upward search.
* @param file The file name to look for.
*/
traverseForFileSync: (dir, file) => {
let foundProjectDir;
try {
exports.fs.statSync(path.join(dir, file));
foundProjectDir = dir;
}
catch (err) {
if (err && err.code === 'ENOENT') {
const nextDir = path.resolve(dir, '..');
if (nextDir !== dir) {
// stop at root
foundProjectDir = exports.fs.traverseForFileSync(nextDir, file);
}
}
}
return foundProjectDir;
},
/**
* Read a file and convert it to JSON. Returns the contents of the file as a JSON object
*
* @param jsonPath The path of the file.
* @param throwOnEmpty Whether to throw an error if the JSON file is empty.
*/
readJson: async (jsonPath, throwOnEmpty) => {
const fileData = await exports.fs.readFile(jsonPath, 'utf8');
return kit_1.parseJson(fileData, jsonPath, throwOnEmpty);
},
/**
* Read a file and convert it to JSON. Returns the contents of the file as a JSON object
*
* @param jsonPath The path of the file.
* @param throwOnEmpty Whether to throw an error if the JSON file is empty.
*/
readJsonSync: (jsonPath, throwOnEmpty) => {
const fileData = exports.fs.readFileSync(jsonPath, 'utf8');
return kit_1.parseJson(fileData, jsonPath, throwOnEmpty);
},
/**
* Read a file and convert it to JSON, throwing an error if the parsed contents are not a `JsonMap`.
*
* @param jsonPath The path of the file.
* @param throwOnEmpty Whether to throw an error if the JSON file is empty.
*/
readJsonMap: async (jsonPath, throwOnEmpty) => {
const fileData = await exports.fs.readFile(jsonPath, 'utf8');
return kit_1.parseJsonMap(fileData, jsonPath, throwOnEmpty);
},
/**
* Read a file and convert it to JSON, throwing an error if the parsed contents are not a `JsonMap`.
*
* @param jsonPath The path of the file.
* @param throwOnEmpty Whether to throw an error if the JSON file is empty.
*/
readJsonMapSync: (jsonPath, throwOnEmpty) => {
const fileData = exports.fs.readFileSync(jsonPath, 'utf8');
return kit_1.parseJsonMap(fileData, jsonPath, throwOnEmpty);
},
/**
* Convert a JSON-compatible object to a `string` and write it to a file.
*
* @param jsonPath The path of the file to write.
* @param data The JSON object to write.
*/
writeJson: async (jsonPath, data, options = {}) => {
options = Object.assign({ space: 2 }, options);
const fileData = JSON.stringify(data, null, options.space);
await exports.fs.writeFile(jsonPath, fileData, {
encoding: 'utf8',
mode: exports.fs.DEFAULT_USER_FILE_MODE,
});
},
/**
* Convert a JSON-compatible object to a `string` and write it to a file.
*
* @param jsonPath The path of the file to write.
* @param data The JSON object to write.
*/
writeJsonSync: (jsonPath, data, options = {}) => {
options = Object.assign({ space: 2 }, options);
const fileData = JSON.stringify(data, null, options.space);
exports.fs.writeFileSync(jsonPath, fileData, {
encoding: 'utf8',
mode: exports.fs.DEFAULT_USER_FILE_MODE,
});
},
/**
* Checks if a file path exists
*
* @param filePath the file path to check the existence of
*/
fileExists: async (filePath) => {
try {
await exports.fs.access(filePath);
return true;
}
catch (err) {
return false;
}
},
/**
* Checks if a file path exists
*
* @param filePath the file path to check the existence of
*/
fileExistsSync: (filePath) => {
try {
exports.fs.accessSync(filePath);
return true;
}
catch (err) {
return false;
}
},
/**
* Recursively act on all files or directories in a directory
*
* @param dir path to directory
* @param perform function to be run on contents of dir
* @param onType optional parameter to specify type to actOn
* @returns void
*/
actOn: async (dir, perform, onType = 'file') => {
for (const file of await exports.fs.readdir(dir)) {
const filePath = path.join(dir, file);
const stat = await exports.fs.stat(filePath);
if (stat) {
if (stat.isDirectory()) {
await exports.fs.actOn(filePath, perform, onType);
if (onType === 'dir' || onType === 'all') {
await perform(filePath);
}
}
else if (stat.isFile() && (onType === 'file' || onType === 'all')) {
await perform(filePath, file, dir);
}
}
}
},
/**
* Recursively act on all files or directories in a directory
*
* @param dir path to directory
* @param perform function to be run on contents of dir
* @param onType optional parameter to specify type to actOn
* @returns void
*/
actOnSync: (dir, perform, onType = 'file') => {
for (const file of exports.fs.readdirSync(dir)) {
const filePath = path.join(dir, file);
const stat = exports.fs.statSync(filePath);
if (stat) {
if (stat.isDirectory()) {
exports.fs.actOnSync(filePath, perform, onType);
if (onType === 'dir' || onType === 'all') {
perform(filePath);
}
}
else if (stat.isFile() && (onType === 'file' || onType === 'all')) {
perform(filePath, file, dir);
}
}
}
},
/**
* Checks if files are the same
*
* @param file1Path the first file path to check
* @param file2Path the second file path to check
* @returns boolean
*/
areFilesEqual: async (file1Path, file2Path) => {
try {
const file1Size = (await exports.fs.stat(file1Path)).size;
const file2Size = (await exports.fs.stat(file2Path)).size;
if (file1Size !== file2Size) {
return false;
}
const contentA = await exports.fs.readFile(file1Path);
const contentB = await exports.fs.readFile(file2Path);
return exports.fs.getContentHash(contentA) === exports.fs.getContentHash(contentB);
}
catch (err) {
throw new sfdxError_1.SfdxError(`The path: ${err.path} doesn't exist or access is denied.`, 'DirMissingOrNoAccess');
}
},
/**
* Checks if files are the same
*
* @param file1Path the first file path to check
* @param file2Path the second file path to check
* @returns boolean
*/
areFilesEqualSync: (file1Path, file2Path) => {
try {
const file1Size = exports.fs.statSync(file1Path).size;
const file2Size = exports.fs.statSync(file2Path).size;
if (file1Size !== file2Size) {
return false;
}
const contentA = exports.fs.readFileSync(file1Path);
const contentB = exports.fs.readFileSync(file2Path);
return exports.fs.getContentHash(contentA) === exports.fs.getContentHash(contentB);
}
catch (err) {
throw new sfdxError_1.SfdxError(`The path: ${err.path} doesn't exist or access is denied.`, 'DirMissingOrNoAccess');
}
},
/**
* Creates a hash for the string that's passed in
*
* @param contents The string passed into the function
* @returns string
*/
getContentHash(contents) {
return crypto.createHash('sha1').update(contents).digest('hex');
},
});
//# sourceMappingURL=fs.js.map