UNPKG

@zowe/cli

Version:

Zowe CLI is a command line interface (CLI) that provides a simple and streamlined way to interact with IBM z/OS.

366 lines 18 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. * */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EditUtilities = exports.Prompt = void 0; const zos_files_for_zowe_sdk_1 = require("@zowe/zos-files-for-zowe-sdk"); const imperative_1 = require("@zowe/imperative"); const CompareBaseHelper_1 = require("../compare/CompareBaseHelper"); const fs_1 = require("fs"); const os_1 = require("os"); const path = require("path"); const LocalfileDataset_handler_1 = require("../compare/lf-ds/LocalfileDataset.handler"); const LocalfileUss_handler_1 = require("../compare/lf-uss/LocalfileUss.handler"); /** * enum of prompts to be used as input to {@link EditUtilities.promptUser} during the file editing process * @export * @enum */ var Prompt; (function (Prompt) { Prompt[Prompt["useStash"] = 0] = "useStash"; Prompt[Prompt["viewDiff"] = 1] = "viewDiff"; Prompt[Prompt["overwriteRemote"] = 2] = "overwriteRemote"; Prompt[Prompt["viewUpdatedRemote"] = 3] = "viewUpdatedRemote"; Prompt[Prompt["continueToUpload"] = 4] = "continueToUpload"; })(Prompt || (exports.Prompt = Prompt = {})); /** * A shared utility class that uss and ds handlers use for local file editing * @export * @class */ class EditUtilities { /** * Builds a temp path where local file will be saved. If uss file, file name will be hashed * to prevent any conflicts with file naming. A given filename will always result in the * same unique file path. * @param {ILocalFile} lfFile - object containing pertinent information about the local file during the editing process * @returns {Promise<string>} - returns unique file path for temp file * @memberof EditUtilities */ static buildTempPath(lfFile, commandParameters) { return __awaiter(this, void 0, void 0, function* () { var _a; // find the appropriate extension for either uss or ds const ussExt = lfFile.fileType === 'uss' && lfFile.fileName.includes(".") ? lfFile.fileName.split(".").pop() : ""; let ext = "." + (lfFile.fileType === 'uss' ? ussExt : (_a = commandParameters.arguments.extension) !== null && _a !== void 0 ? _a : "txt"); ext = ext === "." ? "" : ext; if (lfFile.fileType === 'uss') { // Hash in a repeatable way if uss fileName (in case presence of special chars) const crypto = require("crypto"); let hash = crypto.createHash('sha256').update(lfFile.fileName).digest('hex'); // shorten hash const hashLen = 10; hash = hash.slice(0, hashLen); return path.join((0, os_1.tmpdir)(), path.parse(lfFile.fileName).name + '_' + hash + ext); } return path.join((0, os_1.tmpdir)(), lfFile.fileName + ext); }); } /** * Check for temp path's existence (check if previously 'stashed'/temp edits exist) * @param {string} tempPath - unique file path for local file (stash/temp file) * @returns {Promise<boolean>} - promise that resolves to true if stash exists or false if doesn't * @memberof EditUtilities */ static checkForStash(tempPath) { return __awaiter(this, void 0, void 0, function* () { try { return (0, fs_1.existsSync)(tempPath); } catch (err) { throw new imperative_1.ImperativeError({ msg: 'Failure when checking for stash. Command terminated.', causeErrors: err }); } }); } /** * Collection of prompts to be used at different points in editing process * @param {Prompt} prompt - selected prompt from {@link Prompt} (enum object) * @param {Boolean} conflict - optional. true if detected conflict between local and remote files * @returns {Promise<boolean>} - promise whose resolution depends on user input * @memberof EditUtilities */ static promptUser(prompt, conflict) { return __awaiter(this, void 0, void 0, function* () { let input; let promptText; const promptPrefix = conflict ? 'CONFLICT: ' : ''; switch (prompt) { case Prompt.useStash: promptText = 'Keep and continue editing found temp file? y/n'; break; case Prompt.viewDiff: promptText = 'View diff between temp and mainframe files? y/n'; break; case Prompt.viewUpdatedRemote: promptText = promptPrefix + 'Remote has changed. View diff between local and mainframe files? y/n'; break; case Prompt.overwriteRemote: promptText = promptPrefix + 'Overwrite remote with local edits? (Answer after editing) y/n'; break; case Prompt.continueToUpload: promptText = promptPrefix + 'Ignore conflicts and overwrite remote? y/n'; break; } do { input = yield imperative_1.CliUtils.readPrompt(imperative_1.TextUtils.chalk.green(promptText)); } while (input != null && input.toLowerCase() != 'y' && input.toLowerCase() != 'n'); if (input == null) { throw new imperative_1.ImperativeError({ msg: imperative_1.TextUtils.chalk.red('No input provided. Command terminated. Temp file will persist.') }); } return input.toLowerCase() === 'y'; }); } /** * Download file and determine if downloading just to get etag (useStash) or to save file locally & get etag (!useStash) * @param {AbstractSession} session - the session object generated from the connected profile * @param {ILocalFile} lfFile - object containing pertinent information about the local file during the editing process * @param {boolean} useStash - should be true if don't want to overwrite local file when refreshing etag * @returns {ILocalFile} */ static localDownload(session, lfFile, useStash) { return __awaiter(this, void 0, void 0, function* () { // account for both useStash|!useStash and uss|ds when downloading const tempPath = useStash ? path.posix.join((0, os_1.tmpdir)(), "toDelete.txt") : lfFile.tempPath; const args = [ session, lfFile.fileName, { returnEtag: true, binary: lfFile.binary, encoding: lfFile.encoding, file: tempPath } ]; if (lfFile.fileType === 'uss') { lfFile.zosResp = yield zos_files_for_zowe_sdk_1.Download.ussFile(...args); lfFile.encoding = args[2].encoding; } else { lfFile.zosResp = yield zos_files_for_zowe_sdk_1.Download.dataSet(...args); } if (useStash) { yield this.destroyTempFile(path.posix.join((0, os_1.tmpdir)(), "toDelete.txt")); } return lfFile; }); } /** * Performs appropriate file comparison (either in browser or as a terminal diff) between local file and remote * Local file (lf) will then be opened in default editor * @param {AbstractSession} session - the session object generated from the connected profile * @param {IHandlerParameters} commandParameters - parameters supplied by args * @param {ILocalFile} lfFile - object containing pertinent information about the local file during the editing process * @param {boolean} promptUser - optional. if there are changes then prompt user to show diff, otherwise return * @returns {Promise<IZosFilesResponse>} - the response generated by {@link CompareBaseHelper.getResponse} * @memberof EditUtilities */ static fileComparison(session, commandParameters, lfFile, promptUser) { return __awaiter(this, void 0, void 0, function* () { const handlerDs = new LocalfileDataset_handler_1.default(); const handlerUss = new LocalfileUss_handler_1.default(); const helper = new CompareBaseHelper_1.CompareBaseHelper(commandParameters); const gui = imperative_1.ProcessUtils.isGuiAvailable(); const options = { name1: "local file", name2: "remote file" }; helper.browserView = gui === imperative_1.GuiResult.GUI_AVAILABLE; const lf = yield handlerDs.getFile1(session, commandParameters.arguments, helper); let mf; try { if (commandParameters.positionals[2].toString().includes('d')) { mf = yield handlerDs.getFile2(session, commandParameters.arguments, helper); } else { mf = yield handlerUss.getFile2(session, commandParameters.arguments, helper); } } catch (err) { throw new imperative_1.ImperativeError({ msg: imperative_1.TextUtils.chalk.red(err + '\nCommand terminated. Issue retrieving files for comparison.'), causeErrors: err }); } const localContent = helper.prepareContent(lf); const remoteContent = helper.prepareContent(mf); let viewUpdatedRemote = !promptUser; if (localContent !== remoteContent) { lfFile.conflict = true; } if (promptUser && lfFile.conflict) { viewUpdatedRemote = yield this.promptUser(Prompt.viewUpdatedRemote, lfFile.conflict); } if (!viewUpdatedRemote) { return; } const diffResponse = yield helper.getResponse(localContent, remoteContent, options); if (!helper.browserView) { if (diffResponse) { commandParameters.response.console.log('\n' + diffResponse.commandResponse); } else { throw new imperative_1.ImperativeError({ msg: imperative_1.TextUtils.chalk.red('Diff was unable to be generated') }); } } return diffResponse; }); } /** * Enable user to make their edits and wait for user input to indicate editing is complete * @param {ILocalFile} lfFile - object containing pertinent information about the local file during the editing process * @param {string} editor - optional parameter originally supplied by args * @memberof EditUtilities */ static makeEdits(lfFile, editor) { return __awaiter(this, void 0, void 0, function* () { if (lfFile.guiAvail) { imperative_1.ProcessUtils.openInEditor(lfFile.tempPath, editor, true); } return yield this.promptUser(Prompt.overwriteRemote, lfFile.conflict); }); } /** * Upload temp file with saved etag * - if matching etag: successful upload, destroy stash/temp -> END * - if non-matching etag: unsuccessful upload -> refresh etag -> perform file comparison/edit -> re-attempt upload * @param {AbstractSession} session - the session object generated from the connected profile * @param {IHandlerParameters} commandParameters - parameters supplied by args * @param {ILocalFile} lfFile - object containing pertinent information about the local file during the editing process * @returns {Promise<[boolean, boolean]>} - [resolves to true if uploading was successful and * false if not, resolves to true if user wishes to cancel command and false if not] * @memberof EditUtilities */ static uploadEdits(session, commandParameters, lfFile) { return __awaiter(this, void 0, void 0, function* () { const etagMismatchCode = 412; const args = [ session, lfFile.tempPath, lfFile.fileName, { binary: lfFile.binary, encoding: lfFile.encoding, etag: lfFile.zosResp.apiResponse.etag, returnEtag: true }, ]; let response; try { if (lfFile.fileType === 'uss') { response = yield zos_files_for_zowe_sdk_1.Upload.fileToUssFile(...args); } else { response = yield zos_files_for_zowe_sdk_1.Upload.fileToDataset(...args); } if (response.success) { // If matching etag & successful upload, destroy temp file -> END yield this.destroyTempFile(lfFile.tempPath); return [true, false]; } else { if (response.commandResponse.includes('412')) { return yield this.etagMismatch(session, commandParameters, lfFile); //returns [uploaded, canceled] } } } catch (err) { if (err.errorCode && err.errorCode == etagMismatchCode) { return yield this.etagMismatch(session, commandParameters, lfFile); } } throw new imperative_1.ImperativeError({ msg: imperative_1.TextUtils.chalk.red((response === null || response === void 0 ? void 0 : response.errorMessage) + 'Command terminated. Issue uploading stash. Temp file will persist'), causeErrors: response === null || response === void 0 ? void 0 : response.errorMessage }); }); } /** * When changes occur in the remote file, user will have to overwrite remote or account for the discrepancy between files * @param {AbstractSession} session - the session object generated from the connected profile * @param {IHandlerParameters} commandParameters - parameters supplied by args * @param {ILocalFile} lfFile - object containing pertinent information about the local file during the editing process * @returns {Promise<boolean>} - returns a boolean where true means command is canceled and false means continue * @memberof EditUtilities */ static etagMismatch(session, commandParameters, lfFile) { return __awaiter(this, void 0, void 0, function* () { lfFile.conflict = true; try { //alert user that the version of document they've been editing has changed //ask if they want to see changes on the remote file before continuing const viewUpdatedRemote = yield this.promptUser(Prompt.viewUpdatedRemote, lfFile.conflict); if (viewUpdatedRemote) { yield this.fileComparison(session, commandParameters, lfFile); } //ask if they want to keep editing or upload despite changes to remote const continueToUpload = yield this.promptUser(Prompt.continueToUpload, lfFile.conflict); // refresh etag, keep stash yield this.localDownload(session, lfFile, true); if (!continueToUpload) { // create more edits & open stash/lf in editor const readyToUpload = yield this.makeEdits(lfFile, commandParameters.arguments.editor); if (readyToUpload) { return yield EditUtilities.uploadEdits(session, commandParameters, lfFile); } else { return [false, true]; //[uploaded, canceled] } } return [false, false]; //[uploaded, canceled] } catch (err) { throw new imperative_1.ImperativeError({ msg: imperative_1.TextUtils.chalk.red('Command terminated. Issue with etag. Temp file will persist.'), causeErrors: err }); } }); } /** * Destroy path of temporary local file (remove stash) * @param {string} tempPath - unique file path for local file (stash) * @memberof EditUtilities */ static destroyTempFile(tempPath) { return __awaiter(this, void 0, void 0, function* () { try { (0, fs_1.unlinkSync)(tempPath); } catch (err) { throw new imperative_1.ImperativeError({ msg: 'Temporary file could not be deleted: ${tempPath}', causeErrors: err }); } }); } } exports.EditUtilities = EditUtilities; //# sourceMappingURL=Edit.utils.js.map