outlayer_cli/commands/
deploy.rs1use anyhow::{Context, Result};
2use serde_json::json;
3use sha2::{Digest, Sha256};
4
5use crate::config::{self, NetworkConfig};
6use crate::near::{ContractCaller, NearClient};
7
8pub async fn deploy(
10 network: &NetworkConfig,
11 project_name: &str,
12 wasm_url: Option<String>,
13 wasm_hash: Option<String>,
14 build_target: &str,
15 no_activate: bool,
16) -> Result<()> {
17 let creds = config::load_credentials(network)?;
18 let owner = &creds.account_id;
19
20 let (source, version_label) = if let Some(url) = &wasm_url {
21 let hash = match &wasm_hash {
23 Some(h) => h.clone(),
24 None => {
25 eprintln!("Downloading WASM to compute hash...");
26 compute_wasm_hash(url).await?
27 }
28 };
29 let source = json!({
30 "WasmUrl": {
31 "url": url,
32 "hash": hash,
33 "build_target": build_target
34 }
35 });
36 let short_hash = if hash.len() > 12 { &hash[..12] } else { &hash };
37 (source, format!("{short_hash}..."))
38 } else {
39 let commit = get_git_commit()?;
41 let repo_url = get_git_remote()?;
42 let source = json!({
43 "GitHub": {
44 "repo": repo_url,
45 "commit": commit,
46 "build_target": build_target
47 }
48 });
49 (source, commit)
50 };
51
52 eprintln!("Deploying {owner}/{project_name}...");
53 if wasm_url.is_some() {
54 eprintln!(" Source: WasmUrl ({})", wasm_url.as_ref().unwrap());
55 } else {
56 eprintln!(" Source: GitHub ({version_label})");
57 }
58
59 let near = NearClient::new(network);
60 let caller = ContractCaller::from_credentials(&creds, network)?;
61
62 let project_id = format!("{owner}/{project_name}");
63 let deposit = 100_000_000_000_000_000_000_000u128; let gas = 100_000_000_000_000u64; let existing = near.get_project(&project_id).await?;
68
69 if existing.is_some() {
70 eprintln!(" Adding version {version_label}...");
71 caller
72 .call_contract(
73 "add_version",
74 json!({
75 "project_name": project_name,
76 "source": source,
77 "set_active": !no_activate
78 }),
79 gas,
80 deposit,
81 )
82 .await
83 .context("Failed to add version")?;
84 } else {
85 eprintln!(" Creating new project...");
86 caller
87 .call_contract(
88 "create_project",
89 json!({
90 "name": project_name,
91 "source": source
92 }),
93 gas,
94 deposit,
95 )
96 .await
97 .context("Failed to create project")?;
98 }
99
100 if no_activate {
101 eprintln!(" Version {version_label} deployed (not activated)");
102 } else {
103 eprintln!(" Project deployed");
104 }
105
106 eprintln!("\nRun with: outlayer run {owner}/{project_name} '{{\"command\": \"hello\"}}'");
107 Ok(())
108}
109
110async fn compute_wasm_hash(url: &str) -> Result<String> {
111 let response = reqwest::get(url)
112 .await
113 .with_context(|| format!("Failed to download WASM from {url}"))?;
114
115 if !response.status().is_success() {
116 anyhow::bail!("Failed to download WASM: HTTP {}", response.status());
117 }
118
119 let bytes = response
120 .bytes()
121 .await
122 .context("Failed to read WASM response body")?;
123
124 Ok(hex::encode(Sha256::digest(&bytes)))
125}
126
127fn get_git_commit() -> Result<String> {
128 let output = std::process::Command::new("git")
129 .args(["rev-parse", "HEAD"])
130 .output()
131 .context("Failed to run git rev-parse HEAD")?;
132
133 if !output.status.success() {
134 anyhow::bail!("Not a git repository. Initialize git first: git init && git commit");
135 }
136
137 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
138}
139
140fn get_git_remote() -> Result<String> {
141 let output = std::process::Command::new("git")
142 .args(["remote", "get-url", "origin"])
143 .output()
144 .context("Failed to get git remote URL")?;
145
146 if !output.status.success() {
147 anyhow::bail!("No git remote found. Add one: git remote add origin <url>");
148 }
149
150 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
151}