Rails-inspired generator system that provides scaffolding for your apps
Queue-based task execution system with custom priorities, composition, and lifecycle management that orchestrates generator method execution and composition with other generators.
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
}
}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
}
}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
}));
}
}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
});
}
}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
});
}
}
}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);
}
})
);
}
}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