UNPKG

@appium/support

Version:

Support libs used across Appium packages

357 lines 14.1 kB
"use strict"; 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