dx-forge 0.1.3

Production-ready VCS and orchestration engine for DX tools with Git-like versioning, dual-watcher architecture, traffic branch system, and component injection
Documentation
//! Example DX Tool implementations
//!
//! This file demonstrates how to create DX tools using the Forge framework.

use dx_forge::{DxTool, ExecutionContext, ToolOutput};
use anyhow::Result;
use std::path::PathBuf;

/// Example UI Component Tool
/// Generates UI components from templates
pub struct DxUiTool {
    enabled: bool,
    component_dir: PathBuf,
}

impl DxUiTool {
    pub fn new(component_dir: PathBuf) -> Self {
        Self {
            enabled: true,
            component_dir,
        }
    }
}

impl DxTool for DxUiTool {
    fn name(&self) -> &str {
        "dx-ui"
    }

    fn version(&self) -> &str {
        "1.0.0"
    }

    fn priority(&self) -> u32 {
        30 // Component tools run in middle priority
    }

    fn execute(&mut self, ctx: &ExecutionContext) -> Result<ToolOutput> {
        println!("🎨 DX-UI: Processing UI components...");

        let mut output = ToolOutput::success();
        
        // Example: Generate button component
        let button_path = self.component_dir.join("Button.tsx");
        let button_code = r#"
import React from 'react';

export interface ButtonProps {
    variant?: 'primary' | 'secondary';
    children: React.ReactNode;
}

export const Button: React.FC<ButtonProps> = ({ variant = 'primary', children }) => {
    return (
        <button className={`dx-button dx-button-${variant}`}>
            {children}
        </button>
    );
};
"#;

        std::fs::create_dir_all(&self.component_dir)?;
        std::fs::write(&button_path, button_code)?;
        
        output.files_created.push(button_path);
        output.message = "Generated UI components".to_string();

        println!("✅ DX-UI: Created {} components", output.files_created.len());
        Ok(output)
    }

    fn should_run(&self, ctx: &ExecutionContext) -> bool {
        if !self.enabled {
            return false;
        }

        // Run if any TypeScript/TSX files changed
        ctx.changed_files.iter().any(|f| {
            f.extension()
                .and_then(|e| e.to_str())
                .map_or(false, |e| e == "ts" || e == "tsx")
        })
    }

    fn dependencies(&self) -> Vec<String> {
        vec![] // No dependencies
    }

    fn before_execute(&mut self, _ctx: &ExecutionContext) -> Result<()> {
        // Validate component directory exists or can be created
        if !self.component_dir.exists() {
            println!("📁 Creating component directory: {:?}", self.component_dir);
        }
        Ok(())
    }

    fn after_execute(&mut self, _ctx: &ExecutionContext, output: &ToolOutput) -> Result<()> {
        if output.success {
            println!("📊 DX-UI Stats:");
            println!("  - Created: {} files", output.files_created.len());
            println!("  - Modified: {} files", output.files_modified.len());
        }
        Ok(())
    }

    fn on_error(&mut self, _ctx: &ExecutionContext, error: &anyhow::Error) -> Result<()> {
        eprintln!("❌ DX-UI failed: {}", error);
        // Could implement rollback logic here
        Ok(())
    }
}

/// Example Code Generation Tool
/// Generates boilerplate code from schemas
pub struct DxCodegenTool {
    schema_dir: PathBuf,
    output_dir: PathBuf,
}

impl DxCodegenTool {
    pub fn new(schema_dir: PathBuf, output_dir: PathBuf) -> Self {
        Self {
            schema_dir,
            output_dir,
        }
    }
}

impl DxTool for DxCodegenTool {
    fn name(&self) -> &str {
        "dx-codegen"
    }

    fn version(&self) -> &str {
        "1.0.0"
    }

    fn priority(&self) -> u32 {
        10 // Infrastructure tools run first
    }

    fn execute(&mut self, _ctx: &ExecutionContext) -> Result<ToolOutput> {
        println!("⚙️  DX-Codegen: Generating code from schemas...");

        let mut output = ToolOutput::success();
        
        // Example: Generate types from schema
        let types_path = self.output_dir.join("generated_types.ts");
        let types_code = r#"
// Auto-generated by dx-codegen
// DO NOT EDIT MANUALLY

export interface User {
    id: string;
    name: string;
    email: string;
}

export interface Product {
    id: string;
    title: string;
    price: number;
}
"#;

        std::fs::create_dir_all(&self.output_dir)?;
        std::fs::write(&types_path, types_code)?;
        
        output.files_created.push(types_path);
        output.message = "Generated types from schemas".to_string();

        println!("✅ DX-Codegen: Generated {} files", output.files_created.len());
        Ok(output)
    }

