UNPKG

@zowe/imperative

Version:
265 lines 15.1 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.onlyForTesting = exports.updateExtendersJson = void 0; exports.install = install; const PMFConstants_1 = require("../PMFConstants"); const path = require("path"); const fs = require("fs"); const jsonfile_1 = require("jsonfile"); const logger_1 = require("../../../../../logger"); const error_1 = require("../../../../../error"); const NpmFunctions_1 = require("../NpmFunctions"); const ConfigSchema_1 = require("../../../../../config/src/ConfigSchema"); const PluginManagementFacility_1 = require("../../PluginManagementFacility"); const ConfigurationLoader_1 = require("../../../ConfigurationLoader"); const UpdateImpConfig_1 = require("../../../UpdateImpConfig"); const security_1 = require("../../../../../security"); const semver = require("semver"); const config_1 = require("../../../../../config"); // Helper function to update extenders.json object during plugin install. // Returns true if the object was updated, and false otherwise const updateExtendersJson = (extendersJson, packageInfo, profile) => { if (!(profile.type in extendersJson.profileTypes)) { // If the type doesn't exist, add it to extenders.json and return extendersJson.profileTypes[profile.type] = { from: [packageInfo.name], version: profile.schema.version }; return true; } // Otherwise, only update extenders.json if the schema version is newer const existingTypeInfo = extendersJson.profileTypes[profile.type]; if (semver.valid(existingTypeInfo.version)) { if (profile.schema.version && semver.lt(profile.schema.version, existingTypeInfo.version)) { return false; } } extendersJson.profileTypes[profile.type] = { from: [packageInfo.name], version: profile.schema.version }; return true; }; exports.updateExtendersJson = updateExtendersJson; /** * Common function that abstracts the install process. This function should be called for each * package that needs to be installed. (ex: `sample-cli plugin install a b c d` -> calls install 4 * times) * * @TODO work needs to be done to support proper sharing of the plugins.json. As of now local plugins can only be reinstalled on the same machine. * (due to how the conversion to an absolute URI happens) * * @param {string} packageLocation A package name or location. This value can be a valid npm package * name or the location of an npm tar file or project folder. Also, * git URLs are also acceptable here (basically anything that `npm * install` supports). If this parameter is a relative path, it will * be converted to an absolute path prior to being passed to the * `npm install` command. * * @param {INpmRegistryInfo} registryInfo The npm registry to use. * * @param {boolean} [installFromFile=false] If installing from a file, the package location is * automatically interpreted as an absolute location. * It is assumed that the plugin.json file was previously * generated by this function which always ensures an * absolute path. Also, if this is true, we will not update * the plugins.json file since we are not adding/modifying * it. * @returns {string} The name of the plugin. */ function install(packageLocation_1, registryInfo_1) { return __awaiter(this, arguments, void 0, function* (packageLocation, registryInfo, installFromFile = false, verbose = false) { const iConsole = logger_1.Logger.getImperativeLogger(); let npmPackage = packageLocation; iConsole.debug(`Installing package: ${packageLocation}`); // Do some parsing on the package location in the case it isn't an absolute location // If // we are not installing from a file // and the location is not absolute. // Then // we will try to convert the URI (which is a file path by the above criteria) // to an absolute file path. If we can't resolve it locally, we'll leave it up to npm // to do what's best. if (!installFromFile && !path.isAbsolute(packageLocation)) { const tempLocation = path.resolve(npmPackage); iConsole.debug(`Package is not absolute, let's see if this is a local file: ${tempLocation}`); // Now that we have made the location absolute...does it actually exist if (fs.existsSync(tempLocation)) { npmPackage = tempLocation; iConsole.info(`Installing local package: ${npmPackage}`); } } try { iConsole.debug(`Installing from registry ${registryInfo.location}`); // Perform the npm install. iConsole.info("Installing packages...this may take some time."); (0, NpmFunctions_1.installPackages)(npmPackage, Object.assign({ prefix: PMFConstants_1.PMFConstants.instance.PLUGIN_INSTALL_LOCATION }, registryInfo.npmArgs), verbose); // We fetch the package name and version of newly installed plugin const packageInfo = (0, NpmFunctions_1.getPackageInfo)(npmPackage); const packageName = packageInfo.name; let packageVersion = packageInfo.version; iConsole.debug("Reading in the current configuration."); const installedPlugins = (0, jsonfile_1.readFileSync)(PMFConstants_1.PMFConstants.instance.PLUGIN_JSON); // Set the correct name and version by checking if package is an npm package, this is done // by searching for a / or \ as those are not valid characters for an npm package, but they // would be for a url or local file. if (packageLocation.search(/(\\|\/)/) === -1) { // Getting here means that the package installed was an npm package. So the package property // of the json file should be the same as the package name. npmPackage = packageName; const passedVersionIdx = packageLocation.indexOf("@"); if (passedVersionIdx !== -1) { packageVersion = packageLocation.substring(passedVersionIdx + 1); } } iConsole.debug(`Package version: ${packageVersion}`); const newPlugin = { package: npmPackage, location: registryInfo.location, version: packageVersion }; iConsole.debug("Updating the current configuration with new plugin:\n" + JSON.stringify(newPlugin, null, 2)); installedPlugins[packageName] = newPlugin; iConsole.debug("Updating configuration file = " + PMFConstants_1.PMFConstants.instance.PLUGIN_JSON); (0, jsonfile_1.writeFileSync)(PMFConstants_1.PMFConstants.instance.PLUGIN_JSON, installedPlugins, { spaces: 2 }); // get the plugin's Imperative config definition const requirerFunction = PluginManagementFacility_1.PluginManagementFacility.instance.requirePluginModuleCallback(packageName); const pluginImpConfig = ConfigurationLoader_1.ConfigurationLoader.load(null, packageInfo, requirerFunction); iConsole.debug(`Checking for global Zowe client configuration files to update.`); if (PMFConstants_1.PMFConstants.instance.PLUGIN_USING_CONFIG) { // Update the Imperative Configuration to add the profiles introduced by the recently installed plugin // This might be needed outside of PLUGIN_USING_CONFIG scenarios, but we haven't had issues with other APIs before const globalLayer = PMFConstants_1.PMFConstants.instance.PLUGIN_CONFIG.layers.find((layer) => layer.global && layer.exists); if (globalLayer && Array.isArray(pluginImpConfig.profiles)) { UpdateImpConfig_1.UpdateImpConfig.addProfiles(pluginImpConfig.profiles); const schemaInfo = PMFConstants_1.PMFConstants.instance.PLUGIN_CONFIG.getSchemaInfo(); if (schemaInfo.local && fs.existsSync(schemaInfo.resolved)) { let loadedSchema; try { // load schema from disk to prevent removal of profile types from other applications loadedSchema = ConfigSchema_1.ConfigSchema.loadSchema((0, jsonfile_1.readFileSync)(schemaInfo.resolved)); } catch (err) { iConsole.error("Error when adding new profile type for plugin %s: failed to parse schema", newPlugin.package); } // Only update global schema if we were able to load it from disk if (loadedSchema != null) { const extendersJson = config_1.ConfigUtils.readExtendersJson(); // Determine new profile types to add to schema let shouldUpdate = false; for (const profile of pluginImpConfig.profiles) { const existingType = loadedSchema.find((obj) => obj.type === profile.type); if (existingType == null) { loadedSchema.push(profile); } else { if (semver.valid(existingType.schema.version)) { if (semver.valid(profile.schema.version) && semver.gt(profile.schema.version, existingType.schema.version)) { existingType.schema = profile.schema; existingType.schema.version = profile.schema.version; } } else { existingType.schema = profile.schema; existingType.schema.version = profile.schema.version; } } shouldUpdate = (0, exports.updateExtendersJson)(extendersJson, packageInfo, profile) || shouldUpdate; } if (shouldUpdate) { // Update extenders.json (if necessary) after installing the plugin config_1.ConfigUtils.writeExtendersJson(extendersJson); } const schema = ConfigSchema_1.ConfigSchema.buildSchema(loadedSchema); ConfigSchema_1.ConfigSchema.updateSchema({ layer: "global", schema }); } } } } // call the plugin's postInstall function yield callPluginPostInstall(packageName, pluginImpConfig); iConsole.info("Plugin '" + packageName + "' successfully installed."); return packageName; } catch (e) { throw new error_1.ImperativeError({ msg: e.message, causeErrors: e }); } }); } /** * Call a plugin's lifecycle hook to enable a plugin to take some action * after the plugin has been installed. * * @param pluginPackageNm The package name of the plugin being installed. * @param pluginImpConfig The imperative configuration for this plugin. * * @throws ImperativeError. */ function callPluginPostInstall(pluginPackageNm, pluginImpConfig) { return __awaiter(this, void 0, void 0, function* () { const impLogger = logger_1.Logger.getImperativeLogger(); if (pluginImpConfig.pluginLifeCycle === undefined) { // pluginPostInstall was not defined by the plugin const credMgrInfo = security_1.CredentialManagerOverride.getCredMgrInfoByPlugin(pluginPackageNm); if (credMgrInfo !== null) { // this plugin is a known cred mgr override throw new error_1.ImperativeError({ msg: `The plugin '${pluginPackageNm}' attempted to override the CLI ` + `Credential Manager without providing a 'pluginLifeCycle' class. ` + `The previous Credential Manager remains in place.` }); } return; } // call the plugin's postInstall operation try { impLogger.debug(`Calling the postInstall function for plugin '${pluginPackageNm}'`); const requirerFun = PluginManagementFacility_1.PluginManagementFacility.instance.requirePluginModuleCallback(pluginPackageNm); const lifeCycleClass = requirerFun(pluginImpConfig.pluginLifeCycle); const lifeCycleInstance = new lifeCycleClass(); yield lifeCycleInstance.postInstall(); } catch (err) { throw new error_1.ImperativeError({ msg: `Unable to perform the post-install action for plugin '${pluginPackageNm}'.` + `\nReason: ${err.message}` }); } }); } /* The following functions are private to this module. Breaking changes * might be made at any time to any of the following functions. * Make no attempt to to call them externally. * They are only exported here to enable automated testing. * Only test programs should access 'onlyForTesting'. */ exports.onlyForTesting = { callPluginPostInstall: callPluginPostInstall }; //# sourceMappingURL=install.js.map