huskies: merge 1110 story Chat bootstrap Phase 2b: additional stack overlays (Go, Python, Ruby, JVM)
This commit is contained in:
@@ -97,7 +97,8 @@ pub fn detect_stack(project_path: &Path, stacks_dir: &Path) -> (Option<String>,
|
||||
Err(_) => return (None, vec![]),
|
||||
};
|
||||
|
||||
let mut matched: Vec<String> = Vec::new();
|
||||
// (stack_name, number_of_matched_marker_files)
|
||||
let mut matched: Vec<(String, usize)> = Vec::new();
|
||||
|
||||
let mut stack_dirs: Vec<_> = entries
|
||||
.filter_map(|e| e.ok())
|
||||
@@ -112,26 +113,32 @@ pub fn detect_stack(project_path: &Path, stacks_dir: &Path) -> (Option<String>,
|
||||
let Ok(content) = std::fs::read_to_string(&markers_path) else {
|
||||
continue;
|
||||
};
|
||||
let hit = content.lines().any(|line| {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() || trimmed.starts_with('#') {
|
||||
return false;
|
||||
}
|
||||
project_path.join(trimmed).exists()
|
||||
});
|
||||
if hit {
|
||||
matched.push(stack_name);
|
||||
let count = content
|
||||
.lines()
|
||||
.filter(|line| {
|
||||
let trimmed = line.trim();
|
||||
!trimmed.is_empty()
|
||||
&& !trimmed.starts_with('#')
|
||||
&& project_path.join(trimmed).exists()
|
||||
})
|
||||
.count();
|
||||
if count > 0 {
|
||||
matched.push((stack_name, count));
|
||||
}
|
||||
}
|
||||
|
||||
match matched.len() {
|
||||
0 => (None, vec![]),
|
||||
1 => (Some(matched.remove(0)), vec![]),
|
||||
1 => (Some(matched.remove(0).0), vec![]),
|
||||
_ => {
|
||||
let names = matched.join(", ");
|
||||
let chosen = matched.remove(0);
|
||||
// Dominant stack: most marker files matched; alphabetical tiebreak for stability.
|
||||
matched.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
|
||||
let names: Vec<String> = matched.iter().map(|(n, _)| n.clone()).collect();
|
||||
let names_str = names.join(", ");
|
||||
let chosen = matched.swap_remove(0).0;
|
||||
let warning = format!(
|
||||
"Multiple stacks detected ({names}); using **{chosen}**. \
|
||||
"Multiple stacks detected ({names_str}); using **{chosen}** \
|
||||
(most marker files matched). \
|
||||
Pass `--stack <name>` to override."
|
||||
);
|
||||
(Some(chosen), vec![warning])
|
||||
@@ -527,4 +534,171 @@ mod tests {
|
||||
assert_eq!(stack, None);
|
||||
assert!(warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_stack_go_mod() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("go.mod"), "module example").unwrap();
|
||||
|
||||
let stacks = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir_all(stacks.path().join("go")).unwrap();
|
||||
std::fs::write(stacks.path().join("go/markers"), "go.mod\n").unwrap();
|
||||
|
||||
let (stack, warnings) = detect_stack(dir.path(), stacks.path());
|
||||
assert_eq!(stack, Some("go".to_string()));
|
||||
assert!(warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_stack_python_pyproject() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("pyproject.toml"), "[tool.poetry]").unwrap();
|
||||
|
||||
let stacks = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir_all(stacks.path().join("python")).unwrap();
|
||||
std::fs::write(
|
||||
stacks.path().join("python/markers"),
|
||||
"pyproject.toml\nrequirements.txt\nsetup.py\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (stack, warnings) = detect_stack(dir.path(), stacks.path());
|
||||
assert_eq!(stack, Some("python".to_string()));
|
||||
assert!(warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_stack_python_requirements_txt() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("requirements.txt"), "flask\n").unwrap();
|
||||
|
||||
let stacks = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir_all(stacks.path().join("python")).unwrap();
|
||||
std::fs::write(
|
||||
stacks.path().join("python/markers"),
|
||||
"pyproject.toml\nrequirements.txt\nsetup.py\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (stack, warnings) = detect_stack(dir.path(), stacks.path());
|
||||
assert_eq!(stack, Some("python".to_string()));
|
||||
assert!(warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_stack_python_setup_py() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("setup.py"), "from setuptools import setup").unwrap();
|
||||
|
||||
let stacks = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir_all(stacks.path().join("python")).unwrap();
|
||||
std::fs::write(
|
||||
stacks.path().join("python/markers"),
|
||||
"pyproject.toml\nrequirements.txt\nsetup.py\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (stack, warnings) = detect_stack(dir.path(), stacks.path());
|
||||
assert_eq!(stack, Some("python".to_string()));
|
||||
assert!(warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_stack_ruby_gemfile() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("Gemfile"), "source 'https://rubygems.org'").unwrap();
|
||||
|
||||
let stacks = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir_all(stacks.path().join("ruby")).unwrap();
|
||||
std::fs::write(stacks.path().join("ruby/markers"), "Gemfile\n").unwrap();
|
||||
|
||||
let (stack, warnings) = detect_stack(dir.path(), stacks.path());
|
||||
assert_eq!(stack, Some("ruby".to_string()));
|
||||
assert!(warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_stack_jvm_pom_xml() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("pom.xml"), "<project/>").unwrap();
|
||||
|
||||
let stacks = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir_all(stacks.path().join("jvm")).unwrap();
|
||||
std::fs::write(
|
||||
stacks.path().join("jvm/markers"),
|
||||
"pom.xml\nbuild.gradle\nbuild.gradle.kts\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (stack, warnings) = detect_stack(dir.path(), stacks.path());
|
||||
assert_eq!(stack, Some("jvm".to_string()));
|
||||
assert!(warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_stack_jvm_build_gradle() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("build.gradle"), "plugins { }").unwrap();
|
||||
|
||||
let stacks = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir_all(stacks.path().join("jvm")).unwrap();
|
||||
std::fs::write(
|
||||
stacks.path().join("jvm/markers"),
|
||||
"pom.xml\nbuild.gradle\nbuild.gradle.kts\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (stack, warnings) = detect_stack(dir.path(), stacks.path());
|
||||
assert_eq!(stack, Some("jvm".to_string()));
|
||||
assert!(warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_stack_jvm_build_gradle_kts() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("build.gradle.kts"), "plugins { }").unwrap();
|
||||
|
||||
let stacks = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir_all(stacks.path().join("jvm")).unwrap();
|
||||
std::fs::write(
|
||||
stacks.path().join("jvm/markers"),
|
||||
"pom.xml\nbuild.gradle\nbuild.gradle.kts\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (stack, warnings) = detect_stack(dir.path(), stacks.path());
|
||||
assert_eq!(stack, Some("jvm".to_string()));
|
||||
assert!(warnings.is_empty());
|
||||
}
|
||||
|
||||
/// A polyglot repo with more Python markers than Node markers should prefer python.
|
||||
#[test]
|
||||
fn detect_stack_multiple_dominant_wins() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
// Two Python markers: pyproject.toml + requirements.txt
|
||||
std::fs::write(dir.path().join("pyproject.toml"), "").unwrap();
|
||||
std::fs::write(dir.path().join("requirements.txt"), "").unwrap();
|
||||
// One Node marker: package.json (e.g. for a build tool)
|
||||
std::fs::write(dir.path().join("package.json"), "{}").unwrap();
|
||||
|
||||
let stacks = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir_all(stacks.path().join("node")).unwrap();
|
||||
std::fs::write(
|
||||
stacks.path().join("node/markers"),
|
||||
"package.json\ntsconfig.json\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::create_dir_all(stacks.path().join("python")).unwrap();
|
||||
std::fs::write(
|
||||
stacks.path().join("python/markers"),
|
||||
"pyproject.toml\nrequirements.txt\nsetup.py\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (stack, warnings) = detect_stack(dir.path(), stacks.path());
|
||||
// python matches 2 markers, node matches 1 — python should win.
|
||||
assert_eq!(stack, Some("python".to_string()));
|
||||
assert_eq!(warnings.len(), 1);
|
||||
assert!(warnings[0].contains("Multiple stacks"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user