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
Definition of Done
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
Project Template System
Template Configuration
Acceptance Criteria
Definition of Done