crux_cli/codegen/
generate.rs1use std::path::{Path, PathBuf};
2
3use log::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 recreate_dir(&path)?;
38
39 info!("Generating Swift types at {}", path.display());
40
41 let installer = swift::Installer::new(path.clone());
42 installer
43 .install_serde_runtime()
44 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
45 installer
46 .install_bincode_runtime()
47 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
48
49 let config = serde_generate::CodeGeneratorConfig::new(module_name.to_string())
50 .with_encodings(vec![Encoding::Bincode]);
51
52 installer
53 .install_module(&config, registry)
54 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
55
56 let mut output = File::create(
58 path.join("Sources")
59 .join(module_name)
60 .join("Requests.swift"),
61 )?;
62
63 let requests_path = extensions_path("swift/requests.swift");
64
65 let requests_data = fs::read_to_string(requests_path)?;
66
67 write!(output, "{requests_data}")?;
68
69 let mut output = File::create(path.join("Package.swift"))?;
71
72 let package_path = extensions_path("swift/Package.swift");
73
74 let package_data = fs::read_to_string(package_path)?;
75
76 write!(
77 output,
78 "{}",
79 package_data.replace("SharedTypes", module_name)
80 )?;
81
82 Ok(())
83}
84
85pub fn java(registry: &Registry, package_name: &str, path: impl AsRef<Path>) -> Result {
99 let path = path.as_ref();
100 fs::create_dir_all(path)?;
101
102 let package_path = package_name.replace('.', "/");
103
104 recreate_dir(path.join(&package_path))?;
106 recreate_dir(path.join("com/novi/bincode"))?;
107 recreate_dir(path.join("com/novi/serde"))?;
108
109 info!("Generating Java types at {}", path.display());
110
111 let config = serde_generate::CodeGeneratorConfig::new(package_name.to_string())
112 .with_encodings(vec![Encoding::Bincode]);
113
114 let installer = java::Installer::new(path.to_path_buf());
115 installer
116 .install_serde_runtime()
117 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
118 installer
119 .install_bincode_runtime()
120 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
121
122 installer
123 .install_module(&config, registry)
124 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
125
126 let requests_path = extensions_path("java/Requests.java");
127
128 let requests_data = fs::read_to_string(requests_path)?;
129
130 let requests = format!("package {package_name};\n\n{requests_data}");
131
132 fs::write(path.join(package_path).join("Requests.java"), requests)?;
133
134 Ok(())
135}
136
137pub fn typescript(
147 registry: &Registry,
148 module_name: &str,
149 version: &str,
150 path: impl AsRef<Path>,
151) -> Result {
152 let path = path.as_ref();
153
154 recreate_dir(path)?;
156
157 info!("Generating TypeScript types at {}", path.display());
158
159 let installer = typescript::Installer::new(path.to_path_buf());
160 installer
161 .install_serde_runtime()
162 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
163 installer
164 .install_bincode_runtime()
165 .map_err(|e| TypeGenError::Generation(e.to_string()))?;
166
167 let extensions_dir = extensions_path("typescript");
168 copy(extensions_dir.as_ref(), path)?;
169
170 let config = serde_generate::CodeGeneratorConfig::new(module_name.to_string())
171 .with_encodings(vec![Encoding::Bincode]);
172
173 let generator = serde_generate::typescript::CodeGenerator::new(&config);
174 let mut source = Vec::new();
175 generator.output(&mut source, registry)?;
176
177 let out = String::from_utf8_lossy(&source)
179 .replace(
180 "import { BcsSerializer, BcsDeserializer } from '../bcs/mod.ts';",
181 "",
182 )
183 .replace(".ts'", "'");
184
185 let types_dir = path.join("types");
186 fs::create_dir_all(&types_dir)?;
187
188 let mut package_json = File::create(path.join("package.json"))?;
190 write!(
191 package_json,
192 "{{\"name\": \"{module_name}\", \"version\": \"{version}\"}}"
193 )?;
194
195 std::process::Command::new("pnpm")
197 .current_dir(path)
198 .arg("add")
199 .arg("--save-dev")
200 .arg("typescript")
201 .status()
202 .map_err(|e| match e.kind() {
203 std::io::ErrorKind::NotFound => TypeGenError::PnpmNotFound(e),
204 _ => TypeGenError::Io(e),
205 })?;
206
207 let mut output = File::create(types_dir.join(format!("{module_name}.ts")))?;
208 write!(output, "{out}")?;
209
210 std::process::Command::new("pnpm")
212 .current_dir(path)
213 .arg("install")
214 .status()
215 .map_err(|e| match e.kind() {
216 std::io::ErrorKind::NotFound => TypeGenError::PnpmNotFound(e),
217 _ => TypeGenError::Io(e),
218 })?;
219
220 std::process::Command::new("pnpm")
222 .current_dir(path)
223 .arg("exec")
224 .arg("tsc")
225 .arg("--build")
226 .status()
227 .map_err(TypeGenError::Io)?;
228
229 Ok(())
230}
231
232fn copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result {
233 fs::create_dir_all(&to)?;
234
235 let entries = fs::read_dir(from)?;
236 for entry in entries {
237 let entry = entry?;
238
239 let to = to.as_ref().to_path_buf().join(entry.file_name());
240 if entry.file_type()?.is_dir() {
241 copy(entry.path(), to)?;
242 } else {
243 fs::copy(entry.path(), to)?;
244 }
245 }
246
247 Ok(())
248}
249
250fn extensions_path(path: impl AsRef<Path>) -> impl AsRef<Path> {
251 let path = path.as_ref();
252 let custom = PathBuf::from("./typegen_extensions").join(path);
253 let default = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
254 .join("typegen_extensions")
255 .join(path);
256
257 match custom.try_exists() {
258 Ok(true) => custom,
259 Ok(false) => default,
260 Err(e) => {
261 println!("cant check typegen extensions override: {e}");
262 default
263 }
264 }
265}
266
267fn recreate_dir<P>(dir: P) -> std::io::Result<()>
268where
269 P: AsRef<Path>,
270{
271 match fs::remove_dir_all(&dir) {
272 Err(e) if e.kind() != std::io::ErrorKind::NotFound => Err(e),
273 _ => fs::create_dir_all(&dir),
274 }
275}