@appium/support
Version:
Support libs used across Appium packages
357 lines • 14.1 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fs = void 0;
const bluebird_1 = __importDefault(require("bluebird"));
const node_crypto_1 = __importDefault(require("node:crypto"));
const node_fs_1 = require("node:fs");
const node_util_1 = require("node:util");
const glob_1 = require("glob");
const klaw_1 = __importDefault(require("klaw"));
const lodash_1 = __importDefault(require("lodash"));
const ncp_1 = __importDefault(require("ncp"));
const package_directory_1 = require("package-directory");
const node_path_1 = __importDefault(require("node:path"));
const read_pkg_1 = require("read-pkg");
const sanitize_filename_1 = __importDefault(require("sanitize-filename"));
const which_1 = __importDefault(require("which"));
const logger_1 = __importDefault(require("./logger"));
const timing_1 = require("./timing");
const system_1 = require("./system");
const util_1 = require("./util");
const ncpAsync = (0, node_util_1.promisify)(ncp_1.default);
const findRootCached = lodash_1.default.memoize(package_directory_1.packageDirectorySync, (opts) => opts?.cwd);
function isErrnoException(err) {
return err instanceof Error && 'code' in err;
}
exports.fs = {
/**
* Resolves `true` if `path` is _readable_.
* On Windows, ACLs are not supported, so this becomes a simple check for existence.
* This function will never reject.
*/
async hasAccess(filePath) {
try {
await node_fs_1.promises.access(filePath, node_fs_1.constants.R_OK);
}
catch {
return false;
}
return true;
},
/**
* Resolves `true` if `path` is executable; `false` otherwise.
* On Windows, delegates to {@linkcode fs.hasAccess}.
* This function will never reject.
*/
async isExecutable(filePath) {
try {
if ((0, system_1.isWindows)()) {
return await exports.fs.hasAccess(filePath);
}
await node_fs_1.promises.access(filePath, node_fs_1.constants.R_OK | node_fs_1.constants.X_OK);
}
catch {
return false;
}
return true;
},
/** Alias for {@linkcode fs.hasAccess} */
async exists(filePath) {
return await exports.fs.hasAccess(filePath);
},
/**
* Remove a directory and all its contents, recursively.
* @see https://nodejs.org/api/fs.html#fspromisesrmpath-options
*/
async rimraf(filepath) {
return await node_fs_1.promises.rm(filepath, { recursive: true, force: true });
},
/**
* Remove a directory and all its contents, recursively (sync).
* @see https://nodejs.org/api/fs.html#fsrmsyncpath-options
*/
rimrafSync(filepath) {
return (0, node_fs_1.rmSync)(filepath, { recursive: true, force: true });
},
/**
* Like Node.js `fsPromises.mkdir()`, but will not reject if the directory already exists.
* @see https://nodejs.org/api/fs.html#fspromisesmkdirpath-options
*/
async mkdir(filepath, opts = {}) {
try {
return await node_fs_1.promises.mkdir(filepath, opts);
}
catch (err) {
if (isErrnoException(err) && err.code !== 'EEXIST') {
throw err;
}
}
},
/**
* Copies files and entire directories.
* @see https://npm.im/ncp
*/
async copyFile(source, destination, opts = {}) {
if (!(await exports.fs.hasAccess(source))) {
throw new Error(`The file at '${source}' does not exist or is not accessible`);
}
return await ncpAsync(source, destination, opts);
},
/** Create an MD5 hash of a file. */
async md5(filePath) {
return await exports.fs.hash(filePath, 'md5');
},
/**
* Move a file or a folder.
*/
async mv(from, to, opts = {}) {
const ensureDestination = async (p) => {
if (opts?.mkdirp && !(await this.exists(p))) {
await node_fs_1.promises.mkdir(p, { recursive: true });
return true;
}
return false;
};
const renameFile = async (src, dst, skipExistenceCheck) => {
if (!skipExistenceCheck && (await this.exists(dst))) {
if (opts?.clobber === false) {
const err = new Error(`The destination path '${dst}' already exists`);
err.code = 'EEXIST';
throw err;
}
await this.rimraf(dst);
}
try {
await node_fs_1.promises.rename(src, dst);
}
catch (err) {
if (isErrnoException(err) && err.code === 'EXDEV') {
await this.copyFile(String(src), String(dst));
await this.rimraf(src);
}
else {
throw err;
}
}
};
let fromStat;
try {
fromStat = await node_fs_1.promises.stat(from);
}
catch (err) {
if (isErrnoException(err) && err.code === 'ENOENT') {
throw new Error(`The source path '${from}' does not exist or is not accessible`);
}
throw err;
}
if (fromStat.isFile()) {
const dstRootWasCreated = await ensureDestination(node_path_1.default.dirname(to));
await renameFile(from, to, dstRootWasCreated);
}
else if (fromStat.isDirectory()) {
const dstRootWasCreated = await ensureDestination(to);
const items = await node_fs_1.promises.readdir(from, { withFileTypes: true });
for (const item of items) {
const srcPath = node_path_1.default.join(from, item.name);
const destPath = node_path_1.default.join(to, item.name);
if (item.isDirectory()) {
await this.mv(srcPath, destPath, opts);
}
else if (item.isFile()) {
await renameFile(srcPath, destPath, dstRootWasCreated);
}
}
}
else {
return;
}
await this.rimraf(from);
},
/** Find path to an executable in system `PATH`. @see https://github.com/npm/node-which */
which: which_1.default,
/**
* Given a glob pattern, resolve with list of files matching that pattern.
* @see https://github.com/isaacs/node-glob
*/
glob(pattern, options) {
return Promise.resolve((options ? (0, glob_1.glob)(pattern, options) : (0, glob_1.glob)(pattern)));
},
/** Sanitize a filename. @see https://github.com/parshap/node-sanitize-filename */
sanitizeName: sanitize_filename_1.default,
/** Create a hex digest of some file at `filePath`. */
async hash(filePath, algorithm = 'sha1') {
return await new Promise((resolve, reject) => {
const fileHash = node_crypto_1.default.createHash(algorithm);
const readStream = (0, node_fs_1.createReadStream)(filePath);
readStream.on('error', (e) => reject(new Error(`Cannot calculate ${algorithm} hash for '${filePath}'. Original error: ${e.message}`)));
readStream.on('data', (chunk) => fileHash.update(chunk));
readStream.on('end', () => resolve(fileHash.digest('hex')));
});
},
/**
* Returns a Walker instance (readable stream / async iterator).
* @see https://www.npmjs.com/package/klaw
*/
walk(dir, opts) {
return (0, klaw_1.default)(dir, opts);
},
/** Recursively create a directory. */
async mkdirp(dir) {
return await exports.fs.mkdir(dir, { recursive: true });
},
/**
* Walks a directory; callback is invoked with path joined with dir.
* @param dir - Directory path to start walking
* @param recursive - If true, walk subdirectories
* @param callback - Called for each path; return true to stop
* @returns The found path or null if not found
*/
/* eslint-disable promise/prefer-await-to-callbacks -- walkDir uses callback + stream .on() + Promise executor */
async walkDir(dir, recursive, callback) {
let isValidRoot = false;
let errMsg = null;
try {
isValidRoot = (await exports.fs.stat(dir)).isDirectory();
}
catch (e) {
errMsg = e instanceof Error ? e.message : String(e);
}
if (!isValidRoot) {
throw new Error(`'${dir}' is not a valid root directory` +
(errMsg ? `. Original error: ${errMsg}` : ''));
}
let walker;
let fileCount = 0;
let directoryCount = 0;
const timer = new timing_1.Timer().start();
return await new Promise(function (resolve, reject) {
let lastFileProcessed = Promise.resolve(undefined);
walker = (0, klaw_1.default)(dir, {
depthLimit: recursive ? -1 : 0,
});
walker
.on('data', function (item) {
if (walker) {
walker.pause();
}
if (!item.stats.isDirectory()) {
fileCount++;
}
else {
directoryCount++;
}
lastFileProcessed = (async () => {
try {
const done = await callback(item.path, item.stats.isDirectory());
if (done) {
resolve(item.path);
return item.path;
}
if (walker) {
walker.resume();
}
}
catch (err) {
reject(err);
}
})();
})
.on('error', function (err, item) {
logger_1.default.warn(`Got an error while walking '${item?.path ?? 'unknown'}': ${err.message}`);
if (isErrnoException(err) && err.code === 'ENOENT') {
logger_1.default.warn('All files may not have been accessed');
reject(err);
}
})
.on('end', function () {
(async () => {
try {
const file = await lastFileProcessed;
resolve(file ?? null);
}
catch (err) {
logger_1.default.warn(`Unexpected error: ${err instanceof Error ? err.message : err}`);
reject(err);
}
})();
});
}).finally(function () {
logger_1.default.debug(`Traversed ${(0, util_1.pluralize)('directory', directoryCount, true)} ` +
`and ${(0, util_1.pluralize)('file', fileCount, true)} ` +
`in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
if (walker) {
walker.destroy();
}
});
/* eslint-enable promise/prefer-await-to-callbacks */
},
/**
* Reads the closest `package.json` from absolute path `dir`.
* @throws If there were problems finding or reading `package.json`
*/
readPackageJsonFrom(dir, opts = {}) {
const cwd = exports.fs.findRoot(dir);
try {
return (0, read_pkg_1.readPackageSync)({ normalize: true, ...opts, cwd });
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
err.message = `Failed to read a \`package.json\` from dir \`${dir}\`:\n\n${message}`;
throw err;
}
},
/**
* Finds the project root directory from `dir`.
* @throws TypeError If `dir` is not a non-empty absolute path
* @throws Error If project root could not be found
*/
findRoot(dir) {
if (!dir || !node_path_1.default.isAbsolute(dir)) {
throw new TypeError('`findRoot()` must be provided a non-empty, absolute path');
}
const result = findRootCached({ cwd: dir });
if (!result) {
throw new Error(`\`findRoot()\` could not find \`package.json\` from ${dir}`);
}
return result;
},
access: node_fs_1.promises.access,
appendFile: node_fs_1.promises.appendFile,
chmod: node_fs_1.promises.chmod,
close: (0, node_util_1.promisify)(node_fs_1.close),
constants: node_fs_1.constants,
createWriteStream: node_fs_1.createWriteStream,
createReadStream: node_fs_1.createReadStream,
lstat: node_fs_1.promises.lstat,
/**
* Promisified fs.open. Resolves with a file descriptor (not FileHandle).
* Use fs.openFile for a FileHandle.
*/
open: (0, node_util_1.promisify)(node_fs_1.open),
openFile: node_fs_1.promises.open,
readdir: node_fs_1.promises.readdir,
read: (0, node_util_1.promisify)(node_fs_1.read),
readFile: node_fs_1.promises.readFile,
readlink: node_fs_1.promises.readlink,
realpath: node_fs_1.promises.realpath,
rename: node_fs_1.promises.rename,
stat: node_fs_1.promises.stat,
symlink: node_fs_1.promises.symlink,
unlink: node_fs_1.promises.unlink,
// TODO: replace with native promisify in Appium 4
write: bluebird_1.default.promisify(node_fs_1.write),
writeFile: node_fs_1.promises.writeFile,
/** @deprecated Use `constants.F_OK` instead. */
F_OK: node_fs_1.constants.F_OK,
/** @deprecated Use `constants.R_OK` instead. */
R_OK: node_fs_1.constants.R_OK,
/** @deprecated Use `constants.W_OK` instead. */
W_OK: node_fs_1.constants.W_OK,
/** @deprecated Use `constants.X_OK` instead. */
X_OK: node_fs_1.constants.X_OK,
};
exports.default = exports.fs;
//# sourceMappingURL=fs.js.map