crux_cli/codegen/
generate.rs1use std::path::{Path, PathBuf};
2
3use log::{debug, info};
4use serde_generate::{java, swift, typescript, Encoding, SourceInstaller};
5use serde_reflection::Registry;
6use std::{
7 fs::{self, File},
8 io::Write,
9};
10use thiserror::Error;
11
12pub type Result = std::result::Result<(), TypeGenError>;
13
14#[derive(Error, Debug)]
15pub enum TypeGenError {
16 #[error("type generation failed: {0}")]
17 Generation(String),
18 #[error("error writing generated types")]
19 Io(#[from] std::io::Error),
20 #[error("`pnpm` is needed for TypeScript type generation, but it could not be found in PATH.\nPlease install it from https://pnpm.io/installation")]
21 PnpmNotFound(#[source] std::io::Error),
22}
23
24pub fn swift(registry: &Registry, module_name: &str, path: impl AsRef<Path>) -> Result {
34 let path = path.as_ref().join(module_name);
35
36 remove_dir(&path);
38
39 fs::create_dir_all(&path)?;
40
41 info!("Generating Swift types at {}", path.display());
42
43 let installer = swift::Installer::new(path.clone());
44 installer
45 .install_serde_runtime()
46 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
47 installer
48 .install_bincode_runtime()
49 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
50
51 let config = serde_generate::CodeGeneratorConfig::new(module_name.to_string())
52 .with_encodings(vec![Encoding::Bincode]);
53
54 installer
55 .install_module(&config, registry)
56 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
57
58 let mut output = File::create(
60 path.join("Sources")
61 .join(module_name)
62 .join("Requests.swift"),
63 )?;
64
65 let requests_path = extensions_path("swift/requests.swift");
66
67 let requests_data = fs::read_to_string(requests_path)?;
68
69 write!(output, "{}", requests_data)?;
70
71 let mut output = File::create(path.join("Package.swift"))?;
73
74 let package_path = extensions_path("swift/Package.swift");
75
76 let package_data = fs::read_to_string(package_path)?;
77
78 write!(
79 output,
80 "{}",
81 package_data.replace("SharedTypes", module_name)
82 )?;
83
84 Ok(())
85}
86
87pub fn java(registry: &Registry, package_name: &str, path: impl AsRef<Path>) -> Result {
101 let path = path.as_ref();
102 fs::create_dir_all(&path)?;
103
104 let package_path = package_name.replace('.', "/");
105
106 remove_dir(path.join(&package_path));
108 remove_dir(path.join("com/novi/bincode"));
109 remove_dir(path.join("com/novi/serde"));
110
111 info!("Generating Java types at {}", path.display());
112
113 let config = serde_generate::CodeGeneratorConfig::new(package_name.to_string())
114 .with_encodings(vec![Encoding::Bincode]);
115
116 let installer = java::Installer::new(path.to_path_buf());
117 installer
118 .install_serde_runtime()
119 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
120 installer
121 .install_bincode_runtime()
122 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
123
124 installer
125 .install_module(&config, registry)
126 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
127
128 let requests_path = extensions_path("java/Requests.java");
129
130 let requests_data = fs::read_to_string(requests_path)?;
131
132 let requests = format!("package {package_name};\n\n{}", requests_data);
133
134 fs::write(path.join(package_path).join("Requests.java"), requests)?;
135
136 Ok(())
137}
138
139pub fn typescript(
149 registry: &Registry,
150 module_name: &str,
151 version: &str,
152 path: impl AsRef<Path>,
153) -> Result {
154 let path = path.as_ref();
155
156 remove_dir(path);
158
159 fs::create_dir_all(path)?;
160
161 info!("Generating TypeScript types at {}", path.display());
162
163 let installer = typescript::Installer::new(path.to_path_buf());
164 installer
165 .install_serde_runtime()
166 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
167 installer
168 .install_bincode_runtime()
169 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
170
171 let extensions_dir = extensions_path("typescript");
172 copy(extensions_dir.as_ref(), path)?;
173
174 let config = serde_generate::CodeGeneratorConfig::new(module_name.to_string())
175 .with_encodings(vec![Encoding::Bincode]);
176
177 let generator = serde_generate::typescript::CodeGenerator::new(&config);
178 let mut source = Vec::new();
179 generator.output(&mut source, registry)?;
180
181 let out = String::from_utf8_lossy(&source)
183 .replace(
184 "import { BcsSerializer, BcsDeserializer } from '../bcs/mod.ts';",
185 "",
186 )
187 .replace(".ts'", "'");
188
189 let types_dir = path.join("types");
190 fs::create_dir_all(&types_dir)?;
191
192 let mut package_json = File::create(path.join("package.json"))?;
194 write!(
195 package_json,
196 "{{\"name\": \"{module_name}\", \"version\": \"{version}\"}}"
197 )?;
198
199 std::process::Command::new("pnpm")
201 .current_dir(path)
202 .arg("add")
203 .arg("typescript")
204 .status()
205 .map_err(|e| match e.kind() {
206 std::io::ErrorKind::NotFound => TypeGenError::PnpmNotFound(e),
207 _ => TypeGenError::Io(e),
208 })?;
209
210 let mut output = File::create(types_dir.join(format!("{module_name}.ts")))?;
211 write!(output, "{out}")?;
212
213 std::process::Command::new("pnpm")
215 .current_dir(path)
216 .arg("install")
217 .status()
218 .map_err(|e| match e.kind() {
219 std::io::ErrorKind::NotFound => TypeGenError::PnpmNotFound(e),
220 _ => TypeGenError::Io(e),
221 })?;
222
223 std::process::Command::new("pnpm")
225 .current_dir(path)
226 .arg("exec")
227 .arg("tsc")
228 .arg("--build")
229 .status()
230 .map_err(TypeGenError::Io)?;
231
232 Ok(())
233}
234
235fn copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result {
236 fs::create_dir_all(&to)?;
237
238 let entries = fs::read_dir(from)?;
239 for entry in entries {
240 let entry = entry?;
241
242 let to = to.as_ref().to_path_buf().join(entry.file_name());
243 if entry.file_type()?.is_dir() {
244 copy(entry.path(), to)?;
245 } else {
246 fs::copy(entry.path(), to)?;
247 };
248 }
249
250 Ok(())
251}
252
253fn extensions_path(path: impl AsRef<Path>) -> impl AsRef<Path> {
254 let path = path.as_ref();
255 let custom = PathBuf::from("./typegen_extensions").join(path);
256 let default = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
257 .join("typegen_extensions")
258 .join(path);
259
260 match custom.try_exists() {
261 Ok(true) => custom,
262 Ok(false) => default,
263 Err(e) => {
264 println!("cant check typegen extensions override: {}", e);
265 default
266 }
267 }
268}
269
270fn remove_dir(path: impl AsRef<Path>) {
271 let path = path.as_ref();
272 if path.exists() {
273 debug!("Removing directory: {}", path.display());
274 fs::remove_dir_all(path).unwrap_or_default();
275 }
276}