kui-shell
Version:
This is the monorepo for Kui, the hybrid command-line/GUI electron-based Kubernetes tool
591 lines • 32.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/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