CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-yeoman-generator

Rails-inspired generator system that provides scaffolding for your apps

Overview
Eval results
Files

task-lifecycle.mddocs/

Task & Lifecycle Management

Queue-based task execution system with custom priorities, composition, and lifecycle management that orchestrates generator method execution and composition with other generators.

Capabilities

Task Queuing

Core task management system for scheduling and executing generator methods.

/**
 * Schedule tasks on a run queue
 * @param task - Task configuration object
 */
queueTask(task: Task): void;

/**
 * Schedule methods on a run queue  
 * @param method - Method to be scheduled or object with function properties
 * @param methodName - Name of the method (task) to be scheduled
 * @param queueName - Name of the queue to be scheduled on
 * @param reject - Reject callback for error handling
 */
queueMethod(method: Task['method'], methodName: string, queueName: string, reject?: Task['reject']): void;
queueMethod(method: Record<string, Task['method']>, methodName?: string | Task['reject'], reject?: Task['reject']): void;

/**
 * Schedule tasks from a group on a run queue
 * @param taskGroup - Object containing tasks
 * @param taskOptions - Task configuration options
 */
queueTaskGroup(taskGroup: Record<string, Task['method']>, taskOptions: TaskOptions): void;

Usage Examples:

export default class MyGenerator extends Generator {  
  initializing() {
    // Queue a single task
    this.queueTask({
      method: async () => {
        this.log('Custom initialization task');
        await this.checkEnvironment();
      },
      taskName: 'check-environment',
      queueName: 'initializing'
    });
    
    // Queue method by name
    this.queueMethod(
      this.setupDependencies.bind(this),
      'setup-dependencies', 
      'configuring'
    );
    
    // Queue multiple methods from object
    this.queueTaskGroup({
      validateInput: () => this.validateAnswers(),
      createDirectories: () => this.setupFolders(),
      copyBaseFiles: () => this.copyTemplates()
    }, { queueName: 'writing' });
  }
  
  async checkEnvironment() {
    // Custom task implementation
  }
  
  setupDependencies() {
    // Custom method implementation  
  }
}

Lifecycle Management

Methods for managing the generator lifecycle and execution flow.

/**
 * Schedule every generator's methods on a run queue
 * Analyzes generator prototype and queues methods matching lifecycle phases
 * @param taskOptions - Configuration for task execution
 */
queueOwnTasks(taskOptions: TaskOptions): void;

/**
 * Schedule a generator's method on a run queue
 * @param name - The method name to schedule
 * @param taskOptions - Task configuration options
 */
queueOwnTask(name: string, taskOptions: TaskOptions): void;

/**
 * Execute a task with error handling and lifecycle events
 * @param task - Task to be executed
 * @param args - Task arguments
 * @param taskStatus - Task status for cancellation support
 */
async executeTask(task: Task, args?: any[], taskStatus?: TaskStatus): Promise<void>;

/**
 * Get available task names from generator prototype
 * @returns Array of valid method names that can be queued
 */
getTaskNames(): string[];

Usage Example:

export default class MyGenerator extends Generator {
  constructor(args, opts) {
    super(args, opts);
    
    // Configure task behavior
    this.setFeatures({
      tasksMatchingPriority: true, // Only queue methods matching lifecycle phases
      taskPrefix: 'task_', // Methods must start with 'task_' 
      inheritTasks: true // Include parent class methods
    });
  }
  
  // These methods will be automatically queued
  task_initializing() {
    this.log('Custom initializing task');
  }
  
  task_prompting() {
    return this.prompt([...]);
  }
  
  task_writing() {
    this.renderTemplate('template.ejs', 'output.js');
  }
  
  // This method won't be queued (doesn't match prefix)
  helperMethod() {
    return 'helper';
  }
  
  // Get available tasks
  listTasks() {
    const tasks = this.getTaskNames();
    this.log(`Available tasks: ${tasks.join(', ')}`);
    // Output: Available tasks: initializing, prompting, writing
  }
}

Priority and Queue Management

Manage custom execution priorities and queue ordering.

/**
 * Register custom priorities for this generator
 * @param priorities - Array of priority configurations
 */
registerPriorities(priorities: Priority[]): void;

