xbp 10.15.0

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
use colored::Colorize;
use std::path::Path;

use crate::utils::{preferred_pip_command, preferred_python_command};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProjectType {
    Docker,
    DockerCompose,
    Python,
    NodeJs,
    Rust,
    Railway,
    Go,
}

impl ProjectType {
    pub fn name(&self) -> &str {
        match self {
            ProjectType::Docker => "Docker",
            ProjectType::DockerCompose => "Docker Compose",
            ProjectType::Python => "Python",
            ProjectType::NodeJs => "Node.js",
            ProjectType::Rust => "Rust",
            ProjectType::Railway => "Railway",
            ProjectType::Go => "Go",
        }
    }

    pub fn icon(&self) -> &str {
        match self {
            ProjectType::Docker => "🐳",
            ProjectType::DockerCompose => "🐋",
            ProjectType::Python => "🐍",
            ProjectType::NodeJs => "📦",
            ProjectType::Rust => "🦀",
            ProjectType::Railway => "🚂",
            ProjectType::Go => "🔷",
        }
    }
}

pub fn detect_project_types(path: &Path) -> Vec<ProjectType> {
    let mut types = Vec::new();

    if path.join("Dockerfile").exists() {
        types.push(ProjectType::Docker);
    }

    if path.join("docker-compose.yml").exists()
        || path.join("docker-compose.yaml").exists()
        || path.join("compose.yml").exists()
        || path.join("compose.yaml").exists()
    {
        types.push(ProjectType::DockerCompose);
    }

    if path.join("requirements.txt").exists()
        || path.join("pyproject.toml").exists()
        || path.join("setup.py").exists()
    {
        types.push(ProjectType::Python);
    }

    if path.join("package.json").exists() {
        types.push(ProjectType::NodeJs);
    }

    if path.join("Cargo.toml").exists() {
        types.push(ProjectType::Rust);
    }

    if path.join("railway.json").exists() || path.join("railway.toml").exists() {
        types.push(ProjectType::Railway);
    }

    if path.join("go.mod").exists() {
        types.push(ProjectType::Go);
    }

    types
}

pub fn suggest_commands(project_types: &[ProjectType]) -> Vec<String> {
    let mut commands = Vec::new();

    for project_type in project_types {
        match project_type {
            ProjectType::Docker => {
                commands.push("docker build -t <image-name> .".to_string());
                commands.push("docker run -p <port>:<port> <image-name>".to_string());
            }
            ProjectType::DockerCompose => {
                commands.push("docker-compose up -d".to_string());
                commands.push("docker-compose down".to_string());
            }
            ProjectType::Python => {
                let pip = preferred_pip_command();
                let python = preferred_python_command();
                commands.push(format!("{} install -r requirements.txt", pip));
                commands.push(format!("{} main.py", python));
            }
            ProjectType::NodeJs => {
                commands.push("npm install".to_string());
                commands.push("npm start".to_string());
            }
            ProjectType::Rust => {
                commands.push("cargo build --release".to_string());
                commands.push("cargo run".to_string());
            }
            ProjectType::Railway => {
                commands.push("railway up".to_string());
                commands.push("railway logs".to_string());
            }
            ProjectType::Go => {
                commands.push("go build".to_string());
                commands.push("go run .".to_string());
            }
        }
    }

    commands.dedup();
    commands
}

pub fn display_project_types(project_types: &[ProjectType]) {
    if project_types.is_empty() {
        return;
    }

    let types_str = project_types
        .iter()
        .map(|t| format!("{} {}", t.icon(), t.name()))
        .collect::<Vec<_>>()
        .join(", ");

    tracing::info!("{} {}", "Detected:".bright_blue(), types_str);
}

pub fn display_suggested_commands(project_types: &[ProjectType]) {
    let commands = suggest_commands(project_types);

    if commands.is_empty() {
        return;
    }

    tracing::info!("{}", "Suggested commands:".bright_blue());
    for cmd in commands.iter().take(3) {
        tracing::info!("  {}", cmd.dimmed());
    }
}