use anyhow::{Result};
use colored::Colorize;
use std::fs;
use std::path::Path;
use dialoguer::Confirm;
use ferrisup_common::cargo;
use crate::commands::test_mode::{is_test_mode, test_mode_or};
use super::project_structure::{analyze_project_structure, detect_framework};
use super::utils::{store_transformation_metadata, store_component_type_in_cargo, update_source_imports};
use super::ui::get_input_with_default;
use super::workspace_utils::{
select_files_to_keep_at_root,
move_files_to_component,
update_component_cargo_toml,
create_workspace_cargo_toml,
finalize_workspace_setup,
categorize_files
};
use ferrisup_common::fs::create_directory;
pub fn convert_to_workspace(project_dir: &Path) -> Result<()> {
let structure = analyze_project_structure(project_dir)?;
let project_name = &structure.project_name;
let ferrisup_dir = project_dir.join(".ferrisup");
create_directory(&ferrisup_dir)?;
let default_name = project_name;
let mut component_name = test_mode_or(default_name.to_string(), || {
get_input_with_default(
&format!("What would you like to name the first component? [{}]", default_name),
default_name
)
})?;
if component_name == "ferrisup_common" || component_name == "shared" || component_name.ends_with("-common") || component_name.ends_with("_common") {
if !is_test_mode() {
let mut is_available = false;
while !is_available {
match cargo::is_crate_name_available(&component_name) {
Ok(available) => {
if available {
is_available = true;
println!(
"{} {}",
"Success:".green().bold(),
format!("Crate name '{}' is available on crates.io", component_name).green()
);
} else {
println!(
"{} {}",
"Warning:".yellow().bold(),
format!("Crate name '{}' is already taken on crates.io", component_name).yellow()
);
component_name = get_input_with_default(
"Please enter a different name for your shared component",
&format!("{}-common", project_name)
)?;
}
},
Err(e) => {
println!(
"{} {}",
"Warning:".yellow().bold(),
format!("Could not check crate name availability: {}", e).yellow()
);
is_available = true;
}
}
}
}
}
let component_dir = project_dir.join(&component_name);
create_directory(&component_dir)?;
create_directory(&component_dir.join("src"))?;
let files_to_keep_at_root = select_files_to_keep_at_root(project_dir, &component_name)?;
let always_skip_filenames = vec![
"Cargo.toml".to_string(),
"Cargo.lock".to_string(),
".git".to_string(),
".ferrisup".to_string(),
component_name.clone(),
];
let (critical_files_to_move, other_files_to_move, files_kept_at_root, _workspace_files) =
categorize_files(project_dir, &component_name, &files_to_keep_at_root)?;
if !critical_files_to_move.is_empty() {
println!("{}", "\nCritical files that MUST move to component:".yellow().bold());
for file in &critical_files_to_move {
println!(" → {} (required for component functionality)", file.cyan());
}
}
if !other_files_to_move.is_empty() {
println!("{}", "\nOther files that will move to component:".yellow());
for file in &other_files_to_move {
println!(" → {}", file.green());
}
}
if !files_kept_at_root.is_empty() {
println!("{}", "\nFiles that will stay at the root:".yellow());
for file in &files_kept_at_root {
println!(" → {}", file.blue());
}
}
if !is_test_mode() {
let proceed = Confirm::new()
.with_prompt("\nProceed with these file movements?")
.default(true)
.interact()?;
if !proceed {
println!("{}", "Workspace transformation cancelled.".red());
return Ok(());
}
}
move_files_to_component(project_dir, &component_dir, &files_to_keep_at_root, &always_skip_filenames)?;
let original_cargo_path = project_dir.join("Cargo.toml");
let component_cargo_path = component_dir.join("Cargo.toml");
fs::copy(&original_cargo_path, &component_cargo_path)?;
update_component_cargo_toml(&component_dir, &component_name)?;
update_source_imports(
&component_dir,
&project_name.to_lowercase(),
&component_name.to_lowercase(),
)?;
create_workspace_cargo_toml(project_dir, &component_name)?;
let src_main_path = component_dir.join("src/main.rs");
let src_lib_path = component_dir.join("src/lib.rs");
let detected_framework = detect_framework(&[&src_main_path, &src_lib_path]);
let template = match component_name.as_str() {
"client" => "client",
"server" => "server",
"ferrisup_common" => "ferrisup_common",
"edge" => "edge",
"serverless" => "serverless",
"data-science" => "data-science",
"embedded" => "embedded",
_ => "server", };
store_transformation_metadata(
project_dir,
&component_name,
template,
detected_framework.as_deref()
)?;
store_component_type_in_cargo(&component_dir, template)?;
finalize_workspace_setup(project_dir, &component_dir, &component_name, &files_to_keep_at_root)?;
if let Some(framework) = detected_framework {
println!("{} {}", "Detected framework:".blue(), framework.cyan());
}
Ok(())
}