Rails-inspired generator system that provides scaffolding for your apps
Interactive prompting system with storage, prefilling, and validation capabilities built on Inquirer.js for collecting user input and preferences.
Main prompting system for collecting user input with automatic storage and prefilling.
/**
* Prompt user to answer questions with automatic storage and prefilling
* On top of Inquirer.js API, provides {store: true} property for automatic storage
* @param questions - Array of question descriptor objects or single question
* @param storage - Storage object or name (generator property) for storing responses
* @returns Promise resolving to user answers
*/
async prompt<A extends PromptAnswers = PromptAnswers>(
questions: PromptQuestions<A>,
storage?: string | Storage
): Promise<A>;Usage Examples:
export default class MyGenerator extends Generator {
async prompting() {
// Basic prompting
this.answers = await this.prompt([
{
type: 'input',
name: 'name',
message: 'What is your project name?',
default: this.appname
},
{
type: 'confirm',
name: 'includeTests',
message: 'Include test files?',
default: true
},
{
type: 'list',
name: 'license',
message: 'Choose a license:',
choices: ['MIT', 'GPL-3.0', 'Apache-2.0', 'ISC'],
default: 'MIT'
}
]);
// Prompting with automatic storage
const userPrefs = await this.prompt([
{
type: 'input',
name: 'authorName',
message: 'Your name:',
store: true // Automatically store for reuse
},
{
type: 'input',
name: 'authorEmail',
message: 'Your email:',
store: true
}
]);
// Prompting with custom storage
const dbConfig = await this.prompt([
{
type: 'list',
name: 'database',
message: 'Database type:',
choices: ['mysql', 'postgresql', 'sqlite']
}
], this.config.createStorage('database'));
}
}Register prompts with automatic option export and storage configuration.
/**
* Register stored config prompts and optional option alternative
* @param questions - Inquirer question or questions with export options
*/
registerConfigPrompts(questions: QuestionRegistrationOptions[]): void;Usage Example:
export default class MyGenerator extends Generator {
constructor(args, opts) {
super(args, opts);
// Register questions that can also be passed as CLI options
this.registerConfigPrompts([
{
type: 'input',
name: 'projectName',
message: 'Project name:',
storage: this.config,
exportOption: {
type: String,
alias: 'n',
description: 'Project name'
}
},
{
type: 'confirm',
name: 'typescript',
message: 'Use TypeScript?',
storage: this.config,
exportOption: true // Export with default option config
}
]);
}
}Comprehensive question type support from Inquirer.js.
interface PromptQuestion<T extends PromptAnswers = PromptAnswers> {
name: string;
type?: 'input' | 'number' | 'confirm' | 'list' | 'rawlist' | 'expand' | 'checkbox' | 'password' | 'editor';
message?: string | ((answers: T) => string);
default?: any | ((answers: T) => any);
choices?: Array<any> | ((answers: T) => Array<any>);
validate?: (input: any, answers?: T) => boolean | string | Promise<boolean | string>;
filter?: (input: any, answers?: T) => any | Promise<any>;
transformer?: (input: any, answers?: T, flags?: any) => string | Promise<string>;
when?: boolean | ((answers: T) => boolean | Promise<boolean>);
pageSize?: number;
prefix?: string;
suffix?: string;
askAnswered?: boolean;
loop?: boolean;
// Yeoman-specific extensions
storage?: Storage;
store?: boolean;
}Usage Examples:
export default class MyGenerator extends Generator {
async prompting() {
this.answers = await this.prompt([
// Input with validation
{
type: 'input',
name: 'port',
message: 'Server port:',
default: '3000',
validate: (input) => {
const port = parseInt(input);
return (port > 0 && port < 65536) || 'Port must be between 1 and 65535';
},
filter: (input) => parseInt(input)
},
// Conditional question
{
type: 'input',
name: 'dbUrl',
message: 'Database URL:',
when: (answers) => answers.database !== 'sqlite'
},
// Choice transformation
{
type: 'list',
name: 'framework',
message: 'Choose framework:',
choices: [
{ name: 'Express.js', value: 'express' },
{ name: 'Koa.js', value: 'koa' },
{ name: 'Fastify', value: 'fastify' }
]
},
// Checkbox with validation
{
type: 'checkbox',
name: 'features',
message: 'Select features:',
choices: ['Authentication', 'Database', 'Caching', 'Logging'],
validate: (choices) => choices.length > 0 || 'Select at least one feature'
}
]);
}
}Automatic storage and prefilling of prompt answers from global and local configuration.
interface QuestionRegistrationOptions<T extends PromptAnswers = PromptAnswers>
extends PromptQuestion<T> {
/**
* A value indicating whether an option should be exported for this question
*/
exportOption?: boolean | Record<string, unknown>;
}
// Utility functions for prompt storage (internal)
function prefillQuestions<A extends PromptAnswers = PromptAnswers>(
store: Storage,
questions: Array<PromptQuestion<A>>
): Array<PromptQuestion<A>>;
function storeAnswers(
store: Storage,
questions: any,
answers: PromptAnswers,
storeAll: boolean
): void;Usage Example:
export default class MyGenerator extends Generator {
async prompting() {
// Questions with storage will be automatically prefilled
// from previous runs and stored after completion
this.answers = await this.prompt([
{
type: 'input',
name: 'authorName',
message: 'Author name:',
store: true, // Store globally for reuse
storage: this.config // Store in generator config
},
{
type: 'input',
name: 'gitRepo',
message: 'Git repository URL:',
store: true,
default: async () => {
// Dynamic default using git
try {
const email = await this.git.email();
return `https://github.com/${email?.split('@')[0]}/`;
} catch {
return '';
}
}
}
]);
}
}Methods for handling and processing prompt answers.
type PromptAnswers = Record<string, any>;
type PromptQuestions<A extends PromptAnswers = PromptAnswers> =
PromptQuestion<A> | Array<PromptQuestion<A>>;Usage Example:
export default class MyGenerator extends Generator {
async prompting() {
// Collect answers in stages
const basicInfo = await this.prompt([
{ name: 'name', type: 'input', message: 'Project name:' },
{ name: 'version', type: 'input', message: 'Version:', default: '1.0.0' }
]);
const advancedInfo = await this.prompt([
{
name: 'features',
type: 'checkbox',
message: 'Additional features:',
choices: this._getFeatureChoices(basicInfo.name)
}
]);
// Combine answers
this.answers = { ...basicInfo, ...advancedInfo };
}
_getFeatureChoices(projectName) {
return [
{ name: `${projectName} CLI`, value: 'cli' },
{ name: `${projectName} API`, value: 'api' },
{ name: `${projectName} Web UI`, value: 'web' }
];
}
}Complex prompting scenarios with conditional logic and validation.
Usage Examples:
export default class MyGenerator extends Generator {
async prompting() {
// Multi-step prompting with dependencies
const projectType = await this.prompt({
type: 'list',
name: 'type',
message: 'Project type:',
choices: ['library', 'application', 'plugin']
});
let additionalQuestions = [];
if (projectType.type === 'application') {
additionalQuestions.push({
type: 'list',
name: 'appType',
message: 'Application type:',
choices: ['web', 'cli', 'desktop']
});
}
if (projectType.type === 'library') {
additionalQuestions.push({
type: 'confirm',
name: 'publishToNpm',
message: 'Publish to npm?',
default: true
});
}
const moreAnswers = await this.prompt(additionalQuestions);
this.answers = { ...projectType, ...moreAnswers };
// Validation with async checks
const repoInfo = await this.prompt({
type: 'input',
name: 'repository',
message: 'Repository URL:',
validate: async (input) => {
if (!input) return true; // Optional
// Check if repo exists (example)
try {
const response = await fetch(input);
return response.ok || 'Repository URL is not accessible';
} catch {
return 'Invalid URL format';
}
}
});
this.answers = { ...this.answers, ...repoInfo };
}
}interface PromptQuestion<T extends PromptAnswers = PromptAnswers> {
name: string;
type?: 'input' | 'number' | 'confirm' | 'list' | 'rawlist' | 'expand' | 'checkbox' | 'password' | 'editor';
message?: string | ((answers: T) => string);
default?: any | ((answers: T) => any);
choices?: Array<any> | ((answers: T) => Array<any>);
validate?: (input: any, answers?: T) => boolean | string | Promise<boolean | string>;
filter?: (input: any, answers?: T) => any | Promise<any>;
transformer?: (input: any, answers?: T, flags?: any) => string | Promise<string>;
when?: boolean | ((answers: T) => boolean | Promise<boolean>);
pageSize?: number;
prefix?: string;
suffix?: string;
askAnswered?: boolean;
loop?: boolean;
// Yeoman extensions
storage?: Storage;
store?: boolean;
}
interface QuestionRegistrationOptions<T extends PromptAnswers = PromptAnswers>
extends PromptQuestion<T> {
exportOption?: boolean | Record<string, unknown>;
}
type PromptAnswers = Record<string, any>;
type PromptQuestions<A extends PromptAnswers = PromptAnswers> =
PromptQuestion<A> | Array<PromptQuestion<A>>;Install with Tessl CLI
npx tessl i tessl/npm-yeoman-generator