use anyhow::{bail, Context, Result};
use std::fs;
use std::path::Path;
pub fn handle_init(current_dir: &Path, force: bool) -> Result<()> {
let config_path = current_dir.join(".stand.toml");
let existed = config_path.exists();
if existed && !force {
bail!(
"Stand is already initialized in this directory.\n\
Use 'stand init --force' to overwrite the existing configuration."
);
}
let template = generate_default_template();
fs::write(&config_path, &template)
.with_context(|| format!("Failed to write .stand.toml to {}", config_path.display()))?;
set_secure_permissions(&config_path)?;
if existed {
println!("✓ Overwritten existing .stand.toml");
} else {
println!("✓ Created .stand.toml");
}
println!("\nNext steps:");
println!(" 1. Edit .stand.toml to add your environment variables");
println!(" 2. Run 'stand list' to see available environments");
println!(" 3. Run 'stand shell <env>' to start a shell with that environment");
Ok(())
}
fn set_secure_permissions(path: &Path) -> Result<()> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(path)?.permissions();
perms.set_mode(0o600); fs::set_permissions(path, perms)
.with_context(|| format!("Failed to set permissions for {}", path.display()))?;
}
#[cfg(windows)]
{
let _ = path; }
Ok(())
}
fn generate_default_template() -> String {
r#"version = "2.0"
# Common variables shared across all environments
# Uncomment and add your shared variables here:
# [common]
# APP_NAME = "MyApp"
# Development environment
[environments.dev]
description = "Development environment"
# Display color in terminal (red, green, blue, yellow, purple, cyan)
color = "green"
# Add your environment variables here:
# DATABASE_URL = "postgres://localhost/myapp_dev"
# Production environment
[environments.prod]
description = "Production environment"
color = "red"
# Require confirmation before activating this environment
requires_confirmation = true
# Add your environment variables here:
# DATABASE_URL = "postgres://prod.example.com/myapp"
"#
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_generate_default_template_is_valid_toml() {
let template = generate_default_template();
let parsed: Result<toml::Value, _> = toml::from_str(&template);
assert!(
parsed.is_ok(),
"Generated template should be valid TOML: {:?}",
parsed.err()
);
}
#[test]
fn test_generate_default_template_contains_version() {
let template = generate_default_template();
assert!(template.contains(r#"version = "2.0""#));
}
#[test]
fn test_generate_default_template_contains_dev_environment() {
let template = generate_default_template();
assert!(template.contains("[environments.dev]"));
assert!(template.contains(r#"description = "Development environment""#));
assert!(template.contains(r#"color = "green""#));
}
#[test]
fn test_generate_default_template_contains_prod_environment() {
let template = generate_default_template();
assert!(template.contains("[environments.prod]"));
assert!(template.contains(r#"description = "Production environment""#));
assert!(template.contains(r#"color = "red""#));
assert!(template.contains("requires_confirmation = true"));
}
#[test]
fn test_generate_default_template_contains_common_section_comment() {
let template = generate_default_template();
assert!(template.contains("# [common]"));
}
#[test]
fn test_generate_default_template_contains_color_options_comment() {
let template = generate_default_template();
assert!(template.contains("red, green, blue, yellow, purple, cyan"));
}
#[test]
fn test_generate_default_template_contains_requires_confirmation_comment() {
let template = generate_default_template();
assert!(template.contains("# Require confirmation before activating"));
}
#[test]
fn test_init_creates_stand_toml() {
let dir = tempdir().unwrap();
let result = handle_init(dir.path(), false);
assert!(result.is_ok());
let config_path = dir.path().join(".stand.toml");
assert!(config_path.exists());
let content = fs::read_to_string(&config_path).unwrap();
assert!(content.contains(r#"version = "2.0""#));
assert!(content.contains("[environments.dev]"));
assert!(content.contains("[environments.prod]"));
}
#[test]
fn test_init_fails_when_config_exists_without_force() {
let dir = tempdir().unwrap();
fs::write(dir.path().join(".stand.toml"), "existing").unwrap();
let result = handle_init(dir.path(), false);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("already initialized"));
assert!(error_msg.contains("--force"));
}
#[test]
fn test_init_overwrites_with_force_flag() {
let dir = tempdir().unwrap();
fs::write(dir.path().join(".stand.toml"), "old content").unwrap();
let result = handle_init(dir.path(), true);
assert!(result.is_ok());
let content = fs::read_to_string(dir.path().join(".stand.toml")).unwrap();
assert!(content.contains(r#"version = "2.0""#));
assert!(!content.contains("old content"));
}
#[test]
#[cfg(unix)]
fn test_init_sets_secure_permissions() {
use std::os::unix::fs::PermissionsExt;
let dir = tempdir().unwrap();
handle_init(dir.path(), false).unwrap();
let config_path = dir.path().join(".stand.toml");
let metadata = fs::metadata(&config_path).unwrap();
let permissions = metadata.permissions();
assert_eq!(
permissions.mode() & 0o777,
0o600,
"Config file should have 0600 permissions"
);
}
}