use super::*;
pub(in crate::commands::sdk) fn execute_sdk_rollback(
project_dir: PathBuf,
backup_override: Option<PathBuf>,
) -> Result<(PathBuf, PathBuf, PathBuf), String> {
let config_path = project_dir.join("robotrt.toml");
let backup_path = backup_override.unwrap_or_else(|| project_dir.join("robotrt.toml.bak"));
if !backup_path.exists() {
return Err(format!("backup file not found: {}", backup_path.display()));
}
if !config_path.exists() {
return Err(format!("config file not found: {}", config_path.display()));
}
let rollback_backup_path = project_dir.join("robotrt.toml.rollback.bak");
let current = fs::read_to_string(&config_path)
.map_err(|err| format!("read {} failed: {err}", config_path.display()))?;
fs::write(&rollback_backup_path, current).map_err(|err| {
format!(
"write rollback backup {} failed: {err}",
rollback_backup_path.display()
)
})?;
let backup = fs::read_to_string(&backup_path)
.map_err(|err| format!("read {} failed: {err}", backup_path.display()))?;
fs::write(&config_path, backup)
.map_err(|err| format!("restore config {} failed: {err}", config_path.display()))?;
Ok((config_path, backup_path, rollback_backup_path))
}
pub(in crate::commands::sdk) fn write_scaffold(
project_dir: &Path,
project_name: &str,
template: &str,
workspace_root: &Path,
) -> Result<(), String> {
fs::create_dir_all(project_dir.join("src")).map_err(|err| {
format!(
"create project directory {} failed: {err}",
project_dir.display()
)
})?;
write_file(
&project_dir.join("Cargo.toml"),
&render_cargo_toml(project_name, workspace_root),
)?;
write_file(
&project_dir.join("README.md"),
&render_readme(project_name, template),
)?;
write_file(
&project_dir.join("robotrt.toml"),
&render_robotrt_config(project_name, template),
)?;
write_file(&project_dir.join(".gitignore"), "target/\n")?;
write_file(
&project_dir.join("src/main.rs"),
&render_main_rs(project_name, template),
)?;
Ok(())
}
pub(in crate::commands::sdk) fn write_file(path: &Path, content: &str) -> Result<(), String> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.map_err(|err| format!("create directory {} failed: {err}", parent.display()))?;
}
fs::write(path, content).map_err(|err| format!("write {} failed: {err}", path.display()))
}
pub(in crate::commands::sdk) fn validate_project_name(name: &str) -> Result<(), String> {
if name.is_empty() {
return Err(String::from("project name must not be empty"));
}
let valid = name
.chars()
.all(|ch| ch.is_ascii_alphanumeric() || ch == '-' || ch == '_');
if !valid {
return Err(format!(
"invalid project name: {name} (expected [a-zA-Z0-9_-])"
));
}
Ok(())
}
pub(in crate::commands::sdk) fn is_supported_template(template: &str) -> bool {
matches!(
template,
"local" | "network" | "mission" | "service" | "action"
)
}
pub(in crate::commands::sdk) fn execute_sdk_migrate(
project_dir: PathBuf,
target_schema_version: u64,
dry_run: bool,
) -> Result<SdkMigrateExecution, String> {
if !project_dir.exists() {
return Err(format!("project dir not found: {}", project_dir.display()));
}
if !project_dir.is_dir() {
return Err(format!(
"project path is not a directory: {}",
project_dir.display()
));
}
let config_path = project_dir.join("robotrt.toml");
if !config_path.exists() {
return Err(format!("missing robotrt config: {}", config_path.display()));
}
let original = fs::read_to_string(&config_path)
.map_err(|err| format!("read {} failed: {err}", config_path.display()))?;
let from_schema_version = parse_schema_version(&original).unwrap_or(1);
if target_schema_version < from_schema_version {
return Err(format!(
"target schema {} is lower than current schema {} (use sdk rollback for reverse operation)",
target_schema_version, from_schema_version
));
}
if target_schema_version > SDK_LATEST_SCHEMA_VERSION {
return Err(format!(
"unsupported target schema {} (latest supported: {})",
target_schema_version, SDK_LATEST_SCHEMA_VERSION
));
}
let fallback_project_name = project_dir
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("robotrt_app")
.to_string();
let (migrated, applied_steps) = migrate_robotrt_config_chain(
&original,
from_schema_version,
target_schema_version,
&fallback_project_name,
);
let backup_path = config_path.with_file_name("robotrt.toml.bak");
let manifest_path = project_dir.join("robotrt.migrate.manifest.json");
let updated = migrated != original;
if updated && !dry_run {
fs::write(&backup_path, original)
.map_err(|err| format!("write backup {} failed: {err}", backup_path.display()))?;
fs::write(&config_path, &migrated).map_err(|err| {
format!(
"write migrated config {} failed: {err}",
config_path.display()
)
})?;
let manifest = serde_json::json!({
"kind": "robotrt_sdk_migration_manifest",
"project_dir": project_dir,
"config_path": config_path,
"backup_path": backup_path,
"from_schema_version": from_schema_version,
"to_schema_version": target_schema_version,
"applied_steps": applied_steps,
});
fs::write(
&manifest_path,
serde_json::to_string_pretty(&manifest)
.map_err(|err| format!("serialize migration manifest failed: {err}"))?,
)
.map_err(|err| {
format!(
"write migration manifest {} failed: {err}",
manifest_path.display()
)
})?;
}
Ok(SdkMigrateExecution {
project_dir,
config_path,
backup_path: if updated { Some(backup_path) } else { None },
manifest_path: if updated && !dry_run {
Some(manifest_path)
} else {
None
},
applied_steps,
from_schema_version,
to_schema_version: target_schema_version,
updated,
dry_run,
})
}