CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-yeoman-generator

Rails-inspired generator system that provides scaffolding for your apps

Overview
Eval results
Files

user-interaction.mddocs/

User Interaction & Prompting

Interactive prompting system with storage, prefilling, and validation capabilities built on Inquirer.js for collecting user input and preferences.

Capabilities

Interactive Prompting

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'));
  }
}

Question Registration

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
      }
    ]);
  }
}

Question Types

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'
      }
    ]);
  }
}

Storage Integration

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 '';
          }
        }
      }
    ]);
  }
}

Answer Processing

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' }
    ];
  }
}

Advanced Prompting Patterns

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 };
  }
}

Types

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

docs

command-execution.md

command-line.md

configuration.md

core-generator.md

file-system.md

git-integration.md

index.md

package-management.md

task-lifecycle.md

user-interaction.md

tile.json