UNPKG

kui-shell

Version:

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

591 lines 32.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/repl'); debug('loading'); const encode_1 = require("./encode"); const split_1 = require("./split"); const command_1 = require("../models/command"); const mimic_dom_1 = require("../util/mimic-dom"); const entity_1 = require("../models/entity"); const execOptions_1 = require("../models/execOptions"); const events_1 = require("../core/events"); const history_1 = require("../models/history"); const usage_error_1 = require("../core/usage-error"); const capabilities_1 = require("../core/capabilities"); const types_1 = require("../util/types"); const async_1 = require("../util/async"); const symbol_table_1 = require("../core/symbol-table"); const tree_1 = require("../commands/tree"); const resolution_1 = require("../commands/resolution"); const listen_1 = require("../webapp/listen"); const status_1 = require("../webapp/status"); const print_1 = require("../webapp/print"); const oops_1 = require("../webapp/oops"); const tab_1 = require("../webapp/tab"); const prompt_1 = require("../webapp/prompt"); const block_1 = require("../webapp/block"); const minimist = require("yargs-parser"); class DirectReplEval { constructor() { this.name = 'DirectReplEval'; } apply(commandUntrimmed, execOptions, evaluator, args) { return evaluator.eval(args); } } exports.DirectReplEval = DirectReplEval; let currentEvaluatorImpl = new DirectReplEval(); exports.setEvaluatorImpl = (impl) => { debug('setting evaluator impl', impl.name); currentEvaluatorImpl = impl; }; const emptyPromise = () => { const emptyPromise = Promise.resolve({ blank: true }); return emptyPromise; }; const stripTrailer = (str) => str && str.replace(/\s+.*$/, ''); const unflag = (opt) => opt && stripTrailer(opt.replace(/^[-]+/, '')); let oopsHandler; exports.installOopsHandler = (fn) => { debug('installing oops handler'); oopsHandler = fn; }; const oops = (command, block, nextBlock) => (err) => { if (oopsHandler) { debug('invoking registered oops handler'); return oopsHandler(block, nextBlock)(err); } else { return oops_1.oops(command, block, nextBlock)(err); } }; const emptyExecOptions = () => new execOptions_1.DefaultExecOptions(); class InProcessExecutor { constructor() { this.name = 'InProcessExecutor'; } exec(commandUntrimmed, execOptions = emptyExecOptions()) { return __awaiter(this, void 0, void 0, function* () { const tab = execOptions.tab || tab_1.getCurrentTab(); const REPL = getImpl(tab); if (!capabilities_1.isHeadless()) { const curDic = symbol_table_1.default.read(tab); if (typeof curDic !== 'undefined') { if (!execOptions.env) { execOptions.env = {}; } execOptions.env = Object.assign({}, execOptions.env, curDic); } } const echo = !execOptions || execOptions.echo !== false; const nested = execOptions && execOptions.noHistory && !execOptions.replSilence; if (nested) execOptions.nested = nested; const block = (execOptions && execOptions.block) || block_1.getCurrentBlock(tab); const blockParent = block && block.parentNode; const prompt = block && prompt_1.getPrompt(block); if (!execOptions) execOptions = prompt.execOptions; if (execOptions && execOptions.pip) { const { container, returnTo } = execOptions.pip; try { const { drilldown } = yield Promise.resolve().then(() => require('../webapp/picture-in-picture')); return drilldown(tab, commandUntrimmed, undefined, document.querySelector(container), returnTo)(); } catch (err) { console.error(err); } } let nextBlock; if (!execOptions || (!execOptions.noHistory && echo)) { listen_1.unlisten(prompt); nextBlock = (execOptions && execOptions.nextBlock) || block.cloneNode(true); nextBlock.querySelector('input').value = ''; } else { nextBlock = execOptions && execOptions.nextBlock; } if (nextBlock) { block_1.removeAnyTemps(nextBlock); } const command = commandUntrimmed.trim().replace(split_1.patterns.commentLine, ''); if (!command) { if (block) { status_1.setStatus(block, "valid-response"); block_1.installBlock(blockParent, block, nextBlock)(); } return emptyPromise(); } if (execOptions && execOptions.echo && prompt) { prompt.value = commandUntrimmed; } try { if (block && !nested && echo) { status_1.setStatus(block, "processing"); prompt.readOnly = true; } const argv = split_1.split(command); if (argv.length === 0) { if (block) { status_1.setStatus(block, "valid-response"); block_1.installBlock(blockParent, block, nextBlock)(); } return emptyPromise(); } if (!execOptions || !execOptions.noHistory) { if (!execOptions || !execOptions.quiet) { execOptions.history = history_1.default.add({ raw: command }); } } const argvNoOptions = argv.filter(_ => _.charAt(0) !== '-'); const evaluator = yield tree_1.getModel().read(argvNoOptions, execOptions); if (resolution_1.isSuccessfulCommandResolution(evaluator)) { const _usage = evaluator.options && evaluator.options.usage; const usage = _usage && _usage.fn ? _usage.fn(_usage.command) : _usage; if (execOptions && execOptions.failWithUsage && !usage) { debug('caller needs usage model, but none exists for this command', evaluator); return false; } const builtInOptions = [{ name: '--quiet', alias: '-q', hidden: true, boolean: true }]; if (!usage || !usage.noHelp) { const help = { name: '--help', hidden: true, boolean: true }; if (!usage || !usage.noHelpAlias) { help.alias = '-h'; } builtInOptions.push(help); } const commandFlags = (evaluator.options && evaluator.options.flags) || (evaluator.options && evaluator.options.synonymFor && evaluator.options.synonymFor.options && evaluator.options.synonymFor.options.flags) || {}; const optional = builtInOptions.concat((evaluator.options && evaluator.options.usage && evaluator.options.usage.optional) || []); const optionalBooleans = optional && optional.filter(({ boolean }) => boolean).map(_ => unflag(_.name)); const optionalAliases = optional && optional .filter(({ alias }) => alias) .reduce((M, { name, alias }) => { M[unflag(alias)] = unflag(name); return M; }, {}); const allFlags = { configuration: Object.assign({ 'camel-case-expansion': false }, (usage && usage.configuration) || {}), boolean: (commandFlags.boolean || []).concat(optionalBooleans || []), alias: Object.assign({}, commandFlags.alias || {}, optionalAliases || {}), narg: optional && optional.reduce((N, { name, alias, narg }) => { if (narg) { N[unflag(name)] = narg; N[unflag(alias)] = narg; } return N; }, {}) }; const parsedOptions = minimist(argv, allFlags); const argvNoOptions = parsedOptions._; if ((!usage || !usage.noHelp) && parsedOptions.help && evaluator.options && evaluator.options.usage) { if (execOptions && execOptions.failWithUsage) { return evaluator.options.usage; } else { return oops(command, block, nextBlock)(new usage_error_1.UsageError({ usage: evaluator.options.usage })); } } if (usage && usage.strict) { const { strict: cmd, onlyEnforceOptions = false, required = [], oneof = [], optional: _optional = [] } = usage; const optLikeOneOfs = oneof.filter(({ command, name = command }) => name.charAt(0) === '-'); const positionalConsumers = _optional.filter(({ name, alias, consumesPositional }) => consumesPositional && (parsedOptions[unflag(name)] || parsedOptions[unflag(alias)])); const optional = builtInOptions.concat(_optional).concat(optLikeOneOfs); const positionalOptionals = optional.filter(({ positional }) => positional); const nPositionalOptionals = positionalOptionals.length; const args = argvNoOptions; const nPositionalsConsumed = positionalConsumers.length; const nRequiredArgs = required.length + (oneof.length > 0 ? 1 : 0) - nPositionalsConsumed; const optLikeActuals = optLikeOneOfs.filter(({ name, alias = '' }) => Object.prototype.hasOwnProperty.call(parsedOptions, unflag(name)) || Object.prototype.hasOwnProperty.call(parsedOptions, unflag(alias))); const nOptLikeActuals = optLikeActuals.length; const cmdArgsStart = args.indexOf(cmd); const nActualArgs = args.length - cmdArgsStart - 1 + nOptLikeActuals; for (const optionalArg in parsedOptions) { if (optionalArg === '_' || parsedOptions[optionalArg] === false) { continue; } const enforceThisOption = onlyEnforceOptions === undefined || typeof onlyEnforceOptions === 'boolean' ? true : !!onlyEnforceOptions.find(_ => _ === `-${optionalArg}` || _ === `--${optionalArg}`); if (!enforceThisOption) { continue; } const match = optional.find(({ name, alias }) => { return (stripTrailer(alias) === `-${optionalArg}` || stripTrailer(name) === `-${optionalArg}` || stripTrailer(name) === `--${optionalArg}`); }); if (!match) { debug('unsupported optional paramter', optionalArg); const message = `Unsupported optional parameter ${optionalArg}`; const err = new usage_error_1.UsageError({ message, usage }); err.code = 499; debug(message, args, parsedOptions, optional, argv); if (execOptions && execOptions.failWithUsage) { return err; } else { return oops(command, block, nextBlock)(err); } } else if ((match.boolean && typeof parsedOptions[optionalArg] !== 'boolean') || (match.file && typeof parsedOptions[optionalArg] !== 'string') || (match.booleanOK && !(typeof parsedOptions[optionalArg] === 'boolean' || typeof parsedOptions[optionalArg] === 'string')) || (match.numeric && typeof parsedOptions[optionalArg] !== 'number') || (match.narg > 1 && !Array.isArray(parsedOptions[optionalArg])) || (!match.boolean && !match.booleanOK && !match.numeric && (!match.narg || match.narg === 1) && !(typeof parsedOptions[optionalArg] === 'string' || typeof parsedOptions[optionalArg] === 'number' || typeof parsedOptions[optionalArg] === 'boolean')) || (match.allowed && !match.allowed.find(_ => _ === parsedOptions[optionalArg] || _ === '...' || (match.allowedIsPrefixMatch && parsedOptions[optionalArg].toString().indexOf(_.toString()) === 0)))) { debug('bad value for option', optionalArg, match, parsedOptions, args, allFlags); const expectedMessage = match.boolean ? ', expected boolean' : match.numeric ? ', expected a number' : match.file ? ', expected a file path' : ''; const message = `Bad value for option ${optionalArg}${expectedMessage}${typeof parsedOptions[optionalArg] === 'boolean' ? '' : ', got ' + parsedOptions[optionalArg]}${match.allowed ? ' expected one of: ' + match.allowed.join(', ') : ''}`; const error = new usage_error_1.UsageError({ message, usage }); debug(message, match); error.code = 498; if (execOptions && execOptions.failWithUsage) { return error; } else { return oops(command, block, nextBlock)(error); } } } if (!onlyEnforceOptions && nActualArgs !== nRequiredArgs) { if (!(nActualArgs >= nRequiredArgs && nActualArgs <= nRequiredArgs + nPositionalOptionals)) { const implicitIdx = required.findIndex(({ implicitOK }) => implicitOK !== undefined); const { currentSelection } = yield Promise.resolve().then(() => require('../webapp/views/sidecar')); const selection = currentSelection(tab); let nActualArgsWithImplicit = nActualArgs; if (implicitIdx >= 0 && selection && required[implicitIdx].implicitOK.find(_ => _ === selection.type || _ === selection.prettyType)) { nActualArgsWithImplicit++; const notNeededIfImplicit = required.filter(({ notNeededIfImplicit }) => notNeededIfImplicit); nActualArgsWithImplicit += notNeededIfImplicit.length; } if (nActualArgsWithImplicit !== nRequiredArgs) { const message = nRequiredArgs === 0 && nPositionalOptionals === 0 ? 'This command accepts no positional arguments' : nPositionalOptionals > 0 ? 'This command does not accept this number of arguments' : `This command requires ${nRequiredArgs} parameter${nRequiredArgs === 1 ? '' : 's'}, but you provided ${nActualArgsWithImplicit === 0 ? 'none' : nActualArgsWithImplicit}`; const err = new usage_error_1.UsageError({ message, usage }); err.code = 497; debug(message, cmd, nActualArgs, nRequiredArgs, args, optLikeActuals); if (execOptions && execOptions.nested) { debug('returning usage error'); return err; } else { debug('broadcasting usage error'); return oops(command, block, nextBlock)(err); } } else { debug('repl selection', selection); args.splice(implicitIdx, cmdArgsStart + 1, selection.namespace ? `/${selection.namespace}/${selection.name}` : selection.name); debug('spliced in implicit argument', cmdArgsStart, implicitIdx, args); } } } } if (evaluator.options && evaluator.options.requiresLocal && !capabilities_1.hasLocalAccess()) { debug('command does not work in a browser'); const err = new Error('Command requires local access'); err.code = 406; return oops(command, block, nextBlock)(err); } if (capabilities_1.isHeadless() && !parsedOptions.cli && !parsedOptions.help && ((process.env.DEFAULT_TO_UI && !parsedOptions.cli) || (evaluator.options && evaluator.options.needsUI))) { Promise.resolve().then(() => require('../main/headless')).then(({ createWindow }) => createWindow(argv, evaluator.options.fullscreen, evaluator.options)); return Promise.resolve(true); } if (execOptions && execOptions.placeholder && prompt) { prompt.value = execOptions.placeholder; } return Promise.resolve() .then(() => { events_1.default.emit('/command/start', { tab, route: evaluator.route, command, execType: (execOptions && execOptions.type) || command_1.ExecType.TopLevel }); return currentEvaluatorImpl.apply(commandUntrimmed, execOptions, evaluator, { tab, REPL, block: block || true, nextBlock, argv, command, execOptions, argvNoOptions, parsedOptions, createOutputStream: execOptions.createOutputStream || (() => __awaiter(this, void 0, void 0, function* () { if (capabilities_1.isHeadless()) { const { streamTo: headlessStreamTo } = yield Promise.resolve().then(() => require('../main/headless-support')); return headlessStreamTo(); } else { return Promise.resolve(print_1.streamTo(tab, block)); } })) }); }) .then((response) => __awaiter(this, void 0, void 0, function* () { if (execOptions.rawResponse) { return response; } if (response === undefined) { console.error(argv); throw new Error('Internal Error'); } if (block && block.isCancelled) { debug('squashing output of cancelled command'); return; } if (entity_1.isEntitySpec(response) && response.verb === 'delete') { const { maybeHideEntity } = yield Promise.resolve().then(() => require('../webapp/views/sidecar')); if (maybeHideEntity(tab, response) && nextBlock) { } } if (usage_error_1.UsageError.isUsageError(response)) { throw response; } evaluator.success({ tab, type: (execOptions && execOptions.type) || command_1.ExecType.TopLevel, isDrilldown: execOptions.isDrilldown, command, parsedOptions }); const render = execOptions && !!execOptions.render; if (!render && ((execOptions && execOptions.replSilence) || nested || entity_1.isLowLevelLoop(response) || mimic_dom_1.ElementMimic.isFakeDom(block))) { debug('passing control back to prompt processor or headless'); return Promise.resolve(response); } else { const resultDom = render ? print_1.replResult() : block.querySelector('.repl-result'); return new Promise(resolve => { print_1.printResults(block, nextBlock, tab, resultDom, echo && !render, execOptions, parsedOptions, command, evaluator)(response) .then(() => { if (render) { resolve(resultDom.parentElement); } else if (echo) { setTimeout(() => { block_1.installBlock(blockParent, block, nextBlock)(); resolve(response); }, 100); } else { prompt_1.getPrompt(block).focus({ preventScroll: true }); resolve(response); } }) .catch((err) => { console.error(err); if (execOptions && execOptions.noHistory) { throw err; } else { oops(command, block, nextBlock)(err); } }); }); } })) .catch((err) => { const returnIt = execOptions && execOptions.failWithUsage; const rethrowIt = execOptions && execOptions.rethrowErrors; const reportIt = execOptions && execOptions.reportErrors; if (returnIt) { debug('returning command execution error', err.code, err); return err; } else if (capabilities_1.isHeadless()) { debug('rethrowing error because we are in headless mode', err); throw err; } else { err = evaluator.error(command, tab, (execOptions && execOptions.type) || command_1.ExecType.TopLevel, err); if (!nested && !rethrowIt) { debug('reporting command execution error to user via repl', err); oops(command, block, nextBlock)(err); } else { debug('rethrowing command execution error', err); if (reportIt) { debug('also reporting command execution error to user via repl', err); oops(command, block, nextBlock)(err); } throw err; } } }); } } catch (err) { const e = err; if (e.code !== 404) { console.error(err); } if (execOptions && execOptions.failWithUsage) { return e; } else if (capabilities_1.isHeadless()) { throw e; } console.error('catastrophic error in repl'); console.error(e); if (execOptions.nested) { return; } const blockForError = block || block_1.getCurrentProcessingBlock(tab); return Promise.resolve(e.message).then(message => { if (types_1.isHTML(message)) { e.message = message; oops(command, block, nextBlock)(e); } else { const cmd = oops_1.showHelp(command, blockForError, nextBlock, e); const resultDom = blockForError.querySelector('.repl-result'); return Promise.resolve(cmd) .then(print_1.printResults(blockForError, nextBlock, tab, resultDom)) .then(() => block_1.installBlock(blockForError.parentNode, blockForError, nextBlock)()); } }); } }); } } let currentExecutorImpl = new InProcessExecutor(); exports.exec = (commandUntrimmed, execOptions = emptyExecOptions()) => { return currentExecutorImpl.exec(commandUntrimmed, execOptions); }; exports.doEval = ({ block = block_1.getCurrentBlock(), prompt = prompt_1.getPrompt(block) } = {}) => { const command = prompt.value.trim(); if (block.completion) { block.completion(prompt.value); } else { return exports.exec(command, new execOptions_1.DefaultExecOptionsForTab(tab_1.getTabFromTarget(prompt))); } }; exports.qexec = (command, block, contextChangeOK, execOptions, nextBlock) => { return exports.exec(command, Object.assign({ block: block, nextBlock: nextBlock, noHistory: true, contextChangeOK }, execOptions, { type: command_1.ExecType.Nested })); }; exports.qfexec = (command, block, nextBlock, execOptions) => { return exports.qexec(command, block, true, execOptions, nextBlock); }; exports.rexec = (command, execOptions = emptyExecOptions()) => { return exports.qexec(command, undefined, undefined, Object.assign({ raw: true }, execOptions)); }; exports.pexec = (command, execOptions) => { return exports.exec(command, Object.assign({ echo: true, type: command_1.ExecType.ClickHandler }, execOptions)); }; exports.click = (command, evt) => __awaiter(void 0, void 0, void 0, function* () { const { drilldown } = yield Promise.resolve().then(() => require('../webapp/picture-in-picture')); const tab = tab_1.getTabFromTarget(evt.currentTarget); yield drilldown(tab, command)(evt); }); function update(tab, command, execOptions) { return __awaiter(this, void 0, void 0, function* () { const [resource, { showEntity }] = yield Promise.all([ exports.pexec(command, Object.assign({ echo: false, alreadyWatching: true, noHistory: true }, execOptions)), Promise.resolve().then(() => require('../webapp/views/sidecar')) ]); yield showEntity(tab, resource); }); } exports.update = update; exports.setExecutorImpl = (impl) => { currentExecutorImpl = impl; }; function semicolonInvoke(opts) { return __awaiter(this, void 0, void 0, function* () { const commands = opts.command.split(/\s*;\s*/); if (commands.length > 1) { debug('semicolonInvoke', commands); const result = yield async_1.promiseEach(commands.filter(_ => _), (command) => __awaiter(this, void 0, void 0, function* () { const block = block_1.subblock(); if (typeof opts.block !== 'boolean') { opts.block.querySelector('.repl-result').appendChild(block); } const entity = yield exports.qexec(command, block, undefined, Object.assign({}, opts.execOptions, { quiet: false })); if (entity === true) { return block; } else { block.remove(); return entity; } })); return result; } }); } exports.semicolonInvoke = semicolonInvoke; function getImpl(tab) { const impl = { qexec: exports.qexec, rexec: exports.rexec, pexec: exports.pexec, click: exports.click, update, semicolonInvoke, encodeComponent: encode_1.default, split: split_1.split }; tab.REPL = impl; return impl; } exports.getImpl = getImpl; //# sourceMappingURL=exec.js.map