import {Directive, OptionDefinition} from '../Directive' import * as colors from 'colors/safe' import * as repl from 'repl' // import * as tsNode from 'ts-node' import {globalRegistry} from '../../util' /** * Launch an interactive REPL shell from within the application. * This is very useful for debugging and testing things during development. * * By default, the shell launches a TypeScript interpreter, but you can use * the `--js` flag to get a JavaScript interpreter. * * @example * ```sh * pnpm cli -- shell * pnpm cli -- shell --js * ``` */ export class ShellDirective extends Directive { protected options: any = { welcome: `powered by Extollo, © ${(new Date()).getFullYear()} Garrett Mills\nAccess your application using the "app" global and @extollo/lib using the "lib" global.`, prompt: `${colors.blue('(')}extollo${colors.blue(') ➤ ')}`, } /** * The created Node.js REPL server. * @protected */ protected repl?: repl.REPLServer getDescription(): string { return 'launch an interactive shell inside your application' } getKeywords(): string | string[] { return ['shell'] } getHelpText(): string { return '' } getOptions(): OptionDefinition[] { return [ '--js | launch in JavaScript mode instead of TypeScript', ] } async handle(): Promise { const state: any = { globalRegistry, app: this.app(), lib: await import('../../index'), exports: {}, } await new Promise(res => { // Currently, there's no way to programmatically access the async context // of the REPL from this directive w/o requiring the user to perform manual // actions. So, instead, override the context on the GlobalRegistry to make // the current one the global default. globalRegistry.forceContextOverride() // Create the ts-node compiler service. // const replService = tsNode.createRepl() // const service = tsNode.create({...replService.evalAwarePartialHost}) // replService.setService(service) // We global these values into the REPL's state directly (using the `state` object // above), but since we're using a separate ts-node interpreter, we need to make it // aware of the globals using declaration syntax. // replService.evalCode(` // declare const lib: typeof import('@extollo/lib'); // declare const app: typeof lib['Application']; // declare const globalRegistry: typeof lib['globalRegistry']; // `) // Print the welome message and start the interpreter this.nativeOutput(this.options.welcome) this.repl = repl.start({ // Causes the REPL to use the ts-node interpreter service: // eval: !this.option('js', false) ? (...args) => replService.nodeEval(...args) : undefined, prompt: this.options.prompt, useGlobal: true, useColors: true, terminal: true, preview: true, }) // Add our globals into the REPL's context Object.assign(this.repl.context, state) // Wait for the REPL to exit this.repl.on('exit', () => res()) }) } }