use std::fs;
use std::path::Path;
use crate::error::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StackKind {
NextJs,
Express,
Go,
Rust,
Nginx,
}
pub fn detect_stack_from_entries(entries: &[&str]) -> Vec<StackKind> {
let mut stacks = Vec::new();
if entries.iter().any(|entry| {
matches!(
*entry,
"next.config.js" | "next.config.mjs" | "next.config.ts"
)
}) {
stacks.push(StackKind::NextJs);
}
if entries.contains(&"package.json")
&& entries
.iter()
.any(|entry| matches!(*entry, "server.js" | "server.ts" | "app.js" | "app.ts"))
{
stacks.push(StackKind::Express);
}
if entries.contains(&"go.mod") {
stacks.push(StackKind::Go);
}
if entries.contains(&"Cargo.toml") {
stacks.push(StackKind::Rust);
}
if entries.contains(&"nginx.conf") {
stacks.push(StackKind::Nginx);
}
stacks
}
pub fn detect_stack_in_dir<P: AsRef<Path>>(dir: P) -> Result<Vec<StackKind>> {
let entries = fs::read_dir(dir)?
.map(|entry| entry.map(|value| value.file_name().to_string_lossy().into_owned()))
.collect::<std::result::Result<Vec<_>, _>>()?;
let names = entries.iter().map(String::as_str).collect::<Vec<_>>();
Ok(detect_stack_from_entries(&names))
}
#[cfg(test)]
mod tests {
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use super::{detect_stack_from_entries, detect_stack_in_dir, StackKind};
#[test]
fn detect_stack_from_entries_finds_multiple_known_stacks() {
let stacks = detect_stack_from_entries(&[
"package.json",
"next.config.js",
"go.mod",
"Cargo.toml",
"nginx.conf",
]);
assert!(stacks.contains(&StackKind::NextJs));
assert!(stacks.contains(&StackKind::Go));
assert!(stacks.contains(&StackKind::Rust));
assert!(stacks.contains(&StackKind::Nginx));
}
#[test]
fn detect_stack_from_entries_uses_server_files_for_express() {
let stacks = detect_stack_from_entries(&["package.json", "server.ts"]);
assert_eq!(stacks, vec![StackKind::Express]);
}
#[test]
fn detect_stack_in_dir_reads_directory_entries() {
let dir = unique_temp_dir();
fs::create_dir_all(&dir).expect("temp dir should be created");
fs::write(dir.join("package.json"), "{}").expect("package.json should be written");
fs::write(dir.join("next.config.ts"), "").expect("next config should be written");
let stacks = detect_stack_in_dir(&dir).expect("stack detection should succeed");
assert_eq!(stacks, vec![StackKind::NextJs]);
fs::remove_dir_all(&dir).expect("temp dir should be removed");
}
fn unique_temp_dir() -> PathBuf {
let suffix = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("time should move forward")
.as_nanos();
std::env::temp_dir().join(format!("lobe-stack-detect-{suffix}"))
}
}