Skip to content

v0.5.0 Task 2.1.1: Create forge-project CLI command #146

@gbrennon

Description

@gbrennon

Task Description

Epic: Epic 2.1: CLI/Scaffolding Tools (#73)
Acceptance Criteria: forge-project CLI command, Interactive project setup wizard, Template selection, Directory structure generation

Implementation Details

Files to Create/Modify

  • src/forging_blocks/cli/__init__.py (NEW)
  • src/forging_blocks/cli/commands/project.py (NEW)
  • src/forging_blocks/cli/templates/__init__.py (NEW)
  • tests/unit/cli/test_cli.py (NEW)

CLI Command Structure

import click
from pathlib import Path
from typing import Optional
import yaml

@click.group()
def cli():
    """ForgingBlocks CLI for project scaffolding and management."""
    pass

@cli.command()
@click.argument('project_name')
@click.option('--template', type=click.Choice(['web-api', 'console', 'library']), default='web-api')
@click.option('--output-dir', default='.')
@click.option('--interactive', is_flag=True, default=True)
def create_project(project_name: str, template: str, output_dir: str, interactive: bool):
    """Create a new ForgingBlocks project."""
    
    project_path = Path(output_dir) / project_name
    
    if project_path.exists():
        click.echo(f"Error: Directory {project_path} already exists", err=True)
        return
    
    click.echo(f"Creating ForgingBlocks project: {project_name}")
    click.echo(f"Template: {template}")
    click.echo(f"Location: {project_path}")
    
    # Load template configuration
    template_config = _load_template_config(template)
    
    # Generate project structure
    _generate_project_structure(project_path, template_config, project_name)
    
    # Generate Poetry configuration
    _generate_poetry_config(project_path, project_name, template_config)
    
    click.echo("\n✅ Project created successfully!")
    click.echo(f"\nNext steps:")
    click.echo(f"  cd {project_path}")
    click.echo(f"  poetry install")
    click.echo(f"  poetry run python -m {project_name.replace('-', '_')}")

Project Template System

class ProjectTemplate:
    """Represents a project template with structure and configuration."""
    
    def __init__(self, name: str, config: dict):
        self.name = name
        self.config = config
        self.structure = config.get('structure', {})
        self.dependencies = config.get('dependencies', [])
        self.extras = config.get('extras', {})
    
    def generate_structure(self, project_path: Path, project_name: str):
        """Generate the project structure based on template."""
        
        # Create directory structure
        for dir_path in self.structure.get('directories', []):
            (project_path / dir_path).mkdir(parents=True, exist_ok=True)
        
        # Generate files
        for file_config in self.structure.get('files', []):
            self._generate_file(project_path, file_config, project_name)
    
    def _generate_file(self, project_path: Path, file_config: dict, project_name: str):
        """Generate a single file from template."""
        file_path = project_path / file_config['path']
        template_content = self._load_template_content(file_config['template'])
        
        # Replace placeholders
        content = self._replace_placeholders(template_content, {
            'project_name': project_name,
            'project_slug': project_name.replace('-', '_'),
            'description': file_config.get('description', ''),
            'dependencies': self.dependencies
        })
        
        file_path.parent.mkdir(parents=True, exist_ok=True)
        file_path.write_text(content)
    
    def _replace_placeholders(self, content: str, context: dict) -> str:
        """Replace template placeholders with actual values."""
        for key, value in context.items():
            placeholder = f"{{{{{key}}}}}"
            if isinstance(value, list):
                value = '\n'.join(f"    {item}" for item in value)
            content = content.replace(placeholder, str(value))
        return content

Template Configuration

# templates/web-api/config.yaml
name: "Web API Template"
description: "FastAPI-based web application with ForgingBlocks"
version: "1.0.0"

structure:
  directories:
    - "src/{{project_slug}}"
    - "src/{{project_slug}}/domain"
    - "src/{{project_slug}}/application"
    - "src/{{project_slug}}/infrastructure"
    - "src/{{project_slug}}/presentation"
    - "tests"
    - "tests/domain"
    - "tests/application"
    - "tests/infrastructure"
    - "tests/presentation"
  
  files:
    - path: "pyproject.toml"
      template: "pyproject.toml.jinja"
      description: "Poetry project configuration"
    
    - path: "src/{{project_slug}}/__init__.py"
      template: "__init__.py.jinja"
      description: "Package initialization"
    
    - path: "src/{{project_slug}}/main.py"
      template: "main.py.jinja"
      description: "Application entry point"
    
    - path: "src/{{project_slug}}/domain/__init__.py"
      template: "domain/__init__.py.jinja"
      description: "Domain layer initialization"
    
    - path: "src/{{project_slug}}/application/__init__.py"
      template: "application/__init__.py.jinja"
      description: "Application layer initialization"

dependencies:
  - "fastapi>=0.104.0"
  - "uvicorn[standard]>=0.24.0"
  - "pydantic>=2.4.0"
  - "forging-blocks>=0.4.0"

extras:
  dev:
    - "pytest>=7.4.0"
    - "black>=23.0.0"
    - "ruff>=0.1.0"

Acceptance Criteria

  • CLI command with forge-project functionality
  • Interactive setup wizard with user prompts
  • Template selection for different project types
  • Directory structure generation following clean architecture
  • Poetry configuration with ForgingBlocks dependency
  • Project initialization with working example

Definition of Done

  • CLI tool working with click framework
  • Project templates for web API, console, and library
  • Template system with Jinja2 templating
  • All tests passing with CLI functionality
  • Documentation with CLI usage examples

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions