UNPKG

@kwaeri/wizard

Version:

The @kwaeri/wizard component module of the @kwaeri/node-kit platform.

194 lines (160 loc) 6.09 kB
/*----------------------------------------------------------------------------- * @package: node-kit wizard * @author: Richard B Winters * @copyright: 2014-2020 Massively Modified, Inc. * @license: Apache-2.0 <http://www.apache.org/licenses/LICENSE-2.0> * @version: 0.2.0 *---------------------------------------------------------------------------*/ 'use strict' // INCLUDES import * as rl from 'readline'; import * as Event from 'events'; import { FilesystemDescriptor } from '@kwaeri/filesystem'; import { BaseServiceProvider, ServiceProviderSubscriptions, ServiceProviderHelpText, ServiceEventBits } from '@kwaeri/service'; import debug from 'debug'; // DEFINES /* Configure Debug module support */ let DEBUG = debug( 'nkm:wizard' ); /* Our UserPrompt Interface */ let readline = rl; /* A type for async Wizard responses */ export type WizardResponsePromise = { answers: Array<string>; } /** * BaseWizard defines the interface for any service provider which will * implement a wizard-type service * * @since 0.1.0 */ export interface BaseWizardService { run<T extends WizardResponsePromise>( options: FilesystemDescriptor ): Promise<T> question1( instance: rl.ReadLine, answers: Array<any>, options?: FilesystemDescriptor ): Promise<any> setServiceEventMetadata( data: ServiceEventBits ): void; } /** * WizardService Class * * The { WizardService } is the class from which all wizard services should inherit * from. A wizard service is one that prompts users for information, and will [or should] * always need the readline class, and have at least a pair of methods 'run' and `question1`. * * This class helps to sensibly build a derived service provider, it should not be * used directly. Instead, developers should extend from the WizardServiceProvider class. */ export abstract class WizardService extends Event.EventEmitter implements BaseWizardService { constructor() { super(); } abstract run<T extends WizardResponsePromise>( options: FilesystemDescriptor ): Promise<T>; abstract question1( instance: rl.ReadLine, answers: Array<any>, options: FilesystemDescriptor ): Promise<any>; setServiceEventMetadata( data: ServiceEventBits): void { this.emit( 'ServiceEvent', data ); } } /** * WizardServiceProvider Class * * The { WizardServiceProvider } class implements a facility for presenting * a series of user promopts via CLI, in order to gather information * for a task, much in the same fashion as that of a wizard. */ export abstract class WizardServiceProvider extends WizardService implements BaseServiceProvider { /** * An array of user prompt functions * * @var { Array<Function>} */ prompts: Array<Function>; /** * Class constructor * * @param { void } * * @since 0.1.10 */ constructor() { super(); } abstract getServiceProviderSubscriptions( options: any ): ServiceProviderSubscriptions; abstract getServiceProviderSubscriptionHelpText<T extends ServiceProviderHelpText>( options: any ): T; /** * A method which runs a sequence of user prompts through a CLI * * @param { FilesystemDescriptor } options Any options that might have been provided preemptively * * @return { Promise<WizardResponsePromise> } An array of answers * * @since 0.1.10 */ async run<T extends WizardResponsePromise>( options: FilesystemDescriptor ): Promise<T> { // Any time a Wizard is run, we need a readline interface: let userPrompt = readline.createInterface ( { input: process.stdin, output: process.stdout } ); // As well as an answers array: let answerArray = []; DEBUG( `Call user prompts` ); let answers = await this.question1( userPrompt, answerArray, options ); // Start the wizard: return Promise.resolve( <T>answers ); } /** * A method which implements the first user prompt for a setup wizard that is * run for assisting the automaton with generating file contents. * * @param { rl.ReadLine } instance An instance of readline.createInterface() * @param { Array<string> } answers An array filled with answers from the automaton project generator wizard. * @param { Array<Function> } prompts An array of user prompt functions * @param { FilesystemDescriptor } options An object with options for specifying * * @return { Promise<any> } Returns a promise for allowing asynchronous chaining * * @since 0.1.10 */ async question1( instance: rl.ReadLine, answers: Array<any>, options?: FilesystemDescriptor ): Promise<any> { instance.question ( `This is a @kwaeri/node-kit wizard example user-prompt question. Use as many pro-\n` + `mpts as you'd like, simply assign the next prompts resolved promise as this pro-\n` + `mpts resolved promise. You can chain them in that fashion, passing along requir-\n` + `ed arguments.\n\n` + `Press [Enter] to continue...`, async ( answer ) => { console.log( 'You continued...: ', answer ); instance.close(); return Promise.resolve( answer ); } ); } } // Example of a Wizard Service Provider export class ExampleWizard extends WizardServiceProvider { getServiceProviderSubscriptions( options?: any ): ServiceProviderSubscriptions { return { commands: {}, required: {}, optional: {}, subcommands: {} }; } getServiceProviderSubscriptionHelpText<T extends ServiceProviderHelpText>( options?: any ): T { return { helpText: { "command": `To use this service, read this HelpText.` } } as T; } testEvents( handler: any ): void { this.on( 'ServiceEvent', ( data ) => { DEBUG( `TestServiceEvent Caught and Handled!` ); handler( data ); } ); } }