Skip to main content

interstice_cli/
module.rs

1use interstice_core::{
2    IntersticeError, ModuleEventInstance, NetworkPacket,
3    packet::{read_packet, write_packet},
4};
5use serde_json::Value;
6use std::path::{Path, PathBuf};
7
8pub async fn publish(
9    node_address: String,
10    module_project_path: &Path,
11) -> Result<(), IntersticeError> {
12    // This should take a path to a rust project that it will build and publish. The module name is from Cargo.toml. It should build the project using cargo, then read the generated wasm file and send it to the node using the network module.
13    // It should also be able to use saved servers nodes with their adress to easily publish to known nodes.
14
15    // The cLI simulate a remote node instance
16    let cli_node_id = uuid::Uuid::new_v4();
17
18    // connect to node
19    let mut stream = tokio::net::TcpStream::connect(node_address).await.unwrap();
20    let packet = NetworkPacket::Handshake {
21        node_id: cli_node_id.to_string(),
22        address: "127.0.0.1:12345".into(),
23    };
24    write_packet(&mut stream, &packet).await?;
25    let _ = read_packet(&mut stream).await?;
26
27    // Build module using cargo
28    println!(
29        "Building module from {} using cargo...",
30        module_project_path.display()
31    );
32    let manifest_path = module_project_path.join("Cargo.toml");
33    let output = std::process::Command::new("cargo")
34        .args([
35            "build",
36            "--release",
37            "--target",
38            "wasm32-unknown-unknown",
39            "--manifest-path",
40            manifest_path.to_string_lossy().as_ref(),
41        ])
42        .output()
43        .expect("Failed to execute cargo build command");
44    if !output.status.success() {
45        return Err(IntersticeError::Internal(format!(
46            "Cargo build failed: {}",
47            String::from_utf8_lossy(&output.stderr)
48        )));
49    }
50    // Read generated wasm file
51    let wasm_path = resolve_wasm_path(&manifest_path)?;
52    let wasm_binary = std::fs::read(wasm_path).expect("Failed to read generated wasm file");
53
54    // Send wasm binary to node
55    let packet = NetworkPacket::ModuleEvent(ModuleEventInstance::Publish { wasm_binary });
56    write_packet(&mut stream, &packet).await?;
57
58    // Close connection properly
59    let packet = NetworkPacket::Close;
60    write_packet(&mut stream, &packet).await?;
61
62    Ok(())
63}
64
65pub async fn remove(node_address: String, module_name: &str) -> Result<(), IntersticeError> {
66    // This should take a module name and send a message to the node to delete the module with that name. It should also be able to use saved servers nodes with their adress to easily delete from known nodes.
67
68    // The cLI simulate a remote node instance
69    let cli_node_id = uuid::Uuid::new_v4();
70
71    // connect to node
72    let mut stream = tokio::net::TcpStream::connect(node_address).await.unwrap();
73    let packet = NetworkPacket::Handshake {
74        node_id: cli_node_id.to_string(),
75        address: "127.0.0.1:12345".into(),
76    };
77    write_packet(&mut stream, &packet).await?;
78    let _ = read_packet(&mut stream).await?;
79
80    // Send wasm binary to node
81    let packet = NetworkPacket::ModuleEvent(ModuleEventInstance::Remove {
82        module_name: module_name.into(),
83    });
84    write_packet(&mut stream, &packet).await?;
85
86    // Close connection properly
87    let packet = NetworkPacket::Close;
88    write_packet(&mut stream, &packet).await?;
89
90    Ok(())
91}
92
93fn resolve_wasm_path(manifest_path: &Path) -> Result<PathBuf, IntersticeError> {
94    let output = std::process::Command::new("cargo")
95        .args([
96            "metadata",
97            "--format-version",
98            "1",
99            "--no-deps",
100            "--manifest-path",
101            manifest_path.to_string_lossy().as_ref(),
102        ])
103        .output()
104        .map_err(|err| {
105            IntersticeError::Internal(format!("Failed to run cargo metadata: {}", err))
106        })?;
107
108    let metadata: Value = serde_json::from_slice(&output.stdout).map_err(|err| {
109        IntersticeError::Internal(format!("Failed to parse cargo metadata: {}", err))
110    })?;
111
112    let target_directory = metadata
113        .get("target_directory")
114        .and_then(|v| v.as_str())
115        .ok_or_else(|| IntersticeError::Internal("Missing target_directory".into()))?;
116
117    let packages = metadata
118        .get("packages")
119        .and_then(|v| v.as_array())
120        .ok_or_else(|| IntersticeError::Internal("Missing packages".into()))?;
121
122    let manifest_str = normalize_path(manifest_path)?;
123
124    let package = packages
125        .iter()
126        .find(|pkg| {
127            pkg.get("manifest_path")
128                .and_then(|v| v.as_str())
129                .and_then(|p| normalize_path_str(p).ok())
130                .map(|p| p == manifest_str)
131                .unwrap_or(false)
132        })
133        .ok_or_else(|| IntersticeError::Internal("Could not find package in metadata".into()))?;
134
135    let package_name = package
136        .get("name")
137        .and_then(|v| v.as_str())
138        .ok_or_else(|| IntersticeError::Internal("Missing package name".into()))?;
139
140    Ok(PathBuf::from(target_directory)
141        .join("wasm32-unknown-unknown")
142        .join("release")
143        .join(package_name)
144        .with_extension("wasm"))
145}
146
147fn normalize_path(path: &Path) -> Result<String, IntersticeError> {
148    let canonical = path
149        .canonicalize()
150        .map_err(|err| IntersticeError::Internal(format!("Invalid manifest path: {}", err)))?;
151    Ok(normalize_path_buf(canonical))
152}
153
154fn normalize_path_str(path: &str) -> Result<String, IntersticeError> {
155    Ok(normalize_path_buf(PathBuf::from(path)))
156}
157
158fn normalize_path_buf(path: PathBuf) -> String {
159    let s = path.to_string_lossy().replace('\\', "/");
160    s.strip_prefix("//?/")
161        .or_else(|| s.strip_prefix("\\\\?\\"))
162        .unwrap_or(&s)
163        .to_string()
164}