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 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 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 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 let packet = NetworkPacket::ModuleEvent(ModuleEventInstance::Publish { wasm_binary });
47 write_packet(&mut stream, &packet).await?;
48
49 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 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 let packet = NetworkPacket::ModuleEvent(ModuleEventInstance::Remove {
68 module_name: module_name.into(),
69 });
70 write_packet(&mut stream, &packet).await?;
71
72 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}