UNPKG

@adonisjs/repl

Version:
205 lines (204 loc) 5.86 kB
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 };