@adonisjs/repl
Version:
REPL for AdonisJS
205 lines (204 loc) • 5.86 kB
JavaScript
import stringWidth from "string-width";
import useColors from "@poppinss/colors";
import { inspect, promisify } from "node:util";
import { Recoverable, start } from "node:repl";
const GLOBAL_NODE_PROPERTIES = [
"performance",
"global",
"clearInterval",
"clearTimeout",
"setInterval",
"setTimeout",
"queueMicrotask",
"clearImmediate",
"setImmediate",
"structuredClone",
"atob",
"btoa",
"fetch",
"crypto",
"navigator",
"localStorage",
"sessionStorage"
];
const TS_UTILS_HELPERS = [
"__extends",
"__assign",
"__rest",
"__decorate",
"__param",
"__esDecorate",
"__runInitializers",
"__propKey",
"__setFunctionName",
"__metadata",
"__awaiter",
"__generator",
"__exportStar",
"__createBinding",
"__values",
"__read",
"__spread",
"__spreadArrays",
"__spreadArray",
"__await",
"__asyncGenerator",
"__asyncDelegator",
"__asyncValues",
"__makeTemplateObject",
"__importStar",
"__importDefault",
"__classPrivateFieldGet",
"__classPrivateFieldSet",
"__classPrivateFieldIn"
];
var Repl = class {
#replOptions;
#longestCustomMethodName = 0;
#originalEval;
#compiler;
#historyFilePath;
#onReadyCallbacks = [];
#customMethods = {};
colors = useColors.ansi();
server;
constructor(options) {
const { compiler, historyFilePath, ...rest } = options || {};
this.#compiler = compiler;
this.#historyFilePath = historyFilePath;
this.#replOptions = rest;
}
#registerCustomMethodWithContext(name) {
const customMethod = this.#customMethods[name];
if (!customMethod) return;
const handler = (...args) => customMethod.handler(this, ...args);
Object.defineProperty(handler, "name", { value: customMethod.handler.name });
this.server.context[name] = handler;
}
#setupContext() {
this.addMethod("clear", function clear(repl, key) {
if (!key) console.log(repl.colors.red("Define a property name to remove from the context"));
else delete repl.server.context[key];
repl.server.displayPrompt();
}, {
description: "Clear a property from the REPL context",
usage: `clear ${this.colors.gray("(propertyName)")}`
});
this.addMethod("p", function promisify$1(_, fn) {
return promisify(fn);
}, {
description: "Promisify a function. Similar to Node.js \"util.promisify\"",
usage: `p ${this.colors.gray("(function)")}`
});
Object.keys(this.#customMethods).forEach((name) => {
this.#registerCustomMethodWithContext(name);
});
}
#isRecoverableError(error) {
return /^(Unexpected end of input|Unexpected token|' expected)/.test(error.message);
}
#eval(code, context, filename, callback) {
try {
const compiled = this.#compiler ? this.#compiler.compile(code, filename) : code;
return this.#originalEval(compiled, context, filename, callback);
} catch (error) {
if (this.#isRecoverableError(error)) {
callback(new Recoverable(error), null);
return;
}
callback(error, null);
}
}
#setupHistory() {
if (!this.#historyFilePath) return;
this.server.setupHistory(this.#historyFilePath, (error) => {
if (!error) return;
console.log(this.colors.red("Unable to write to the history file. Exiting"));
console.error(error);
process.exit(1);
});
}
#printContextHelp() {
console.log("");
console.log(this.colors.green("CONTEXT PROPERTIES/METHODS:"));
const context = Object.keys(this.server.context).reduce((result, key) => {
if (!this.#customMethods[key] && !GLOBAL_NODE_PROPERTIES.includes(key) && !TS_UTILS_HELPERS.includes(key)) result[key] = this.server.context[key];
return result;
}, {});
console.log(inspect(context, false, 1, true));
}
#printCustomMethodsHelp() {
console.log("");
console.log(this.colors.green("GLOBAL METHODS:"));
Object.keys(this.#customMethods).forEach((method) => {
const { options } = this.#customMethods[method];
const usage = this.colors.yellow(options.usage || method);
const spaces = " ".repeat(this.#longestCustomMethodName - options.width + 2);
const description = this.colors.dim(options.description || "");
console.log(`${usage}${spaces}${description}`);
});
}
#ls() {
this.#printCustomMethodsHelp();
this.#printContextHelp();
this.server.displayPrompt();
}
notify(message) {
console.log(this.colors.yellow().italic(message));
if (this.server) this.server.displayPrompt();
}
ready(callback) {
this.#onReadyCallbacks.push(callback);
return this;
}
addMethod(name, handler, options) {
const width = stringWidth(options?.usage || name);
if (width > this.#longestCustomMethodName) this.#longestCustomMethodName = width;
this.#customMethods[name] = {
handler,
options: Object.assign({ width }, options)
};
if (this.server) this.#registerCustomMethodWithContext(name);
return this;
}
getMethods() {
return this.#customMethods;
}
useCompiler(compiler) {
this.#compiler = compiler;
return this;
}
start(context) {
console.log("");
this.notify("Type \".ls\" to a view list of available context methods/properties");
this.server = start({
prompt: `> ${this.#compiler?.supportsTypescript ? "(ts) " : "(js) "}`,
input: process.stdin,
output: process.stdout,
terminal: process.stdout.isTTY && !Number.parseInt(process.env.NODE_NO_READLINE, 10),
useGlobal: true,
writer(output) {
return inspect(output, {
showProxy: false,
colors: true
});
},
...this.#replOptions
});
if (context) Object.keys(context).forEach((key) => {
this.server.context[key] = context[key];
});
this.server.defineCommand("ls", {
help: "View list of available context methods/properties",
action: this.#ls.bind(this)
});
this.#setupContext();
this.#setupHistory();
this.#originalEval = this.server.eval;
this.server.eval = this.#eval.bind(this);
this.server.displayPrompt();
this.#onReadyCallbacks.forEach((callback) => callback(this));
return this;
}
};
export { Repl };