/**
 * Extract tasks from a priority queue
 * @param name - Priority/queue name
 * @param taskOptions - Task configuration options
 * @returns Array of tasks from the priority
 */
extractTasksFromPriority(name: string, taskOptions?: TaskOptions): Task[];

/**
 * Extract tasks from a task group object
 * @param group - Object containing task methods
 * @param taskOptions - Task configuration options
 * @returns Array of individual tasks
 */
extractTasksFromGroup(group: Record<string, Task['method']>, taskOptions: TaskOptions): Task[];

Usage Examples:

export default class MyGenerator extends Generator {
  constructor(args, opts) {
    super(args, opts);
    
    // Register custom priorities
    this.registerPriorities([
      {
        priorityName: 'validation',
        before: 'prompting' // Run before prompting phase
      },
      {
        priorityName: 'cleanup', 
        queueName: 'custom-cleanup-queue',
        before: 'end' // Run before end phase
      }
    ]);
  }
  
  // Custom priority methods
  validation() {
    this.log('Running validation phase');
    this.validateEnvironment();
  }
  
  cleanup() {
    this.log('Running cleanup phase');
    this.removeTemporaryFiles();
  }
  
  // Extract and manipulate tasks
  customTaskHandling() {
    const validationTasks = this.extractTasksFromPriority('validation');
    const groupTasks = this.extractTasksFromGroup({
      step1: () => this.log('Step 1'),
      step2: () => this.log('Step 2'),
      step3: () => this.log('Step 3')
    }, { queueName: 'custom' });
    
    // Queue extracted tasks with custom options
    groupTasks.forEach(task => this.queueTask({
      ...task,
      once: true // Only run once per generator instance
    }));
  }
}

Generator Composition

Compose with other generators to create complex scaffolding workflows.

/**
 * Compose this generator with another one
 * @param generator - Path to generator module or generator object
 * @param options - Composition options
 * @returns Promise resolving to composed generator instance(s)
 */
async composeWith<G extends BaseGenerator = BaseGenerator>(
  generator: string | { Generator: any; path: string },
  options?: ComposeOptions<G>
): Promise<G>;

async composeWith<G extends BaseGenerator = BaseGenerator>(
  generator: string[],
  options?: ComposeOptions<G>
): Promise<G[]>;

async composeWith<G extends BaseGenerator = BaseGenerator>(
  generator: string | { Generator: any; path: string },
  args: string[],
  options?: Partial<GetGeneratorOptions<G>>,
  immediately?: boolean
): Promise<G>;

Usage Examples:

export default class MyGenerator extends Generator {
  async initializing() {  
    // Compose with a peerDependency generator
    await this.composeWith('bootstrap', { 
      sass: true,
      version: '5.0'
    });
    
    // Compose with local generator
    await this.composeWith(
      path.resolve(__dirname, '../generator-component/app/main.js'),
      { componentType: 'functional' }
    );
    
    // Compose with multiple generators
    await this.composeWith([
      'typescript',
      'eslint',
      'prettier'  
    ], {
      skipInstall: this.options.skipInstall
    });
    
    // Compose with arguments
    await this.composeWith(
      'sub-generator',
      ['component', 'Button'],
      { typescript: true }
    );
  }
  
  async writing() {
    // Compose with Generator class directly
    const SubGenerator = await import('./sub-generator.js');
    await this.composeWith({ 
      Generator: SubGenerator.default,
      path: './sub-generator.js'
    }, {
      forwardOptions: true // Forward all options
    });
  }
}

Task Cancellation and Restart

Manage task cancellation and generator restart functionality.

/**
 * Ignore cancellable tasks
 * Marks current task status as cancelled to skip remaining cancellable tasks
 */
cancelCancellableTasks(): void;

/**
 * Start the generator again with new options
 * @param options - New options to merge with existing ones
 */
startOver(options?: BaseOptions): void;

/**
 * Queue generator tasks (main entry point for task scheduling)
 */
async queueTasks(): Promise<void>;

Usage Examples:

export default class MyGenerator extends Generator {
  async prompting() {
    this.answers = await this.prompt([
      {
        type: 'confirm',
        name: 'continueAdvanced',
        message: 'Continue with advanced setup?'
      }
    ]);
    
    if (!this.answers.continueAdvanced) {
      // Cancel remaining cancellable tasks
      this.cancelCancellableTasks();
      return;
    }
  }
  
  // This task can be cancelled
  writing() {
    this.queueTask({
      method: () => {
        this.log('This task can be cancelled');
        this.renderTemplate('advanced.ejs', 'advanced.js');
      },
      taskName: 'advanced-setup',
      queueName: 'writing',
      cancellable: true
    });
  }
  
  // Restart scenario
  async handleError() {
    try {
      await this.someFailingOperation();
    } catch (error) {
      this.log('Setup failed, restarting with safe defaults...');
      this.startOver({
        safeMode: true,
        skipAdvanced: true
      });
    }
  }
}

Pipeline Operations

File processing pipelines with transform streams.

/**
 * Process files through transform streams
 * @param options - Pipeline configuration options
 * @param transforms - Array of transform streams to apply
 */
async pipeline(
  options?: GeneratorPipelineOptions,
  ...transforms: Array<FileTransform<MemFsEditorFile>>
): Promise<void>;

/**
 * Add a transform stream to the commit stream
 * @param options - Pipeline options with priority configuration
 * @param transforms - Transform streams to queue
 * @returns This generator for chaining
 */
queueTransformStream(
  options?: GeneratorPipelineOptions & { priorityToQueue?: string },
  ...transforms: Array<FileTransform<MemFsEditorFile>>
): this;

Usage Examples:

import { Transform } from 'stream';

export default class MyGenerator extends Generator {
  writing() {
    // Copy files first
    this.copyTemplate('src/**/*', 'src/');
    
    // Queue transform pipeline
    this.queueTransformStream(
      { priorityToQueue: 'transform' },
      new Transform({
        objectMode: true,
        transform(file, encoding, callback) {
          if (file.path.endsWith('.js')) {
            // Add strict mode to JS files
            file.contents = Buffer.from(
              `'use strict';\n${file.contents.toString()}`
            );
          }
          callback(null, file);
        }
      })
    );
  }
  
  async install() {
    // Run pipeline directly
    await this.pipeline(
      { name: 'Post-install processing' },
      new Transform({
        objectMode: true,
        transform(file, encoding, callback) {
          if (file.path.includes('node_modules')) {
            // Skip node_modules
            return callback();
          }
          callback(null, file);
        }
      })
    );
  }
}

Types

interface Task<TaskContext = any> {
  method: (this: TaskContext, ...args: any[]) => unknown | Promise<unknown>;
  taskName: string;
  queueName?: string;
  once?: boolean;
  run?: boolean;
  edit?: boolean;
  skip?: boolean;
  args?: any[] | ((generator: Generator) => any[]);
  reject?: (error: unknown) => void;
  taskPrefix?: string;
  auto?: boolean;
  taskOrigin?: any;
  cancellable?: boolean;
}

interface TaskOptions {
  queueName?: string;
  once?: boolean;
  run?: boolean;
  edit?: boolean;
  skip?: boolean;
  args?: any[] | ((generator: Generator) => any[]);
  reject?: (error: unknown) => void;
  taskPrefix?: string;
  auto?: boolean;
  taskOrigin?: any;
  cancellable?: boolean;
}

interface Priority {
  priorityName: string;
  queueName?: string;
  before?: string;
  once?: boolean;
  run?: boolean;
  edit?: boolean;
  skip?: boolean;
  args?: any[] | ((generator: Generator) => any[]);
}

interface ComposeOptions<G extends BaseGenerator = BaseGenerator> {
  generatorArgs?: string[];
  generatorOptions?: Partial<GetGeneratorOptions<G>>;
  destinationRoot?: string;
  skipEnvRegister?: boolean;
  forceResolve?: boolean;
  forwardOptions?: boolean;
  schedule?: boolean;
}

interface GeneratorPipelineOptions {
  disabled?: boolean;
  name?: string;
  pendingFiles?: boolean;
  filter?: (file: MemFsEditorFile) => boolean;
}

type TaskStatus = {
  cancelled: boolean;
  timestamp: Date;
};

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