UNPKG

@zowe/imperative

Version:
479 lines 17.7 kB
"use strict"; /* * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * * Copyright Contributors to the Zowe Project. * */ Object.defineProperty(exports, "__esModule", { value: true }); exports.IO = void 0; const fs = require("fs"); const path = require("path"); const os = require("os"); const error_1 = require("../../error"); const expect_1 = require("../../expect"); // use complete path to ExecUtils to avoid circular dependency that results from utilities/index const ExecUtils_1 = require("../../utilities/src/ExecUtils"); /** * This class will handle common sequences of node I/O and issue messages / * throw errors as neccessary. * @export * @class IO */ class IO { /** * Return whether input file is a directory or file * @static * @param {string} dirOrFile - file path * @returns {boolean} - true if file path is a directory, false otherwise * @memberof IO */ static isDir(dirOrFile) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(dirOrFile, "dirOrFile"); const stat = fs.statSync(dirOrFile); return stat.isDirectory(); } /** * Take an extension and prefix with a '.' identifier * @static * @param {string} extension - extension to normalize * @returns {string} - '.bin' for input 'bin' for example * @memberof IO */ static normalizeExtension(extension) { expect_1.ImperativeExpect.toNotBeNullOrUndefined(extension, "extension"); extension = extension.trim(); if (extension != null && extension.length > 0 && extension[0] !== ".") { // add a '.' character to the extension if omitted // (if someone specifies just "bin", make the extension ".bin" ) extension = "." + extension; } return extension; } /** * Wraps fs.existsSync so that we dont have to import fs unnecessarily * @static * @param {string} file - file to validate existence against * @returns true if file exists * @memberof IO */ static existsSync(file) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(file, "file"); return fs.existsSync(file); } /** * Create a directory and all subdirectories if they do not yet exist synchronously. * @static * @param {string} dir - directory to create * @return {undefined} * @memberof IO */ static createDirSync(dir) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(dir, "dir"); fs.mkdirSync(dir, { recursive: true }); } /** * Create all needed directories for an input directory in the form of: * first/second/third where first will contain director second and second * will contain directory third * @static * @param {string} dir - directory to create all sub directories for * @memberof IO * @deprecated Please use IO.createDirSync */ static createDirsSync(dir) { IO.createDirSync(dir); } /** * Create all necessary directories for a fully qualified file and its path, * for example, if filePath = oneDir/twoDir/threeDir/file.txt, * oneDir, twoDir, and threeDir will be created. * @static * @param {string} filePath [description] * @return {[type]} [description] * @memberof IO */ static createDirsSyncFromFilePath(filePath) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(filePath, "filePath"); IO.createDirSync(path.dirname(filePath)); } /** * Create a symbolic link to a directory. If the symbolic link already exists, * re-create it with the specified target directory. * * @param {string} newSymLinkPath - the path new symbolic link to be created * @param {string} existingDirPath - the path the existing directory that we will link to */ static createSymlinkToDir(newSymLinkPath, existingDirPath) { try { if (!fs.existsSync(newSymLinkPath)) { fs.symlinkSync(existingDirPath, newSymLinkPath, "junction"); return; } // Get the file status of the existing intended symlink to ensure it is a symlink. const fileStats = fs.lstatSync(newSymLinkPath); if (fileStats.isSymbolicLink()) { fs.unlinkSync(newSymLinkPath); fs.symlinkSync(existingDirPath, newSymLinkPath, "junction"); return; } } catch (exception) { throw new error_1.ImperativeError({ msg: "Failed to create symbolic link from '" + newSymLinkPath + "' to '" + existingDirPath + "'\n" + "Reason: " + exception.message + "\n" + "Full exception: " + exception }); } throw new error_1.ImperativeError({ msg: "The intended symlink '" + newSymLinkPath + "' already exists and is not a symbolic link. So, we did not create a symlink from there to '" + existingDirPath + "'." }); } /** * Create a directory and all subdirectories if they do not yet exist synchronously. * @static * @param {string} dir - the directory (do not include a file name) * @memberof IO * @deprecated Please use IO.createDirSync */ static mkdirp(dir) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(dir, "dir"); IO.createDirSync(dir); } /** * Wraps fs.readFileSync so that we dont have to import fs unnecessarily * or specify encoding. * @static * @param {string} file - file to read * @param normalizeNewLines - remove Windows line endings (\r\n) in favor of \n * @param binary - should the file be read in binary mode? If so, normalizeNewLines is ignored. If false, * the file will be read in UTF-8 encoding * @return Buffer - the content of the file * @memberof IO */ static readFileSync(file, normalizeNewLines = false, binary = false) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(file, "file"); if (binary) { return fs.readFileSync(file); } else { let content = fs.readFileSync(file, IO.UTF8).toString(); if (normalizeNewLines) { content = content.replace(/\r\n/g, "\n"); } return Buffer.from(content, IO.UTF8); } } /** * Create a Node.js Readable stream from a file * @param file - the file from which to create a read stream * @return Buffer - the content of the file * @memberof IO */ static createReadStream(file) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(file, "file"); return fs.createReadStream(file, { autoClose: true }); } /** * Create a Node.js Readable stream from a file * @param file - the file from which to create a read stream * @return Buffer - the content of the file * @memberof IO */ static createWriteStream(file) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(file, "file"); return fs.createWriteStream(file, { autoClose: true }); } /** * Process a string so that its line endings are operating system * appropriate before you save it to disk * (basically, if the user is on Windows, change \n to \r\n) * @static * @param {string} original - original input * @param {number} lastByte - last byte of previous input, if it is being processed in chunks * @returns {string} - input with removed newlines * @memberof IO */ static processNewlines(original, lastByte) { expect_1.ImperativeExpect.toNotBeNullOrUndefined(original, "Required parameter 'original' must not be null or undefined"); if (os.platform() !== IO.OS_WIN32) { return original; } let processed; if (typeof original === "string") { processed = original.replace(/([^\r])\n/g, "$1\r\n"); if ((lastByte == null || lastByte !== "\r".charCodeAt(0)) && original.startsWith("\n")) { return ("\r" + processed); } return processed; } else { const bufferList = []; let prevByte = lastByte; for (let i = 0; i < original.length; i++) { const currentByte = original[i]; // Check if previous byte is not Carriage Return (13) and if current byte is Line Feed (10) // eslint-disable-next-line @typescript-eslint/no-magic-numbers if (currentByte === 10 && prevByte !== 13) { // eslint-disable-next-line @typescript-eslint/no-magic-numbers bufferList.push(13); } bufferList.push(currentByte); prevByte = currentByte; } processed = Buffer.from(bufferList); return processed; } } /** * Get default text editor for a given operating system * @static * @returns {string} - text editor launch string * @memberof IO */ static getDefaultTextEditor() { const platform = os.platform(); if (platform === IO.OS_WIN32) { return "notepad"; } else if (platform === IO.OS_MAC) { return "open -a TextEdit"; } else if (platform === IO.OS_LINUX) { return "vi"; } } /** * Create a file * @static * @param {string} file - file to create * @memberof IO */ static createFileSync(file) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(file, "file"); fs.closeSync(fs.openSync(file, "w")); } /** * Set file access permissions so that only the current user will have access to the file. * On windows, it means "full control" for the current user. * On posix, it means read & write access for the current user. * Obviously, the current user must have permission to change permissions * on the specified file. * * @param {string} fileName - file name for which we modify access. * @throws An ImperativeError when the operation fails. */ static giveAccessOnlyToOwner(fileName) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(fileName, "fileName"); try { if (!IO.existsSync(fileName)) { throw new Error("Attempted to restrict access on a non-existent file."); } if (os.platform() === IO.OS_WIN32) { // On windows, we use an icacls command to prevent access const stdout = ExecUtils_1.ExecUtils.spawnAndGetOutput("icacls", [fileName, "/inheritancelevel:r", "/grant:r", `${os.userInfo().username}:F`, "/c"], { encoding: "utf8" }); if (!stdout.includes("Successfully processed 1 files; Failed processing 0 files")) { throw new Error(`icacls reports that it could not change file access:\n${stdout}`); } } else { // we are on Posix // give read & write permissions to owner let ownerPrivileges = fs.constants.S_IRUSR | fs.constants.S_IWUSR; // give execute permission, if owner had it before if (fs.statSync(fileName).mode & fs.constants.S_IXUSR) { ownerPrivileges |= fs.constants.S_IXUSR; } fs.chmodSync(fileName, ownerPrivileges); } } catch (errObj) { throw new error_1.ImperativeError({ msg: `Failed to restrict access to others on file = ${fileName}\nError Details: ${errObj.message}` }); } } /** * Create a file asynchronously * @static * @param {string} file - file to create * @param {string} content - content to write in the file * @return {[type]} [description] * @memberof IO */ static writeFileAsync(file, content) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(file, "file"); expect_1.ImperativeExpect.toNotBeNullOrUndefined(content, "Content to write to the file must not be null or undefined"); return new Promise((resolve, reject) => { fs.writeFile(file, content, IO.UTF8, (err) => { if (err != null) { reject(new error_1.ImperativeError({ msg: err.message })); } resolve(); }); }); } /** * Write a file * @static * @param {string} file - file to create * @param {string} content - content to write * @return {undefined} * @memberof IO */ static writeFile(file, content) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(file, "file"); expect_1.ImperativeExpect.toNotBeNullOrUndefined(content, "Content to write to the file must not be null or undefined"); const fd = fs.openSync(file, "w"); try { fs.writeFileSync(fd, content); } finally { fs.closeSync(fd); } } /** * Write an object to a file and set consistent formatting on the serialized * JSON object. * @static * @param {string} configFile - file to create * @param {Object} object - object to serialize * @return {undefined} * @memberof IO */ static writeObject(configFile, object) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(configFile, "configFile"); expect_1.ImperativeExpect.toNotBeNullOrUndefined(object, "content"); const fd = fs.openSync(configFile, "w"); try { fs.writeFileSync(fd, JSON.stringify(object, null, 2)); } finally { fs.closeSync(fd); } } /** * Delete a file * @static * @param {string} file: The file to delete * @memberof IO */ static deleteFile(file) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(file, "file"); if (fs.existsSync(file)) { fs.unlinkSync(file); } } /** * Delete a directory * @static * @param {string} dir: The directory to delete * @memberof IO */ static deleteDir(dir) { expect_1.ImperativeExpect.toBeDefinedAndNonBlank(dir, "dir"); fs.rmdirSync(dir); } /** * Recursively delete all files and subdirectories of the specified directory. * Ensure that we do not follow a symlink. Just delete the link. * * @params {string} pathToTreeToDelete - Path to top directory of the tree * to delete. */ static deleteDirTree(pathToTreeToDelete) { try { // if pathToTreeToDelete is a symlink, just delete the link file if (fs.existsSync(pathToTreeToDelete)) { const fileStats = fs.lstatSync(pathToTreeToDelete); if (fileStats.isSymbolicLink() || fileStats.isFile()) { fs.unlinkSync(pathToTreeToDelete); return; } // read all of the children of this directory fs.readdirSync(pathToTreeToDelete).forEach((nextChild) => { // recursively delete the child IO.deleteDirTree(pathToTreeToDelete + path.sep + nextChild); }); // delete our starting directory fs.rmdirSync(pathToTreeToDelete); } } catch (exception) { throw new error_1.ImperativeError({ msg: "Failed to delete the directory tree '" + pathToTreeToDelete + "'\nReason: " + exception.message + "\n" + "Full exception: " + exception }); } } /** * Delete a symbolic link. * * @param {string} symLinkPath - the path to a symbolic link to be deleted */ static deleteSymLink(symLinkPath) { try { if (!fs.existsSync(symLinkPath)) { return; } // Get the file status to determine if it is a symlink. const fileStats = fs.lstatSync(symLinkPath); if (fileStats.isSymbolicLink()) { fs.unlinkSync(symLinkPath); return; } } catch (ioExcept) { throw new error_1.ImperativeError({ msg: "Failed to delete the symbolic link '" + symLinkPath + "'\nReason: " + ioExcept.message + "\n" + "Full exception: " + ioExcept }); } throw new error_1.ImperativeError({ msg: "The specified symlink '" + symLinkPath + "' already exists and is not a symbolic link. So, we did not delete it." }); } } exports.IO = IO; /** * File delimiter * @deprecated Use `path.posix.sep` instead or `path.sep` for better cross-platform support * @static * @type {string} * @memberof IO */ IO.FILE_DELIM = path.posix.sep; /** * UTF8 identifier * @static * @memberof IO */ IO.UTF8 = "utf8"; /** * Windows OS identifier * @static * @memberof IO */ IO.OS_WIN32 = "win32"; /** * Mac OS identifier * @static * @memberof IO */ IO.OS_MAC = "darwin"; /** * Linux OS identifier * @static * @memberof IO */ IO.OS_LINUX = "linux"; //# sourceMappingURL=IO.js.map