Skip to main content

interstice_cli/
module.rs

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