UNPKG

kui-shell

Version:

This is the monorepo for Kui, the hybrid command-line/GUI electron-based Kubernetes tool

278 lines 12.5 kB
"use strict"; 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 }); const debug_1 = require("debug"); const debug = debug_1.default('core/plugins/scanner'); debug('loading'); const plugins_1 = require("./plugins"); const commandTree = require("../core/command-tree"); const errors_1 = require("../models/errors"); const tree_1 = require("../commands/tree"); class ScanCache { constructor(registrar) { this.commandToPlugin = {}; this.topological = {}; this.overrides = {}; this.usage = {}; this.flat = []; this.isSubtreeSynonym = {}; this.isSynonym = {}; this.registrar = registrar; } } class CommandRegistrarForScan extends commandTree.ImplForPlugins { constructor(plugin, scanCache) { super(plugin); this.scanCache = scanCache; this.cmdToPlugin = {}; } subtreeSynonym(route, master) { if (route !== master.route) { this.scanCache.isSubtreeSynonym[route] = true; this.scanCache.isSubtreeSynonym[master.route] = true; return super.subtreeSynonym(route, master, {}); } } listen(route, handler, options) { this.cmdToPlugin[route] = this.plugin; return super.listen(route, handler, options); } subtree(route, options) { return super.subtree(route, Object.assign({ listen: this.listen.bind(this) }, options)); } synonym(route, handler, master, options) { this.cmdToPlugin[route] = this.plugin; this.scanCache.isSynonym[route] = true; return super.synonym(route, handler, master, options); } } const loadPlugin = (route, pluginPath, scanCache) => __awaiter(void 0, void 0, void 0, function* () { debug('loadPlugin %s', route); const ctree = new CommandRegistrarForScan(route, scanCache); const pluginLoaderRef = yield Promise.resolve().then(() => require(pluginPath)); function isDirect(loader) { return typeof loader === 'function'; } const pluginLoader = isDirect(pluginLoaderRef) ? pluginLoaderRef : pluginLoaderRef.default; if (typeof pluginLoader === 'function') { scanCache.registrar[route] = yield pluginLoader(ctree, {}); const { cmdToPlugin } = ctree; for (const k in cmdToPlugin) { if (scanCache.commandToPlugin[k]) { debug('override', k, cmdToPlugin[k], scanCache.commandToPlugin[k]); scanCache.overrides[k] = cmdToPlugin[k]; } else { debug('not override', k, cmdToPlugin[k]); } scanCache.commandToPlugin[k] = cmdToPlugin[k]; } } }); const topologicalSortForScan = (scanCache, pluginPaths, iter, lastError, lastErrorAlreadyEmitted) => __awaiter(void 0, void 0, void 0, function* () { debug('topologicalSortForScan', iter); if (iter >= 100) { debug('unable to resolve plugins'); if (lastError) { if (!lastErrorAlreadyEmitted) { throw lastError; } throw new Error('Unable to resolve plugins'); } } let nUnresolved = 0; const unresolved = []; for (const route in pluginPaths) { debug('resolving %s', route); try { const module = { route, path: pluginPaths[route] }; yield loadPlugin(route, pluginPaths[route], scanCache); scanCache.flat.push(module); delete pluginPaths[route]; } catch (err) { const notFound = err.message.indexOf('Module not found') >= 0 || err.message.indexOf('Cannot find module') >= 0; if ((!notFound || iter > 10) && (lastError && lastError.message !== err.message)) { debug('not retrying'); console.error(err); lastErrorAlreadyEmitted = true; } debug('retry on', err); lastError = err; nUnresolved++; unresolved.push(route); } } if (nUnresolved > 0) { debug('nUnresolved', nUnresolved, unresolved); return topologicalSortForScan(scanCache, pluginPaths, iter + 1, lastError, lastErrorAlreadyEmitted); } else { debug('topologicalSortForScan done'); } }); exports.scanForModules = (dir, quiet = false, filter = () => true) => __awaiter(void 0, void 0, void 0, function* () { debug('scanForModules %s', dir); const fs = yield Promise.resolve().then(() => require('fs')); const path = yield Promise.resolve().then(() => require('path')); const colors = yield Promise.resolve().then(() => require('colors/safe')); try { const plugins = {}; const preloads = {}; const doScan = ({ modules, moduleDir, parentPath }) => { debug('doScan', modules); modules.forEach(module => { const modulePath = path.join(moduleDir, module); const name = (parentPath ? `${parentPath}/` : '') + module; if (module.charAt(0) === '@') { return doScan({ modules: fs.readdirSync(modulePath), moduleDir: modulePath, parentPath: module }); } function lookFor(filename, destMap, colorFn) { const pluginPath = path.join(moduleDir, module, filename); debug('lookFor', filename, pluginPath); if (fs.existsSync(pluginPath)) { if (!quiet) { debug('found', name); console.log(colors.green(' \u2713 ') + colorFn(filename.replace(/\..*$/, '')) + '\t' + path.basename(module)); } destMap[name] = pluginPath; } else { const backupPluginPath = path.join(modulePath, 'dist', filename); debug('lookFor2', filename, backupPluginPath); if (fs.existsSync(backupPluginPath)) { if (!quiet) { debug('found2', name); console.log(colors.green(' \u2713 ') + colorFn(filename.replace(/\..*$/, '')) + '\t' + path.basename(module)); } destMap[name] = backupPluginPath; } else { const backupPluginPath = path.join(modulePath, 'src/plugin', filename); debug('lookFor3', filename, backupPluginPath); if (fs.existsSync(backupPluginPath)) { if (!quiet) { debug('found3', name); console.log(colors.green(' \u2713 ') + colorFn(filename.replace(/\..*$/, '')) + '\t' + path.basename(module)); } destMap[name] = backupPluginPath; } else { const backupPluginPath = path.join(modulePath, 'plugin', filename); debug('lookFor4', filename, backupPluginPath); if (fs.existsSync(backupPluginPath)) { if (!quiet) { debug('found4', name); console.log(colors.green(' \u2713 ') + colorFn(filename.replace(/\..*$/, '')) + '\t' + path.basename(module)); } destMap[name] = backupPluginPath; } } } } } lookFor('plugin.js', plugins, colors.bold); lookFor('preload.js', preloads, colors.dim); }); }; const moduleDir = dir; debug('moduleDir', moduleDir); doScan({ modules: fs.readdirSync(moduleDir).filter(filter), moduleDir }); return { plugins, preloads }; } catch (err) { if (errors_1.isCodedError(err) && err.code !== 'ENOENT') { console.error('Error scanning for external plugins', err); } return {}; } }); const resolveFromLocalFilesystem = (scanCache, opts = {}) => __awaiter(void 0, void 0, void 0, function* () { debug('resolveFromLocalFilesystem'); const { dirname, join } = yield Promise.resolve().then(() => require('path')); const pluginRootAbsolute = process.env.PLUGIN_ROOT || opts.pluginRoot || join(__dirname, plugins_1.pluginRoot); debug('pluginRootAbsolute', pluginRootAbsolute); const clientHosted = yield exports.scanForModules(opts.pluginRoot || pluginRootAbsolute); let plugins = clientHosted.plugins || {}; let preloads = clientHosted.preloads || {}; if (!opts.externalOnly) { let clientRequired; try { const secondary = dirname(dirname(require.resolve('@kui-shell/core/package.json'))); clientRequired = yield exports.scanForModules(secondary, false, (filename) => !!filename.match(/^plugin-/)); } catch (err) { if (err.code !== 'ENOENT') { console.error('error scanning for client-required plugins', err); } } plugins = Object.assign({}, clientRequired.plugins, clientHosted.plugins); preloads = Object.assign({}, clientRequired.preloads, clientHosted.preloads); } debug('availablePlugins %s', JSON.stringify(plugins)); yield topologicalSortForScan(scanCache, plugins, 0); return preloads; }); exports.generatePrescanModel = (registrar, opts) => __awaiter(void 0, void 0, void 0, function* () { debug('generatePrescanModel', opts); const scanCache = new ScanCache(registrar); const preloads = yield resolveFromLocalFilesystem(scanCache, opts); const disambiguator = {}; for (const route in scanCache.commandToPlugin) { const A = route.split('/'); for (let idx = 1; idx < A.length; idx++) { const cmd = `/${A.slice(idx).join('/')}`; if (!disambiguator[cmd] && (route === cmd || !scanCache.commandToPlugin[route])) { disambiguator[cmd] = route; scanCache.commandToPlugin[cmd] = scanCache.commandToPlugin[route]; } else if (disambiguator[cmd]) { const subtree = route.substring(0, route.lastIndexOf('/')); if (!scanCache.isSubtreeSynonym[subtree]) { if (disambiguator[cmd] === cmd) { } else if (route === cmd) { disambiguator[cmd] = route; scanCache.commandToPlugin[cmd] = scanCache.commandToPlugin[route]; } else { disambiguator[cmd] = true; delete scanCache.commandToPlugin[cmd]; } } } } } return { preloads: Object.keys(preloads).map(route => ({ route, path: preloads[route] })), commandToPlugin: scanCache.commandToPlugin, topological: scanCache.topological, flat: scanCache.flat, overrides: scanCache.overrides, usage: scanCache.usage, disambiguator: undefined, catchalls: tree_1.getModel().catchalls, docs: undefined }; }); exports.assemble = (registrar, opts) => { return exports.generatePrescanModel(registrar, Object.assign({ assembly: true }, opts)); }; //# sourceMappingURL=scanner.js.map