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 let cli_node_id = uuid::Uuid::new_v4();
17
18 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 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 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 let packet = NetworkPacket::ModuleEvent(ModuleEventInstance::Publish { wasm_binary });
56 write_packet(&mut stream, &packet).await?;
57
58 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 let cli_node_id = uuid::Uuid::new_v4();
70
71 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 let packet = NetworkPacket::ModuleEvent(ModuleEventInstance::Remove {
82 module_name: module_name.into(),
83 });
84 write_packet(&mut stream, &packet).await?;
85
86 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}