use std::path::Path;
pub struct Preset {
pub name: &'static str,
pub detect_files: &'static [&'static str],
pub detect_deps: &'static [&'static str],
pub entry_attributes: &'static [&'static str],
pub entry_functions: &'static [&'static str],
pub lifecycle_methods: &'static [&'static str],
}
static PRESETS: &[Preset] = &[
Preset {
name: "rust",
detect_files: &["Cargo.toml"],
detect_deps: &[],
entry_attributes: &[
"impl_trait:",
"derive:",
"serde_default:",
"serde_serialize_with:",
"serde_deserialize_with:",
"implements:",
],
entry_functions: &[],
lifecycle_methods: &[],
},
Preset {
name: "spring",
detect_files: &["pom.xml", "build.gradle", "build.gradle.kts"],
detect_deps: &["spring-boot", "spring-framework", "org.springframework"],
entry_attributes: &[
"Bean",
"Controller",
"RestController",
"Service",
"Component",
"Repository",
"Configuration",
"Scheduled",
"PostConstruct",
"PreDestroy",
"RequestMapping",
"GetMapping",
"PostMapping",
"PutMapping",
"DeleteMapping",
"PatchMapping",
"EventListener",
"Async",
"Transactional",
],
entry_functions: &[],
lifecycle_methods: &[],
},
Preset {
name: "nestjs",
detect_files: &[],
detect_deps: &["@nestjs/core", "@nestjs/common"],
entry_attributes: &[
"route",
"component",
"Controller",
"Injectable",
"Module",
"Pipe",
"Guard",
"Interceptor",
"Middleware",
"Get",
"Post",
"Put",
"Delete",
"Patch",
"Head",
"Options",
"All",
],
entry_functions: &[],
lifecycle_methods: &[
"onModuleInit",
"onModuleDestroy",
"onApplicationBootstrap",
"onApplicationShutdown",
],
},
Preset {
name: "express",
detect_files: &[],
detect_deps: &["express"],
entry_attributes: &["route", "handler", "endpoint"],
entry_functions: &[],
lifecycle_methods: &[],
},
Preset {
name: "django",
detect_files: &["manage.py"],
detect_deps: &["django", "Django"],
entry_attributes: &[
"route",
"login_required",
"permission_required",
"csrf_exempt",
"require_http_methods",
],
entry_functions: &[],
lifecycle_methods: &["setUp", "tearDown", "setUpClass", "tearDownClass"],
},
Preset {
name: "flask",
detect_files: &[],
detect_deps: &["flask", "Flask"],
entry_attributes: &["route", "before_request", "after_request", "errorhandler"],
entry_functions: &[],
lifecycle_methods: &[],
},
Preset {
name: "fastapi",
detect_files: &[],
detect_deps: &["fastapi", "FastAPI"],
entry_attributes: &["route", "Depends"],
entry_functions: &[],
lifecycle_methods: &[],
},
Preset {
name: "cdk",
detect_files: &["cdk.json"],
detect_deps: &["aws-cdk-lib", "@aws-cdk/core"],
entry_attributes: &["component"],
entry_functions: &[],
lifecycle_methods: &[],
},
Preset {
name: "aspnet",
detect_files: &[],
detect_deps: &["Microsoft.AspNetCore"],
entry_attributes: &[
"HttpGet",
"HttpPost",
"HttpPut",
"HttpDelete",
"ApiController",
"Authorize",
"AllowAnonymous",
],
entry_functions: &[],
lifecycle_methods: &[],
},
Preset {
name: "lombok",
detect_files: &[],
detect_deps: &["lombok", "org.projectlombok"],
entry_attributes: &[
"Data",
"Getter",
"Setter",
"Builder",
"NoArgsConstructor",
"AllArgsConstructor",
"RequiredArgsConstructor",
"Value",
"EqualsAndHashCode",
"ToString",
],
entry_functions: &[],
lifecycle_methods: &[],
},
Preset {
name: "jpa",
detect_files: &[],
detect_deps: &["javax.persistence", "jakarta.persistence", "hibernate"],
entry_attributes: &[
"Entity",
"Table",
"MappedSuperclass",
"Embeddable",
"Column",
"Id",
"GeneratedValue",
],
entry_functions: &[],
lifecycle_methods: &[],
},
Preset {
name: "react",
detect_files: &[],
detect_deps: &["react", "react-dom", "next"],
entry_attributes: &[],
entry_functions: &["App"],
lifecycle_methods: &[
"render",
"componentDidMount",
"componentDidUpdate",
"componentWillUnmount",
"componentDidCatch",
"getDerivedStateFromError",
"shouldComponentUpdate",
"getSnapshotBeforeUpdate",
"getStaticProps",
"getServerSideProps",
"getStaticPaths",
],
},
Preset {
name: "vue",
detect_files: &[],
detect_deps: &["vue", "nuxt"],
entry_attributes: &[],
entry_functions: &[],
lifecycle_methods: &[
"mounted",
"created",
"beforeDestroy",
"destroyed",
"beforeMount",
"beforeCreate",
"updated",
"beforeUpdate",
"activated",
"deactivated",
"setup",
],
},
Preset {
name: "angular",
detect_files: &["angular.json"],
detect_deps: &["@angular/core"],
entry_attributes: &["Component", "Injectable", "NgModule", "Directive", "Pipe"],
entry_functions: &[],
lifecycle_methods: &[
"ngOnInit",
"ngOnDestroy",
"ngOnChanges",
"ngAfterViewInit",
"ngAfterContentInit",
"ngAfterViewChecked",
"ngAfterContentChecked",
"ngDoCheck",
],
},
Preset {
name: "axum",
detect_files: &[],
detect_deps: &["axum"],
entry_attributes: &["tokio::main", "debug_handler"],
entry_functions: &["router", "app"],
lifecycle_methods: &[],
},
Preset {
name: "actix",
detect_files: &[],
detect_deps: &["actix-web"],
entry_attributes: &["actix_web::main", "get", "post", "put", "delete"],
entry_functions: &[],
lifecycle_methods: &[],
},
];
pub fn get_preset(name: &str) -> Option<&'static Preset> {
PRESETS.iter().find(|p| p.name == name)
}
pub fn auto_detect_presets(root: &Path) -> Vec<String> {
let mut active = Vec::new();
for preset in PRESETS {
for detect_file in preset.detect_files {
if root.join(detect_file).exists() {
active.push(preset.name.to_string());
break;
}
}
if active.last().map(|s| s.as_str()) == Some(preset.name) {
continue;
}
if !preset.detect_deps.is_empty() {
if let Some(deps) = read_package_json_deps(root) {
if preset
.detect_deps
.iter()
.any(|d| deps.contains(&d.to_string()))
{
active.push(preset.name.to_string());
continue;
}
}
}
}
active
}
fn read_package_json_deps(root: &Path) -> Option<Vec<String>> {
let pkg_path = root.join("package.json");
let content = std::fs::read_to_string(pkg_path).ok()?;
let json: serde_json::Value = serde_json::from_str(&content).ok()?;
let mut deps = Vec::new();
for key in &["dependencies", "devDependencies", "peerDependencies"] {
if let Some(obj) = json.get(key).and_then(|v| v.as_object()) {
for dep_name in obj.keys() {
deps.push(dep_name.clone());
}
}
}
Some(deps)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_preset_spring() {
let preset = get_preset("spring");
assert!(preset.is_some());
assert!(preset.unwrap().entry_attributes.contains(&"Bean"));
}
#[test]
fn test_get_preset_unknown() {
assert!(get_preset("nonexistent").is_none());
}
#[test]
fn test_auto_detect_rust() {
use std::io::Write;
let dir = tempfile::TempDir::new().unwrap();
let cargo_path = dir.path().join("Cargo.toml");
let mut f = std::fs::File::create(&cargo_path).unwrap();
writeln!(f, "[package]\nname = \"test\"").unwrap();
let presets = auto_detect_presets(dir.path());
assert!(
presets.contains(&"rust".to_string()),
"Should detect rust preset, got: {:?}",
presets
);
}
#[test]
fn test_auto_detect_nestjs() {
use std::io::Write;
let dir = tempfile::TempDir::new().unwrap();
let pkg_path = dir.path().join("package.json");
let mut f = std::fs::File::create(&pkg_path).unwrap();
writeln!(f, r#"{{"dependencies": {{"@nestjs/core": "^10.0.0"}}}}"#).unwrap();
let presets = auto_detect_presets(dir.path());
assert!(
presets.contains(&"nestjs".to_string()),
"Should detect nestjs preset, got: {:?}",
presets
);
}
}