@autodev/backend-generator
Version:
Backend project generator with support for parsing and generating project configurations
325 lines (261 loc) • 9.14 kB
text/typescript
import { Project } from '../types/project-config.js';
import * as fs from 'fs-extra';
import * as path from 'path';
import Handlebars from 'handlebars';
export interface GeneratorOptions {
outputDir: string;
overwrite?: boolean;
dryRun?: boolean;
}
export class ProjectGenerateError extends Error {
constructor(message: string, public details?: any) {
super(message);
this.name = 'ProjectGenerateError';
}
}
export class ProjectGenerator {
private project: Project;
private options: GeneratorOptions;
constructor(project: Project, options: GeneratorOptions) {
this.project = project;
this.options = options;
}
/**
* Generate the complete project structure
*/
async generate(): Promise<void> {
try {
await this.validateOptions();
await this.createDirectories();
await this.generateFiles();
await this.generateConfigurationFiles();
} catch (error) {
if (error instanceof Error) {
throw new ProjectGenerateError(`Generation failed: ${error.message}`, error);
}
throw error;
}
}
/**
* Generate project configuration as JSON
*/
static generateJson(project: Project, pretty: boolean = true): string {
return JSON.stringify(project, null, pretty ? 2 : 0);
}
/**
* Save project configuration to file
*/
static async saveToFile(project: Project, filePath: string, pretty: boolean = true): Promise<void> {
const json = this.generateJson(project, pretty);
await fs.ensureDir(path.dirname(filePath));
await fs.writeFile(filePath, json, 'utf-8');
}
private async validateOptions(): Promise<void> {
if (!this.options.outputDir) {
throw new ProjectGenerateError('Output directory is required');
}
if (await fs.pathExists(this.options.outputDir) && !this.options.overwrite) {
const files = await fs.readdir(this.options.outputDir);
if (files.length > 0) {
throw new ProjectGenerateError(
`Output directory ${this.options.outputDir} is not empty. Use --overwrite to force.`
);
}
}
}
private async createDirectories(): Promise<void> {
const { directories } = this.project.structure;
for (const dir of directories) {
const fullPath = path.join(this.options.outputDir, dir);
if (this.options.dryRun) {
console.log(`[DRY RUN] Would create directory: ${fullPath}`);
} else {
await fs.ensureDir(fullPath);
console.log(`Created directory: ${fullPath}`);
}
}
}
private async generateFiles(): Promise<void> {
const { files } = this.project.structure;
for (const file of files) {
const fullPath = path.join(this.options.outputDir, file);
if (this.options.dryRun) {
console.log(`[DRY RUN] Would create file: ${fullPath}`);
} else {
await fs.ensureDir(path.dirname(fullPath));
// Generate file content based on file type
const content = await this.generateFileContent(file);
await fs.writeFile(fullPath, content, 'utf-8');
console.log(`Created file: ${fullPath}`);
}
}
}
private async generateConfigurationFiles(): Promise<void> {
const { configurations } = this.project;
for (const [fileName, lines] of Object.entries(configurations)) {
const fullPath = path.join(this.options.outputDir, fileName);
const content = lines.join('\n');
if (this.options.dryRun) {
console.log(`[DRY RUN] Would create configuration file: ${fullPath}`);
} else {
await fs.ensureDir(path.dirname(fullPath));
await fs.writeFile(fullPath, content, 'utf-8');
console.log(`Created configuration file: ${fullPath}`);
}
}
}
private async generateFileContent(fileName: string): Promise<string> {
const { projectConfig, dependencies } = this.project;
// Basic template context
const context = {
projectName: projectConfig.name,
description: projectConfig.description,
language: projectConfig.language,
framework: projectConfig.framework,
dependencies,
packageName: this.getPackageName(projectConfig.name),
className: this.getClassName(projectConfig.name),
};
// Generate content based on file type
if (fileName.endsWith('.java')) {
return this.generateJavaFile(fileName, context);
} else if (fileName === 'pom.xml') {
return this.generatePomXml(context);
} else if (fileName === 'build.gradle') {
return this.generateBuildGradle(context);
} else if (fileName.endsWith('.md')) {
return this.generateMarkdownFile(fileName, context);
}
return `// Generated file: ${fileName}\n// TODO: Implement content for ${fileName}`;
}
private generateJavaFile(fileName: string, context: any): string {
if (fileName.includes('Application.java')) {
return this.generateSpringBootApplication(context);
}
return `package ${context.packageName};\n\n// TODO: Implement ${fileName}`;
}
private generateSpringBootApplication(context: any): string {
const template = `package {{packageName}};
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* {{description}}
*/
@SpringBootApplication
public class {{className}}Application {
public static void main(String[] args) {
SpringApplication.run({{className}}Application.class, args);
}
}`;
return Handlebars.compile(template)(context);
}
private generatePomXml(context: any): string {
const template = `<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>{{projectName}}</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>{{projectName}}</name>
<description>{{description}}</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/>
</parent>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
{{#each dependencies}}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>{{@key}}</artifactId>
<version>{{this}}</version>
</dependency>
{{/each}}
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>`;
return Handlebars.compile(template)(context);
}
private generateBuildGradle(context: any): string {
const template = `plugins {
id 'java'
id 'org.springframework.boot' version '3.0.0'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
{{#each dependencies}}
implementation '{{@key}}:{{this}}'
{{/each}}
}
tasks.named('test') {
useJUnitPlatform()
}`;
return Handlebars.compile(template)(context);
}
private generateMarkdownFile(fileName: string, context: any): string {
if (fileName.toLowerCase() === 'readme.md') {
const template = `# {{projectName}}
{{description}}
## Features
- Microservice architecture
- Spring Boot 3.0
- RESTful API
- Database integration
- Docker support
## Getting Started
### Prerequisites
- Java 11 or higher
- Maven 3.6 or higher
- Docker (optional)
### Running the application
\`\`\`bash
mvn spring-boot:run
\`\`\`
### Building for production
\`\`\`bash
mvn clean package
\`\`\`
### Docker
\`\`\`bash
docker build -t {{projectName}} .
docker run -p 8080:8080 {{projectName}}
\`\`\`
## API Documentation
The API documentation is available at: http://localhost:8080/swagger-ui.html
## License
This project is licensed under the MIT License.`;
return Handlebars.compile(template)(context);
}
return `# ${fileName}\n\nTODO: Add content for ${fileName}`;
}
private getPackageName(projectName: string): string {
return `com.example.${projectName.toLowerCase().replace(/-/g, '')}`;
}
private getClassName(projectName: string): string {
return projectName
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join('');
}
}