use crate::error::{RailError, RailResult};
use crate::progress;
use crate::toml::TomlFormatter;
use crate::workspace::WorkspaceContext;
use std::fs;
use std::path::{Path, PathBuf};
pub fn run_init(
ctx: &WorkspaceContext,
output_path: &str,
force: bool,
dry_run: bool,
json: bool,
targets: &[String],
detect_targets: bool,
) -> RailResult<()> {
run_init_impl(
ctx.workspace_root(),
output_path,
force,
dry_run,
json,
targets,
detect_targets,
)
}
fn detect_targets(workspace_root: &Path) -> RailResult<Vec<String>> {
crate::targets::detect_targets(workspace_root)
}
fn build_sparse_config(targets: &[String]) -> String {
let mut content =
String::from("# cargo-rail configuration\n# Documentation: https://github.com/loadingalias/cargo-rail\n");
if !targets.is_empty() {
content.push_str("\n# Additional supported Cargo target-resolution views detected from repository files.\n");
content.push_str(&format!("targets = {}\n", TomlFormatter::new().array_targets(targets)));
}
content
}
fn check_existing_config(workspace_root: &Path) -> Option<PathBuf> {
crate::config::RailConfig::find_config_path(workspace_root)
}
fn ensure_output_dir(output_path: &Path) -> RailResult<()> {
if let Some(parent) = output_path.parent() {
fs::create_dir_all(parent)
.map_err(|e| RailError::message(format!("failed to create {}: {}", parent.display(), e)))?;
}
Ok(())
}
fn write_config_file(config_path: &Path, content: &str) -> RailResult<()> {
crate::utils::write_file_atomic(config_path, content.as_bytes())
}
fn is_cargo_workspace(workspace_root: &Path) -> bool {
let cargo_toml = workspace_root.join("Cargo.toml");
if !cargo_toml.exists() {
return false;
}
if let Ok(content) = fs::read_to_string(&cargo_toml) {
content.contains("[workspace]")
} else {
false
}
}
fn run_init_impl(
workspace_root: &Path,
output_path: &str,
force: bool,
dry_run: bool,
json: bool,
selected_targets: &[String],
detect_repository_targets: bool,
) -> RailResult<()> {
let config_path = workspace_root.join(output_path);
if !is_cargo_workspace(workspace_root) && !dry_run && !json && !crate::output::is_quiet() {
crate::warn!("no Cargo workspace detected in {}", workspace_root.display());
eprintln!(" cargo-rail works best with Cargo workspaces\n");
}
if config_path.exists() && !dry_run {
if !force {
return Err(RailError::with_help(
format!("configuration exists: {}", config_path.display()),
"use --force to overwrite, or use --dry-run to preview what would be generated",
));
}
if !json {
progress!("overwriting: {}", config_path.display());
}
} else if let Some(existing) = check_existing_config(workspace_root)
&& existing != config_path
{
return Err(RailError::with_help(
format!("active configuration already exists: {}", existing.display()),
"edit the active file, or pass its exact path with --output and --force",
));
}
let mut targets = selected_targets.to_vec();
if detect_repository_targets {
targets.extend(detect_targets(workspace_root)?);
}
targets.sort();
targets.dedup();
let toml_content = build_sparse_config(&targets);
if dry_run {
if json {
let payload = serde_json::json!({
"command": "init",
"dry_run": true,
"config_path": config_path.display().to_string(),
"targets": targets,
"content": toml_content
});
let output = crate::output::machine_json_envelope("init", "dry_run", "success", 0, payload);
println!(
"{}",
serde_json::to_string_pretty(&output)
.map_err(|error| RailError::message(format!("failed to serialize init result: {error}")))?
);
} else {
println!("Destination: {}\n", config_path.display());
print!("{}", toml_content);
}
} else {
ensure_output_dir(&config_path)?;
write_config_file(&config_path, &toml_content)?;
if json {
let payload = serde_json::json!({
"command": "init",
"status": "created",
"config_path": config_path.display().to_string(),
"targets": targets
});
let output = crate::output::machine_json_envelope("init", "apply", "created", 0, payload);
println!(
"{}",
serde_json::to_string_pretty(&output)
.map_err(|error| RailError::message(format!("failed to serialize init result: {error}")))?
);
} else {
println!("Created {}", config_path.display());
println!("Next: cargo rail config validate");
}
}
Ok(())
}
pub fn run_init_standalone(
workspace_root: &Path,
output_path: &str,
force: bool,
dry_run: bool,
json: bool,
targets: &[String],
detect_targets: bool,
) -> RailResult<()> {
run_init_impl(
workspace_root,
output_path,
force,
dry_run,
json,
targets,
detect_targets,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sparse_config_omits_coded_defaults() {
let toml = build_sparse_config(&[]);
assert!(toml.contains("cargo-rail configuration"));
assert!(toml.contains("Documentation:"));
assert!(!toml.contains("[unify]"));
assert!(!toml.contains("[release]"));
}
#[test]
fn sparse_config_writes_detected_targets() {
let toml = build_sparse_config(&["wasm32-unknown-unknown".to_string()]);
assert!(toml.contains("targets = ["));
assert!(toml.contains("wasm32-unknown-unknown"));
}
}