kui-shell
Version:
This is the monorepo for Kui, the hybrid command-line/GUI electron-based Kubernetes tool
278 lines • 12.5 kB
JavaScript
;
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