#![allow(dead_code)]
use std::fs;
use std::path::Path;
const FIRST_RUN_MARKER: &str = ".jarvy_initialized";
pub fn is_first_run() -> bool {
if is_ci_environment() {
return false;
}
if std::env::var("JARVY_TEST_MODE").as_deref() == Ok("1") {
return false;
}
let marker_path = get_marker_path();
match marker_path {
Some(path) => !path.exists(),
None => false, }
}
pub fn mark_initialized() -> Result<(), std::io::Error> {
let marker_path = get_marker_path().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
"Could not determine config directory",
)
})?;
if let Some(parent) = marker_path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(marker_path, "")?;
Ok(())
}
fn get_marker_path() -> Option<std::path::PathBuf> {
dirs::home_dir().map(|home| home.join(".jarvy").join(FIRST_RUN_MARKER))
}
fn is_ci_environment() -> bool {
std::env::var("CI").is_ok()
|| std::env::var("GITHUB_ACTIONS").is_ok()
|| std::env::var("GITLAB_CI").is_ok()
|| std::env::var("CIRCLECI").is_ok()
|| std::env::var("TRAVIS").is_ok()
|| std::env::var("JENKINS_URL").is_ok()
|| std::env::var("BUILDKITE").is_ok()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProjectType {
NodeJs,
Rust,
Go,
Python,
Java,
Ruby,
DotNet,
Php,
Elixir,
Flutter,
Terraform,
Docker,
Kubernetes,
Unknown,
}
impl ProjectType {
pub fn display_name(&self) -> &'static str {
match self {
ProjectType::NodeJs => "Node.js",
ProjectType::Rust => "Rust",
ProjectType::Go => "Go",
ProjectType::Python => "Python",
ProjectType::Java => "Java",
ProjectType::Ruby => "Ruby",
ProjectType::DotNet => ".NET",
ProjectType::Php => "PHP",
ProjectType::Elixir => "Elixir",
ProjectType::Flutter => "Flutter",
ProjectType::Terraform => "Terraform",
ProjectType::Docker => "Docker",
ProjectType::Kubernetes => "Kubernetes",
ProjectType::Unknown => "Unknown",
}
}
pub fn suggested_template(&self) -> Option<&'static str> {
match self {
ProjectType::NodeJs => Some("react"), ProjectType::Rust => Some("rust-cli"),
ProjectType::Go => Some("go-api"),
ProjectType::Python => Some("python-api"),
ProjectType::Java => Some("java-spring"),
ProjectType::Ruby => None, ProjectType::DotNet => None,
ProjectType::Php => None,
ProjectType::Elixir => None,
ProjectType::Flutter => Some("flutter"),
ProjectType::Terraform => Some("terraform"),
ProjectType::Docker => Some("docker-dev"),
ProjectType::Kubernetes => Some("k8s-admin"),
ProjectType::Unknown => Some("essential"),
}
}
}
#[derive(Debug, Clone)]
pub struct DetectedProject {
pub primary: ProjectType,
pub all: Vec<ProjectType>,
pub detection_files: Vec<String>,
}
impl DetectedProject {
pub fn is_multi_stack(&self) -> bool {
self.all.len() > 1
}
}
pub fn detect_project_type<P: AsRef<Path>>(dir: P) -> DetectedProject {
let dir = dir.as_ref();
let mut detected = Vec::new();
let mut detection_files = Vec::new();
if dir.join("package.json").exists() {
detected.push(ProjectType::NodeJs);
detection_files.push("package.json".to_string());
}
if dir.join("Cargo.toml").exists() {
detected.push(ProjectType::Rust);
detection_files.push("Cargo.toml".to_string());
}
if dir.join("go.mod").exists() {
detected.push(ProjectType::Go);
detection_files.push("go.mod".to_string());
}
if dir.join("pyproject.toml").exists() {
detected.push(ProjectType::Python);
detection_files.push("pyproject.toml".to_string());
} else if dir.join("requirements.txt").exists() {
detected.push(ProjectType::Python);
detection_files.push("requirements.txt".to_string());
} else if dir.join("setup.py").exists() {
detected.push(ProjectType::Python);
detection_files.push("setup.py".to_string());
}
if dir.join("pom.xml").exists() {
detected.push(ProjectType::Java);
detection_files.push("pom.xml".to_string());
} else if dir.join("build.gradle").exists() || dir.join("build.gradle.kts").exists() {
detected.push(ProjectType::Java);
if dir.join("build.gradle").exists() {
detection_files.push("build.gradle".to_string());
} else {
detection_files.push("build.gradle.kts".to_string());
}
}
if dir.join("Gemfile").exists() {
detected.push(ProjectType::Ruby);
detection_files.push("Gemfile".to_string());
}
if has_file_with_extension(dir, "csproj") || has_file_with_extension(dir, "sln") {
detected.push(ProjectType::DotNet);
detection_files.push("*.csproj/*.sln".to_string());
}
if dir.join("composer.json").exists() {
detected.push(ProjectType::Php);
detection_files.push("composer.json".to_string());
}
if dir.join("mix.exs").exists() {
detected.push(ProjectType::Elixir);
detection_files.push("mix.exs".to_string());
}
if dir.join("pubspec.yaml").exists() {
detected.push(ProjectType::Flutter);
detection_files.push("pubspec.yaml".to_string());
}
if has_file_with_extension(dir, "tf") {
detected.push(ProjectType::Terraform);
detection_files.push("*.tf".to_string());
}
if dir.join("Dockerfile").exists()
|| dir.join("docker-compose.yml").exists()
|| dir.join("docker-compose.yaml").exists()
{
detected.push(ProjectType::Docker);
if dir.join("Dockerfile").exists() {
detection_files.push("Dockerfile".to_string());
}
if dir.join("docker-compose.yml").exists() {
detection_files.push("docker-compose.yml".to_string());
}
}
if dir.join("k8s").is_dir() || dir.join("kubernetes").is_dir() || dir.join("manifests").is_dir()
{
detected.push(ProjectType::Kubernetes);
detection_files.push("k8s/".to_string());
}
let primary = detected.first().cloned().unwrap_or(ProjectType::Unknown);
DetectedProject {
primary,
all: if detected.is_empty() {
vec![ProjectType::Unknown]
} else {
detected
},
detection_files,
}
}
fn has_file_with_extension(dir: &Path, extension: &str) -> bool {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
if let Some(ext) = entry.path().extension() {
if ext == extension {
return true;
}
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use tempfile::tempdir;
#[test]
fn test_detect_nodejs_project() {
let dir = tempdir().unwrap();
File::create(dir.path().join("package.json")).unwrap();
let detected = detect_project_type(dir.path());
assert_eq!(detected.primary, ProjectType::NodeJs);
assert!(
detected
.detection_files
.contains(&"package.json".to_string())
);
}
#[test]
fn test_detect_rust_project() {
let dir = tempdir().unwrap();
File::create(dir.path().join("Cargo.toml")).unwrap();
let detected = detect_project_type(dir.path());
assert_eq!(detected.primary, ProjectType::Rust);
}
#[test]
fn test_detect_go_project() {
let dir = tempdir().unwrap();
File::create(dir.path().join("go.mod")).unwrap();
let detected = detect_project_type(dir.path());
assert_eq!(detected.primary, ProjectType::Go);
}
#[test]
fn test_detect_python_project() {
let dir = tempdir().unwrap();
File::create(dir.path().join("pyproject.toml")).unwrap();
let detected = detect_project_type(dir.path());
assert_eq!(detected.primary, ProjectType::Python);
}
#[test]
fn test_detect_multi_stack_project() {
let dir = tempdir().unwrap();
File::create(dir.path().join("package.json")).unwrap();
File::create(dir.path().join("Dockerfile")).unwrap();
let detected = detect_project_type(dir.path());
assert!(detected.is_multi_stack());
assert!(detected.all.contains(&ProjectType::NodeJs));
assert!(detected.all.contains(&ProjectType::Docker));
}
#[test]
fn test_detect_unknown_project() {
let dir = tempdir().unwrap();
let detected = detect_project_type(dir.path());
assert_eq!(detected.primary, ProjectType::Unknown);
}
#[test]
fn test_project_type_display_name() {
assert_eq!(ProjectType::NodeJs.display_name(), "Node.js");
assert_eq!(ProjectType::Rust.display_name(), "Rust");
assert_eq!(ProjectType::Go.display_name(), "Go");
}
#[test]
fn test_project_type_suggested_template() {
assert_eq!(ProjectType::Rust.suggested_template(), Some("rust-cli"));
assert_eq!(ProjectType::Go.suggested_template(), Some("go-api"));
assert_eq!(ProjectType::Unknown.suggested_template(), Some("essential"));
}
#[test]
fn test_is_ci_environment() {
let _ = is_ci_environment();
}
}