@kwaeri/wizard
Version:
The @kwaeri/wizard component module of the @kwaeri/node-kit platform.
194 lines (160 loc) • 6.09 kB
text/typescript
/*-----------------------------------------------------------------------------
* @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
*---------------------------------------------------------------------------*/
// 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 ); } );
}
}