@semo/cli
Version:
266 lines • 8.27 kB
JavaScript
import { error, formatRcOptions, splitComma, success, } from '@semo/core';
import { closeSync, createWriteStream, existsSync, openSync, readFileSync, statSync, } from 'fs';
import { ensureDirSync } from 'fs-extra';
import _ from 'lodash';
import repl from 'repl';
import shell from 'shelljs';
import yargsParser from 'yargs-parser';
/**
* Keep repl history
*
*/
export const replHistory = function (repl, file) {
try {
statSync(file);
repl.history = readFileSync(file, 'utf-8').split('\n').reverse();
repl.history.shift();
repl.historyIndex = -1; // will be incremented before pop
}
catch { }
const fd = openSync(file, 'a');
const wstream = createWriteStream(file, {
fd,
});
wstream.on('error', function (err) {
throw err;
});
repl.addListener('line', function (code) {
if (code && code !== '.history') {
wstream.write(code + '\n');
}
else {
repl.historyIndex++;
repl.history.pop();
}
});
process.on('exit', function () {
closeSync(fd);
});
repl.defineCommand('history', {
help: 'Show the history',
action: function () {
const out = [];
repl.history.forEach(function (v) {
out.push(v);
});
repl.output.write(out.reverse().join('\n') + '\n');
repl.displayPrompt();
},
});
};
export const reload = async (repl, argv) => {
const scriptName = argv.scriptName || 'semo';
let pluginsReturn = await argv.$core.invokeHook(`${scriptName}:repl`, _.isBoolean(argv.hook)
? {
reload: true,
mode: 'group',
}
: {
mode: 'group',
include: splitComma(argv.hook),
reload: true,
});
pluginsReturn = _.omitBy(pluginsReturn, _.isEmpty);
repl.context.Semo.hooks = formatRcOptions(pluginsReturn);
if (argv.extract && argv.extract.length > 0) {
argv.extract.forEach((keyPath) => {
const extracted = _.get(repl.context, keyPath) || {};
Object.keys(extracted).forEach((key) => {
repl.context[key] = extracted[key];
});
});
}
const hookReplCommands = await argv.$core.invokeHook('semo:repl_command');
Object.keys(hookReplCommands)
.filter((command) => {
return ![
'break',
'clear',
'editor',
'exit',
'help',
'history',
'load',
'reload',
'save',
].includes(command);
})
.forEach((command) => {
repl.defineCommand(command, hookReplCommands[command]);
});
success('Hooked files reloaded.');
};
export const extract = (repl, obj, keys = [], newObjName = '') => {
if (!keys || keys.length === 0) {
// If keys empty, extract all keys from obj
Object.keys(obj).forEach((key) => {
const value = _.get(obj, `${key}`);
if (value) {
Object.defineProperty(repl.context, key, {
value,
});
}
});
}
else {
const keysCast = _.castArray(keys);
keysCast.forEach((key) => {
const splitExtractKey = key.split('.');
const finalExtractKey = splitExtractKey.at(-1);
const value = _.get(obj, `${key}`);
if (value) {
if (!newObjName) {
Object.defineProperty(repl.context, finalExtractKey, {
value,
});
}
else {
if (!repl.context[newObjName]) {
repl.context[newObjName] = {};
}
Object.defineProperty(repl.context[newObjName], finalExtractKey, {
value,
});
}
}
});
}
};
export async function openRepl(context) {
const { Semo } = context;
const argv = Semo.argv;
const r = repl.start({
prompt: argv.prompt,
ignoreUndefined: true,
});
r.defineCommand('reload', {
help: 'Reload hooked files',
async action() {
this.clearBufferedCommand();
try {
await reload(r, argv);
}
catch (e) {
error(e.message);
}
this.displayPrompt();
},
});
r.defineCommand('shell', {
help: 'Execute shell commands',
async action(cmd) {
this.clearBufferedCommand();
try {
shell.exec(cmd);
}
catch { }
this.displayPrompt();
},
});
const requireAction = async function (input) {
// @ts-ignore
this.clearBufferedCommand();
try {
const opts = yargsParser(input);
const packages = {};
for (const part of opts._) {
if (_.isString(part)) {
const split = part.split(':');
if (split.length === 1) {
packages[part] = part;
}
else if (split.length > 1) {
packages[split[0]] = split[1];
}
}
}
for (const pack in packages) {
const imported = importPackage(argv)(pack);
Object.defineProperty(r.context, packages[pack], { value: imported });
}
}
catch { }
// @ts-ignore
this.displayPrompt();
};
// Add require and import command
r.defineCommand('require', {
help: 'Require npm packages',
action: requireAction,
});
r.defineCommand('import', {
help: 'import npm packages',
action: requireAction,
});
const hookReplCommands = await argv.$core.invokeHook('semo:repl_command');
Object.keys(hookReplCommands)
.filter((command) => {
return ![
'break',
'clear',
'editor',
'exit',
'help',
'history',
'load',
'reload',
'save',
].includes(command);
})
.forEach((command) => {
r.defineCommand(command, hookReplCommands[command]);
});
const Home = process.env.HOME + `/.${argv.scriptName}`;
ensureDirSync(Home);
if (!existsSync(Home)) {
shell.exec(`mkdir -p ${Home}`);
}
replHistory(r, `${Home}/.${argv.scriptName}_repl_history`);
// @ts-ignore
// context即为REPL中的上下文环境
r.context = Object.assign(r.context, context);
r.context.Semo.repl = r;
r.context.Semo.extract = (obj, keys = [], newObjName = '') => {
extract(r, obj, keys, newObjName);
};
corepl(r);
}
export const corepl = (cli) => {
const originalEval = cli.eval;
// @ts-ignore
cli.eval = function coEval(cmd, context, filename, callback) {
if (cmd.match(/^await\s+/) ||
(cmd.match(/.*?await\s+/) && cmd.match(/^\s*\{/))) {
if (cmd.match(/=/)) {
cmd = '(async function() { (' + cmd + ') })()';
}
else {
cmd = '(async function() { let _ = ' + cmd + '; return _;})()';
}
}
else if (cmd.match(/\W*await\s+/)) {
cmd =
'(async function() { (' +
cmd.replace(/^\s*(var|let|const)\s+/, '') +
') })()';
}
function done(val) {
return callback(null, val);
}
originalEval.call(cli, cmd, context, filename, function (err, res) {
if (err || !res || typeof res.then !== 'function') {
return callback(err, res);
}
else {
return res.then(done, callback);
}
});
};
return cli;
};
export const importPackage = (argv) => {
return (name, force = false) => {
return argv.$core.importPackage(name, 'repl-package-cache', true, force);
};
};
//# sourceMappingURL=repl.js.map