Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions examples/deploy-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "starforge-example-stack",
"network": "testnet",
"wallet": "deployer",
"contracts": [
{
"id": "token",
"wasm": "templates/examples/token-allowlist/target/wasm32v1-none/release/token_allowlist.wasm",
"depends_on": []
},
{
"id": "vault",
"wasm": "templates/examples/multisig-vault/target/wasm32v1-none/release/multisig_vault.wasm",
"depends_on": ["token"]
},
{
"id": "governance",
"wasm": "templates/examples/dao-governance/target/wasm32v1-none/release/dao_governance.wasm",
"depends_on": ["token", "vault"]
}
]
}
2 changes: 2 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ pub mod lint;
pub mod monitor;
pub mod network;
pub mod new;
pub mod orchestrate;
pub mod node;
pub mod plugin;
pub mod security;
pub mod shell;
pub mod telemetry;
pub mod template;
Expand Down
187 changes: 187 additions & 0 deletions src/commands/orchestrate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
use crate::utils::deploy_orchestrator::{
build_plan, execute_plan, list_states, load_manifest, load_state, render_dag, rollback,
save_state,
};
use crate::utils::print as p;
use anyhow::Result;
use clap::{Args, Subcommand};
use std::path::PathBuf;

#[derive(Subcommand)]
pub enum OrchestrateCommands {
/// Validate manifest and build deployment plan
Plan(PlanArgs),
/// Execute deployment plan (use --dry-run to simulate)
Execute(ExecuteArgs),
/// Roll back a completed deployment
Rollback(RollbackArgs),
/// List saved deployment states
List,
/// Visualize dependency graph
Visualize(VisualizeArgs),
/// Show deployment state by ID
Status(StatusArgs),
}

#[derive(Args)]
pub struct PlanArgs {
#[arg(long)]
pub file: PathBuf,
}

#[derive(Args)]
pub struct ExecuteArgs {
#[arg(long)]
pub file: Option<PathBuf>,
#[arg(long)]
pub id: Option<String>,
#[arg(long, default_value = "true")]
pub dry_run: bool,
}

#[derive(Args)]
pub struct RollbackArgs {
#[arg(long)]
pub id: String,
}

#[derive(Args)]
pub struct VisualizeArgs {
#[arg(long)]
pub file: PathBuf,
}

#[derive(Args)]
pub struct StatusArgs {
#[arg(long)]
pub id: String,
}

pub fn handle(cmd: OrchestrateCommands) -> Result<()> {
match cmd {
OrchestrateCommands::Plan(args) => handle_plan(args),
OrchestrateCommands::Execute(args) => handle_execute(args),
OrchestrateCommands::Rollback(args) => handle_rollback(args),
OrchestrateCommands::List => handle_list(),
OrchestrateCommands::Visualize(args) => handle_visualize(args),
OrchestrateCommands::Status(args) => handle_status(args),
}
}

fn handle_plan(args: PlanArgs) -> Result<()> {
p::header("Deployment Orchestration — Plan");
let manifest = load_manifest(&args.file)?;
let state = build_plan(&manifest)?;
let path = save_state(&state)?;

p::kv("Deployment ID", &state.id);
p::kv("Manifest", &manifest.name);
p::kv("Network", &state.network);
p::kv("Contracts", &state.steps.len().to_string());
p::kv("State file", &path.display().to_string());

println!();
for step in &state.steps {
p::kv(
&format!(" {}. {}", step.order, step.contract_id),
&format!("{} ({})", step.wasm.display(), &step.wasm_hash[..12]),
);
}

p::success("Deployment plan created");
Ok(())
}

fn handle_execute(args: ExecuteArgs) -> Result<()> {
p::header("Deployment Orchestration — Execute");

let mut state = if let Some(id) = args.id {
load_state(&id)?
} else if let Some(file) = args.file {
let manifest = load_manifest(&file)?;
build_plan(&manifest)?
} else {
anyhow::bail!("Specify --file or --id");
};

execute_plan(&mut state, args.dry_run)?;

p::kv("Deployment ID", &state.id);
p::kv("Status", &state.status);
for step in &state.steps {
println!(
" {} — {:?} {}",
step.contract_id,
step.status,
step.deployed_address.as_deref().unwrap_or("-")
);
}

p::success(if args.dry_run {
"Dry-run execution complete"
} else {
"Deployment execution complete"
});
Ok(())
}

fn handle_rollback(args: RollbackArgs) -> Result<()> {
p::header("Deployment Orchestration — Rollback");
let mut state = load_state(&args.id)?;
let rolled = rollback(&mut state)?;

p::kv("Deployment ID", &state.id);
p::kv("Rolled back", &rolled.join(", "));
p::success("Rollback complete");
Ok(())
}

fn handle_list() -> Result<()> {
p::header("Deployment Orchestration — List");
let states = list_states()?;
if states.is_empty() {
p::info("No deployment states found");
return Ok(());
}
for state in states {
println!(
" {} | {} | {} | {} contracts | {}",
state.id,
state.manifest_name,
state.network,
state.steps.len(),
state.status
);
}
Ok(())
}

fn handle_visualize(args: VisualizeArgs) -> Result<()> {
p::header("Deployment Orchestration — Visualization");
let manifest = load_manifest(&args.file)?;
println!("{}", render_dag(&manifest)?);
Ok(())
}

fn handle_status(args: StatusArgs) -> Result<()> {
p::header("Deployment Orchestration — Status");
let state = load_state(&args.id)?;
p::kv("ID", &state.id);
p::kv("Manifest", &state.manifest_name);
p::kv("Network", &state.network);
p::kv("Status", &state.status);
p::kv("Created", &state.created_at);
p::kv("Updated", &state.updated_at);

for step in &state.steps {
println!(
" {}. {} [{:?}] hash={} addr={}",
step.order,
step.contract_id,
step.status,
&step.wasm_hash[..12],
step.deployed_address.as_deref().unwrap_or("-")
);
}
Ok(())
}
Loading