use std::path::Path;
use cuenv_core::Result;
use cuenv_core::contributors::{
ContributorContext, ContributorEngine, builtin_workspace_contributors,
};
use cuenv_core::manifest::Project;
use cuenv_core::tasks::TaskIndex;
fn apply_workspace_contributors(manifest: &mut Project, project_root: &Path) {
let context = ContributorContext::detect(project_root).with_task_commands(&manifest.tasks);
let contributors = builtin_workspace_contributors();
let engine = ContributorEngine::new(&contributors, context);
if let Err(error) = engine.apply(&mut manifest.tasks) {
tracing::warn!(
project_root = %project_root.display(),
error = %error,
"Failed to apply workspace contributors"
);
}
}
pub fn prepare_task_index(manifest: &mut Project, project_root: &Path) -> Result<TaskIndex> {
apply_workspace_contributors(manifest, project_root);
TaskIndex::build(&manifest.tasks)
}
#[cfg(test)]
mod tests {
use super::*;
use cuenv_core::contributors::CONTRIBUTOR_TASK_PREFIX;
use cuenv_core::tasks::{Task, TaskNode};
use std::fs;
use tempfile::TempDir;
fn create_test_dir() -> TempDir {
tempfile::Builder::new()
.prefix("cuenv_list_builder_test_")
.tempdir()
.expect("Failed to create temp directory")
}
#[test]
fn test_prepare_task_index_empty_manifest_no_lockfile() {
let tmp = create_test_dir();
let mut manifest = Project::default();
let result = prepare_task_index(&mut manifest, tmp.path());
assert!(result.is_ok());
let index = result.unwrap();
assert!(index.list().is_empty());
}
#[test]
fn test_prepare_task_index_preserves_explicit_tasks() {
let tmp = create_test_dir();
let mut manifest = Project::default();
manifest.tasks.insert(
"build".to_string(),
TaskNode::Task(Box::new(Task {
command: "echo build".to_string(),
description: Some("Build the project".to_string()),
..Default::default()
})),
);
manifest.tasks.insert(
"test".to_string(),
TaskNode::Task(Box::new(Task {
command: "echo test".to_string(),
..Default::default()
})),
);
let result = prepare_task_index(&mut manifest, tmp.path());
assert!(result.is_ok());
let index = result.unwrap();
let task_names: Vec<_> = index.list().iter().map(|t| t.name.as_str()).collect();
assert!(task_names.contains(&"build"), "should contain 'build' task");
assert!(task_names.contains(&"test"), "should contain 'test' task");
assert_eq!(task_names.len(), 2, "should have exactly 2 tasks");
}
#[test]
fn test_prepare_task_index_injects_bun_tasks_from_lockfile() {
let tmp = create_test_dir();
fs::write(tmp.path().join("bun.lock"), "lockfile content").unwrap();
let mut manifest = Project::default();
let result = prepare_task_index(&mut manifest, tmp.path());
assert!(result.is_ok());
let index = result.unwrap();
let task_names: Vec<_> = index.list().iter().map(|t| t.name.as_str()).collect();
let install_task = CONTRIBUTOR_TASK_PREFIX.replace(':', ".") + "bun.workspace.install";
let setup_task = CONTRIBUTOR_TASK_PREFIX.replace(':', ".") + "bun.workspace.setup";
assert!(
task_names.contains(&install_task.as_str()),
"should contain auto-injected '{}' task, got: {:?}",
install_task,
task_names
);
assert!(
task_names.contains(&setup_task.as_str()),
"should contain auto-injected '{}' task, got: {:?}",
setup_task,
task_names
);
}
#[test]
fn test_prepare_task_index_no_lockfile_no_inject() {
let tmp = create_test_dir();
let mut manifest = Project::default();
let result = prepare_task_index(&mut manifest, tmp.path());
assert!(result.is_ok());
let index = result.unwrap();
let task_names: Vec<_> = index.list().iter().map(|t| t.name.as_str()).collect();
let install_task = CONTRIBUTOR_TASK_PREFIX.replace(':', ".") + "bun.workspace.install";
assert!(
!task_names.contains(&install_task.as_str()),
"no lockfile should mean no injected tasks"
);
}
#[test]
fn test_prepare_task_index_auto_associates_bun_tasks() {
let tmp = create_test_dir();
fs::write(tmp.path().join("bun.lock"), "lockfile content").unwrap();
let mut manifest = Project::default();
manifest.tasks.insert(
"dev".to_string(),
TaskNode::Task(Box::new(Task {
command: "bun".to_string(),
args: vec!["run".to_string(), "dev".to_string()],
..Default::default()
})),
);
let result = prepare_task_index(&mut manifest, tmp.path());
assert!(result.is_ok());
let index = result.unwrap();
let dev_task = index.resolve("dev").unwrap();
let expected_dep = CONTRIBUTOR_TASK_PREFIX.replace(':', ".") + "bun.workspace.setup";
if let TaskNode::Task(task) = &dev_task.node {
assert!(
task.depends_on
.iter()
.any(|d| d.task_name() == expected_dep),
"bun task should auto-depend on {}, got: {:?}",
expected_dep,
task.depends_on
);
} else {
panic!("expected single task");
}
}
}