    fn should_run(&self, ctx: &ExecutionContext) -> bool {
        // Run if schema files changed
        ctx.changed_files.iter().any(|f| {
            f.extension()
                .and_then(|e| e.to_str())
                .map_or(false, |e| e == "schema" || e == "graphql")
        })
    }

    fn dependencies(&self) -> Vec<String> {
        vec![] // Runs first, no dependencies
    }
}

/// Example Style Tool
/// Processes and optimizes styles
pub struct DxStyleTool {
    style_dir: PathBuf,
}

impl DxStyleTool {
    pub fn new(style_dir: PathBuf) -> Self {
        Self { style_dir }
    }
}

impl DxTool for DxStyleTool {
    fn name(&self) -> &str {
        "dx-style"
    }

    fn version(&self) -> &str {
        "1.0.0"
    }

    fn priority(&self) -> u32 {
        40 // Style processing in middle priority
    }

    fn execute(&mut self, _ctx: &ExecutionContext) -> Result<ToolOutput> {
        println!("🎨 DX-Style: Processing styles...");

        let mut output = ToolOutput::success();
        
        // Example: Generate CSS from design tokens
        let css_path = self.style_dir.join("components.css");
        let css_code = r#"
/* Auto-generated by dx-style */

:root {
    --dx-color-primary: #007bff;
    --dx-color-secondary: #6c757d;
    --dx-spacing-sm: 0.5rem;
    --dx-spacing-md: 1rem;
    --dx-spacing-lg: 1.5rem;
}

.dx-button {
    padding: var(--dx-spacing-sm) var(--dx-spacing-md);
    border-radius: 4px;
    font-weight: 500;
    cursor: pointer;
}

.dx-button-primary {
    background-color: var(--dx-color-primary);
    color: white;
}
"#;

        std::fs::create_dir_all(&self.style_dir)?;
        std::fs::write(&css_path, css_code)?;
        
        output.files_created.push(css_path);
        output.message = "Processed styles".to_string();

        println!("✅ DX-Style: Processed styles successfully");
        Ok(output)
    }

    fn should_run(&self, ctx: &ExecutionContext) -> bool {
        // Run if style files changed
        ctx.changed_files.iter().any(|f| {
            f.extension().and_then(|e| e.to_str()).map_or(false, |e| {
                e == "css" || e == "scss" || e == "sass" || e == "less"
            })
        })
    }

    fn dependencies(&self) -> Vec<String> {
        vec!["dx-codegen".to_string()] // Depends on codegen for types
    }
}

/// Example tool with dependencies
pub struct DxOptimizerTool;

impl DxTool for DxOptimizerTool {
    fn name(&self) -> &str {
        "dx-optimizer"
    }

    fn version(&self) -> &str {
        "1.0.0"
    }

    fn priority(&self) -> u32 {
        90 // Post-processing tools run last
    }

    fn execute(&mut self, _ctx: &ExecutionContext) -> Result<ToolOutput> {
        println!("⚡ DX-Optimizer: Optimizing output...");

        let mut output = ToolOutput::success();
        output.message = "Optimization complete".to_string();

        Ok(output)
    }

    fn dependencies(&self) -> Vec<String> {
        vec![
            "dx-codegen".to_string(),
            "dx-ui".to_string(),
            "dx-style".to_string(),
        ]
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn test_dx_ui_tool_creation() {
        let temp_dir = TempDir::new().unwrap();
        let tool = DxUiTool::new(temp_dir.path().to_path_buf());
        assert_eq!(tool.name(), "dx-ui");
        assert_eq!(tool.priority(), 30);
    }

    #[test]
    fn test_dx_codegen_execution() {
        let temp_dir = TempDir::new().unwrap();
        let schema_dir = temp_dir.path().join("schemas");
        let output_dir = temp_dir.path().join("generated");

        let mut tool = DxCodegenTool::new(schema_dir, output_dir.clone());
        let ctx = ExecutionContext::new(
            temp_dir.path().to_path_buf(),
            temp_dir.path().join(".dx/forge"),
        );

        let result = tool.execute(&ctx).unwrap();
        assert!(result.success);
        assert_eq!(result.files_created.len(), 1);
        assert!(output_dir.join("generated_types.ts").exists());
    }

    #[test]
    fn test_tool_dependencies() {
        let optimizer = DxOptimizerTool;
        let deps = optimizer.dependencies();
        assert_eq!(deps.len(), 3);
        assert!(deps.contains(&"dx-codegen".to_string()));
        assert!(deps.contains(&"dx-ui".to_string()));
        assert!(deps.contains(&"dx-style".to_string()));
    }
}