Main Builder
The MainBuilder class is the core orchestrator of AI Scaffolding's project generation process. It's responsible for creating the project structure, handling package generation, and executing various build tasks in a coordinated sequence.
Overview
The MainBuilder is instantiated with the project configuration collected during the prompt phase and used to execute the entire build process.
import { MainBuilder } from './builders/MainBuilder.js';
// Create a new MainBuilder with the collected config
const builder = new MainBuilder(config);
// Execute the build process
await builder.build(); Class Structure
export class MainBuilder {
config: ProjectConfig; // Project configuration
tasks: Listr; // Task list for execution
packagesPath: string; // Path to packages directory
templatesPath: string; // Path to templates directory
nextSteps: string[] = []; // Next steps to display after build
constructor(config: ProjectConfig) { /* ... */ }
// Main public method
public async build() { /* ... */ }
// Private implementation methods
private _createProjectStructure() { /* ... */ }
private _buildPackages() { /* ... */ }
private _createCursorrules() { /* ... */ }
private _installDependencies() { /* ... */ }
private _formatAll() { /* ... */ }
private _initGitRepository() { /* ... */ }
// More helper methods...
}Key Responsibilities
1. Build Orchestration
The build() method orchestrates the entire process in a specific order:
public async build() {
const startTime = Date.now();
// Orchestrate tasks
this._createProjectStructure();
this._buildPackages();
this._createCursorrules();
this._installDependencies();
this._formatAll();
this._initGitRepository();
// Run tasks
await this.tasks.run();
// Log completion and next steps
const endTime = Date.now();
const duration = ((endTime - startTime) / 1000).toFixed(2);
console.log(green(`\n✨ Project created successfully! Built in ${duration} seconds`));
this._nextSteps();
}2. Project Structure Creation
The MainBuilder creates the basic project structure:
private _createProjectStructure() {
this.tasks.add({
title: magenta('Ensure project directories'),
task: async () => {
await fs.ensureDir(this.config.projectPath);
await fs.ensureDir(this.packagesPath);
}
});
// Add tasks for project files, README, package.json, etc.
}3. Package Building
The MainBuilder delegates to package-specific builders:
private _buildPackages() {
for (const pkg of Object.keys(this.config.packages)) {
const Builder = Packages[pkg as keyof ProjectConfigV2['packages']].builder
if (Builder) {
this.tasks.add({
title: magenta(`Create ${pkg} package`),
task: async (_, task) => {
const builder = new Builder(this.config);
return builder.build(task, this.nextSteps);
}
})
}
}
}4. Cursor Rules Generation
The MainBuilder creates AI assistant rules for Cursor:
private async _createCursorrules() {
// Collects and merges .cursorrules files from each package
// and processes them with Handlebars
}5. Dependency Installation
private _installDependencies() {
this.tasks.add({
title: magenta('Install dependencies'),
skip: this.config.skipInstall,
task: async (_, task) => task.newListr([{
title: `Run ${this.config.packageManager} install`,
task: async () => {
const cmd = execa.command(`${this.config.packageManager} install`, {
cwd: this.config.projectPath,
})
// Execute the command
}
}])
})
}6. Code Formatting
private _formatAll() {
this.tasks.add({
title: magenta('Format code'),
skip: this.config.skipInstall || this.config.global.isNpm,
task: async (_, task) => task.newListr([
{
title: 'Format all packages',
task: async () => {
await execSync(`${this.config.packageManager} format:all`, { cwd: this.config.projectPath });
}
},
// More formatting tasks
])
})
}7. Git Repository Setup
private _initGitRepository() {
this.tasks.add({
title: magenta('Setup Git repository'),
task: async (_, task) => task.newListr([
{
title: 'Initialize repository',
task: async () => {
execSync('git init -b main', { cwd: this.config.projectPath });
}
},
// More git tasks
])
})
}Template Processing Methods
The MainBuilder includes several methods for processing Handlebars templates:
Write Raw File
private async _writeRawFile(
file: string,
extraParams: Record<string, unknown> = {},
srcPath: string
): Promise<void> {
const template = await this._compile(file, srcPath);
const data = template({
...this.config,
...extraParams
});
return fs.writeFile(
path.join(this.config.projectPath, path.basename(file)),
data
);
}Write Template File
private async _writeFile(
file: string,
extraParams: Record<string, unknown> = {},
isJson?: boolean,
parseFn?: (data: string) => string
): Promise<void> {
const template = await this._compile(`${file}.hbs`);
const data = template({
...this.config,
...extraParams
});
// Write file based on type (JSON or plain text)
}Copy Folder
private async _copyFolder(folder: string, targetFolder?: string) {
return fs.copy(
path.join(this.templatesPath, folder),
path.join(this.config.projectPath, targetFolder || folder)
);
}Compile Template
private async _compile(templateName: string, srcPath: string = this.templatesPath): Promise<TemplateDelegate> {
const templatePath = path.join(srcPath, templateName);
const templateContent = await fs.readFile(templatePath, 'utf-8');
return Handlebars.compile(templateContent);
}Helper Methods
Package Manager Command Generator
private _getPackageManagerCmd(): string {
const packageManagerCmd = {
[PackageManager.NPM]: 'npm run -w',
[PackageManager.YARN]: 'yarn workspace',
[PackageManager.PNPM]: 'pnpm --filter'
}[this.config.packageManager];
if (!packageManagerCmd) {
throw new Error('Unsupported package manager');
}
return packageManagerCmd;
}Next Steps Display
private _nextSteps(): void {
console.log(blue(`\nNext steps:\n`));
this.nextSteps.forEach((step, i) => console.log(` ${i + 1}. ${step}`));
console.log(green('\nHappy coding! 🚀\n'));
}Usage in Custom Packages
When creating custom packages, you typically won't interact with the MainBuilder directly. Instead, your package's builder will be invoked by the MainBuilder during the _buildPackages() phase.
However, understanding the MainBuilder is important for:
- Understanding the overall project generation flow
- Knowing what files and directories will already be created
- Seeing how template processing works at the project level
Best Practices
- Don't Modify MainBuilder Directly: Instead of modifying the MainBuilder class, create a custom Package Builder
- Leverage Next Steps: Add custom next steps to guide users after project creation
- Follow Task Structure: Use similar task structure in your own builders for consistency
Related Documentation
- Package Builder - Base class for package-specific builders
- Package Architecture - How to create new packages