Skip to main content

xbp_cli/
project_detector.rs

1use colored::Colorize;
2use std::path::Path;
3
4use crate::utils::{preferred_pip_command, preferred_python_command};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum ProjectType {
8    Docker,
9    DockerCompose,
10    Python,
11    NodeJs,
12    Rust,
13    Railway,
14    Go,
15}
16
17impl ProjectType {
18    pub fn name(&self) -> &str {
19        match self {
20            ProjectType::Docker => "Docker",
21            ProjectType::DockerCompose => "Docker Compose",
22            ProjectType::Python => "Python",
23            ProjectType::NodeJs => "Node.js",
24            ProjectType::Rust => "Rust",
25            ProjectType::Railway => "Railway",
26            ProjectType::Go => "Go",
27        }
28    }
29
30    pub fn icon(&self) -> &str {
31        match self {
32            ProjectType::Docker => "🐳",
33            ProjectType::DockerCompose => "🐋",
34            ProjectType::Python => "🐍",
35            ProjectType::NodeJs => "📦",
36            ProjectType::Rust => "🦀",
37            ProjectType::Railway => "🚂",
38            ProjectType::Go => "🔷",
39        }
40    }
41}
42
43pub fn detect_project_types(path: &Path) -> Vec<ProjectType> {
44    let mut types = Vec::new();
45
46    if path.join("Dockerfile").exists() {
47        types.push(ProjectType::Docker);
48    }
49
50    if path.join("docker-compose.yml").exists()
51        || path.join("docker-compose.yaml").exists()
52        || path.join("compose.yml").exists()
53        || path.join("compose.yaml").exists()
54    {
55        types.push(ProjectType::DockerCompose);
56    }
57
58    if path.join("requirements.txt").exists()
59        || path.join("pyproject.toml").exists()
60        || path.join("setup.py").exists()
61    {
62        types.push(ProjectType::Python);
63    }
64
65    if path.join("package.json").exists() {
66        types.push(ProjectType::NodeJs);
67    }
68
69    if path.join("Cargo.toml").exists() {
70        types.push(ProjectType::Rust);
71    }
72
73    if path.join("railway.json").exists() || path.join("railway.toml").exists() {
74        types.push(ProjectType::Railway);
75    }
76
77    if path.join("go.mod").exists() {
78        types.push(ProjectType::Go);
79    }
80
81    types
82}
83
84pub fn suggest_commands(project_types: &[ProjectType]) -> Vec<String> {
85    let mut commands = Vec::new();
86
87    for project_type in project_types {
88        match project_type {
89            ProjectType::Docker => {
90                commands.push("docker build -t <image-name> .".to_string());
91                commands.push("docker run -p <port>:<port> <image-name>".to_string());
92            }
93            ProjectType::DockerCompose => {
94                commands.push("docker-compose up -d".to_string());
95                commands.push("docker-compose down".to_string());
96            }
97            ProjectType::Python => {
98                let pip = preferred_pip_command();
99                let python = preferred_python_command();
100                commands.push(format!("{} install -r requirements.txt", pip));
101                commands.push(format!("{} main.py", python));
102            }
103            ProjectType::NodeJs => {
104                commands.push("npm install".to_string());
105                commands.push("npm start".to_string());
106            }
107            ProjectType::Rust => {
108                commands.push("cargo build --release".to_string());
109                commands.push("cargo run".to_string());
110            }
111            ProjectType::Railway => {
112                commands.push("railway up".to_string());
113                commands.push("railway logs".to_string());
114            }
115            ProjectType::Go => {
116                commands.push("go build".to_string());
117                commands.push("go run .".to_string());
118            }
119        }
120    }
121
122    commands.dedup();
123    commands
124}
125
126pub fn display_project_types(project_types: &[ProjectType]) {
127    if project_types.is_empty() {
128        return;
129    }
130
131    let types_str = project_types
132        .iter()
133        .map(|t| format!("{} {}", t.icon(), t.name()))
134        .collect::<Vec<_>>()
135        .join(", ");
136
137    tracing::info!("{} {}", "Detected:".bright_blue(), types_str);
138}
139
140pub fn display_suggested_commands(project_types: &[ProjectType]) {
141    let commands = suggest_commands(project_types);
142
143    if commands.is_empty() {
144        return;
145    }
146
147    tracing::info!("{}", "Suggested commands:".bright_blue());
148    for cmd in commands.iter().take(3) {
149        tracing::info!("  {}", cmd.dimmed());
150    }
151}