UNPKG

@zowe/imperative

Version:
163 lines 7.15 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.PluginRequireProvider = void 0; const Module = require("module"); const utilities_1 = require("../../../utilities"); const path = require("path"); const findUp = require("find-up"); const lodash = require("lodash"); /** * This class will allow imperative to intercept calls by plugins so that it can * provide them with the runtime instance of imperative / base cli when necessary. * * @future Currently this loader is only available from Imperative's internals but * work could be done to make this a true standalone package that either * Imperative depends on or ships as a separate folder under packages. * * @example <caption>Proper Use of the Module Loader</caption> * // Ideally this is the first thing that gets called by your application; however, * // the module loader can be created and destroyed at any point by your application. * * // Initializing the loader * PluginRequireProvider.createPluginHooks(["module-a", "module-b"]); * * // Now in all places of the application, module-a and module-b will be loaded * // from the package location of require.main (I.E the Host Package). This * // is useful when the Host Package has some sort of plugin infrastructure that * // might require modules to be injected to the plugins. * * // So this will always be the Host Package module regardless of where it was * // called from. * require("module-a"); * * // But this will act as normal * require("module-c"); * * // It is not necessary to cleanup the module loader before exiting. Your * // application lifecycle may require it to be brought up and down during the * // course of execution. With this in mind, a method has been provided to remove * // the hooks created by `createPluginHooks`. * * // Calling this * PluginRequirePriovider.destroyPluginHooks(); * * // Will now cause this to act as normal regardless of how it would have been * // injected before. * require("module-b"); * */ class PluginRequireProvider { /** * Create hooks for the specified modules to be injected at runtime. * * @param modules An array of modules to inject from the host application. * * @throws {PluginRequireAlreadyCreatedError} when hooks have already been added. */ static createPluginHooks(modules) { if (PluginRequireProvider.mInstance != null) { const { PluginRequireAlreadyCreatedError } = require("./errors/PluginRequireAlreadyCreatedError"); throw new PluginRequireAlreadyCreatedError(); } this.mInstance = new PluginRequireProvider(modules); } /** * Restore the default node require hook. * * @throws {PluginRequireNotCreatedError} when hooks haven't been added. */ static destroyPluginHooks() { if (PluginRequireProvider.mInstance == null) { const { PluginRequireNotCreatedError } = require("./errors/PluginRequireNotCreatedError"); throw new PluginRequireNotCreatedError(); } // Set everything back to normal Module.prototype.require = PluginRequireProvider.mInstance.origRequire; PluginRequireProvider.mInstance = undefined; } /** * This regular expression is used by the module loader to * escape any valid characters that might be present in provided * modules. */ static sanitizeExpression(module) { /* * This replaces special characters that might be present in a regular expression and an * npm package name. * * @see https://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript */ return lodash.escapeRegExp(module); } /** * Construct the class and create hooks into require. * @param modules The modules that should be injected from the runtime instance */ constructor(modules) { this.modules = modules; const hostPackageRoot = path.join(findUp.sync("package.json", { cwd: utilities_1.ImperativeConfig.instance.callerLocation }), ".."); const hostPackageNameLength = utilities_1.ImperativeConfig.instance.hostPackageName.length; // We must remember to escape periods from modules for regular expression // purposes. const internalModules = []; for (const module of modules) { internalModules.push(PluginRequireProvider.sanitizeExpression(module)); } /* * Check that the element (or module that we inject) is present at position 0. * It was designed this way to support submodule imports. * * Example: * If modules = ["@zowe/imperative"] * request = "@zowe/imperative/lib/errors" */ // This regular expression will match /(@zowe\/imperative)/.*/ /* * The ?: check after the group in the regular expression is to explicitly * require that a submodule import has to match. This is to account for the * case where one of the packages to be injected is some-test-module and * we are requiring some-test-module-from-npm. Without the slash, that * module is incorrectly matched and injected. */ const regex = this.regex = new RegExp(`^(${internalModules.join("|")})(?:\\/.*)?$`, "gm"); const origRequire = this.origRequire = Module.prototype.require; const origMain = this.origRequire.main; // Timerify the function if needed // Gave it a name so that we can more easily track it Module.prototype.require = function PluginModuleLoader(...args) { // Check to see if the module should be injected const request = args[0]; const doesUseOverrides = request.match(regex); if (doesUseOverrides) { // Next we need to check if this is the root module. If so, then // we need to remap the import. if (request.startsWith(utilities_1.ImperativeConfig.instance.hostPackageName)) { if (request === utilities_1.ImperativeConfig.instance.hostPackageName) { args[0] = "./"; } else { args[0] = `${hostPackageRoot}${request.substring(hostPackageNameLength)}`; } } // Inject it from the main module dependencies return origRequire.apply(origMain, args); } else { // Otherwise use the package dependencies return origRequire.apply(this, args); } }; } } exports.PluginRequireProvider = PluginRequireProvider; //# sourceMappingURL=PluginRequireProvider.js.map