use anyhow::Result;
use rusqlite::Connection;
use crate::infrastructure::config::Config;
use crate::infrastructure::model::Project;
use crate::infrastructure::project::find_git_root;
mod render;
pub fn detect_stack(path: &str) -> String {
let root = std::path::Path::new(path);
let mut stacks = vec![];
let markers = [
("Cargo.toml", "Rust"),
("package.json", "Node.js/JS"),
("pyproject.toml", "Python"),
("requirements.txt", "Python"),
("go.mod", "Go"),
("pom.xml", "Java/Maven"),
("build.gradle", "Java/Gradle"),
("Gemfile", "Ruby"),
("composer.json", "PHP"),
("pubspec.yaml", "Dart/Flutter"),
("*.swift", "Swift"),
("CMakeLists.txt", "C/C++"),
("mix.exs", "Elixir"),
];
for (file, label) in &markers {
if file.contains('*') {
} else if root.join(file).exists() {
stacks.push(*label);
}
}
if let Ok(rd) = std::fs::read_dir(root) {
for entry in rd.flatten() {
if entry
.path()
.extension()
.and_then(|e| e.to_str())
.map(|e| e == "swift")
.unwrap_or(false)
&& !stacks.contains(&"Swift")
{
stacks.push("Swift");
}
}
}
if stacks.is_empty() {
"unknown".to_string()
} else {
stacks.join(", ")
}
}
#[allow(clippy::too_many_arguments)]
pub fn run(
conn: &Connection,
cfg: &Config,
name_override: Option<&str>,
goal_override: Option<&str>,
stack_override: Option<&str>,
conventions_override: Option<&str>,
notes_override: Option<&str>,
setup_cmd: Option<&str>,
test_cmd: Option<&str>,
lint_cmd: Option<&str>,
run_cmd: Option<&str>,
yes: bool,
) -> Result<()> {
let cwd = std::env::current_dir()?;
let git_root = find_git_root(&cwd);
let (resolved_name, resolved_path) =
crate::infrastructure::project::project_identity_for_dir(&cwd, cfg);
let project_name = name_override.map(str::to_string).unwrap_or(resolved_name);
let project_path = Some(resolved_path);
if git_root.is_none() {
render::note_not_in_git(&project_name);
}
let detected_stack = project_path
.as_deref()
.map(detect_stack)
.unwrap_or_else(|| "unknown".to_string());
render::print_intro(&project_name, &detected_stack);
let existing = crate::infrastructure::db::get_project(conn, &project_name)?;
let resolved_stack = stack_override
.map(str::to_string)
.or_else(|| existing.as_ref().and_then(|p| p.stack.clone()))
.unwrap_or_else(|| detected_stack.clone());
let goal = if let Some(g) = goal_override {
g.to_string()
} else if yes {
existing
.as_ref()
.and_then(|p| p.goal.clone())
.unwrap_or_default()
} else {
let current_goal = existing.as_ref().and_then(|p| p.goal.as_deref());
render::prompt("What is this project? (one-line goal)", current_goal)?
};
let notes = if let Some(n) = notes_override {
n.to_string()
} else if yes {
existing
.as_ref()
.and_then(|p| p.notes.clone())
.unwrap_or_default()
} else {
let current_notes = existing.as_ref().and_then(|p| p.notes.as_deref());
render::prompt("Any conventions or notes? (optional)", current_notes)?
};
let conventions = conventions_override
.map(str::to_string)
.or_else(|| existing.as_ref().and_then(|p| p.conventions.clone()));
let project = Project {
name: project_name.clone(),
path: project_path,
goal: if goal.is_empty() {
None
} else {
Some(goal.clone())
},
stack: Some(resolved_stack.clone()),
conventions,
notes: if notes.is_empty() { None } else { Some(notes) },
initialized_at: Some(chrono::Utc::now()),
last_seen: Some(chrono::Utc::now()),
github_repo: None,
github_login: None,
github_sync_scope: None,
};
crate::infrastructure::db::save_project_profile(conn, &project)?;
if setup_cmd.is_some() || test_cmd.is_some() || lint_cmd.is_some() || run_cmd.is_some() {
crate::infrastructure::db::set_project_commands(
conn,
&project_name,
&crate::infrastructure::db::ProjectCommands {
setup_cmd: setup_cmd.map(str::to_string),
test_cmd: test_cmd.map(str::to_string),
lint_cmd: lint_cmd.map(str::to_string),
run_cmd: run_cmd.map(str::to_string),
},
)?;
}
let commands = crate::infrastructure::db::get_project_commands(conn, &project_name)?;
render::print_saved(&project, &resolved_stack, &commands);
Ok(())
}