diff --git a/package.json b/package.json index b93d669..810f9e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@extollo/lib", - "version": "0.13.3", + "version": "0.13.4", "description": "The framework library that lifts up your code.", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/cli/directive/ShellDirective.ts b/src/cli/directive/ShellDirective.ts index f5ab483..93a11ca 100644 --- a/src/cli/directive/ShellDirective.ts +++ b/src/cli/directive/ShellDirective.ts @@ -1,16 +1,26 @@ -import {Directive} from '../Directive' +import {Directive, OptionDefinition} from '../Directive' import * as colors from 'colors/safe' import * as repl from 'repl' -import {DependencyKey} from '../../di' +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.`, - prompt: `${colors.blue('(')}extollo${colors.blue(') ➤ ')}`, + 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(') ➤ ')}`, } /** @@ -31,17 +41,57 @@ export class ShellDirective extends Directive { 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'), - make: (target: DependencyKey, ...parameters: any[]) => this.make(target, ...parameters), + 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(this.options.prompt) + 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()) }) }