use crate::config::CliConfig;
use std::process::Command;
pub async fn run(
target_version: Option<i32>,
allow_destructive: bool,
env: Option<&str>,
) -> eyre::Result<()> {
let config = CliConfig::load_with_env(env)?;
if let Some(ref name) = config.env_name {
println!("Environment: {}", name);
}
let output = Command::new("cargo")
.args(["run", "--", "--emit-manifest"])
.output()?;
if !output.status.success() {
eyre::bail!("Failed to build service. Run from a cufflink service directory.");
}
let stdout = String::from_utf8(output.stdout)?;
let manifest: serde_json::Value = serde_json::from_str(stdout.trim())?;
let service_name = manifest["name"].as_str().unwrap_or("unknown");
let client = config.http_client();
let resp = config
.auth_request(
&client,
reqwest::Method::GET,
&format!("{}/api/services", config.api_url),
)
.send()
.await?;
let body: serde_json::Value = resp.json().await?;
let services = body["services"]
.as_array()
.ok_or_else(|| eyre::eyre!("No services found"))?;
let service = services
.iter()
.find(|s| s["name"].as_str() == Some(service_name))
.ok_or_else(|| eyre::eyre!("Service '{}' not found. Deploy it first.", service_name))?;
let service_id = service["id"]
.as_str()
.ok_or_else(|| eyre::eyre!("Service has no ID"))?;
let current_version = service["current_version"].as_i64().unwrap_or(0);
if current_version <= 1 && target_version.is_none() {
eyre::bail!(
"Cannot rollback: only one version exists (v{})",
current_version
);
}
let target = target_version.unwrap_or((current_version - 1) as i32);
println!(
"Rolling back {} from v{} to v{}...",
service_name, current_version, target
);
let rollback_req = serde_json::json!({
"target_version": target,
"allow_destructive": allow_destructive,
});
let resp = config
.auth_request(
&client,
reqwest::Method::POST,
&format!("{}/api/services/{}/rollback", config.api_url, service_id),
)
.json(&rollback_req)
.send()
.await?;
if resp.status().is_success() {
let body: serde_json::Value = resp.json().await?;
println!(
"Rolled back {} to v{} (now v{})",
body["name"].as_str().unwrap_or("?"),
body["rolled_back_to"],
body["new_version"]
);
println!(" Schema changes: {}", body["schema_changes"]);
println!(" Deployment ID: {}", body["deployment_id"]);
if body["wasm_restored"].as_bool() == Some(true) {
println!(" WASM binary: restored from v{}", target);
}
if let Some(commit) = body["rolled_back_from_commit"].as_str() {
println!(" Source commit: {}", commit);
}
} else {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
if body.contains("Destructive changes detected") {
eprintln!("Rollback blocked: {}", body);
eprintln!();
eprintln!("Re-run with --allow-destructive to force:");
eprintln!(" cufflink rollback --allow-destructive");
std::process::exit(1);
}
eyre::bail!("Rollback failed ({}): {}", status, body);
}
Ok(())
}