modrpc_codegen/codegen/wasm/
project.rs1use std::fs;
2use std::io::Write;
3use std::path::Path;
4
5use mproto_codegen::codegen::name_util::{camel_to_kebab_case, camel_to_snake_case};
6
7use crate::{Database, ast::Schema, codegen};
8
9const RUST_GLUE_CARGO_TOML: &'static str = include_str!("templates/rust-wasm-glue/cargo.toml");
10const RUST_GLUE_BUILD_SH: &'static str = include_str!("templates/rust-wasm-glue/build.sh");
11
12const TS_GLUE_PACKAGE_JSON: &'static str = include_str!("templates/ts-wasm-glue/package.json");
13const TS_GLUE_TSCONFIG_JSON: &'static str = include_str!("templates/ts-wasm-glue/tsconfig.json");
14const TS_GLUE_BUILD_SH: &'static str = include_str!("templates/ts-wasm-glue/build.sh");
15
16pub fn wasm_project_gen(
17 root_dir: impl AsRef<Path>,
18 project_name: &str,
19 schema: &Schema,
20) -> std::io::Result<()> {
21 let pkg_name = &format!("{}-modrpc", project_name);
22 let wasm_root = root_dir.as_ref().join(pkg_name).join("wasm");
23
24 let local_mproto_module = mproto_codegen::Module::from_type_defs(schema.type_defs.clone());
26 let mproto_db = mproto_codegen::Database::new(local_mproto_module);
27
28 let mut db = Database::new(mproto_db);
29 crate::codegen::load_imports_recursive(&mut db, schema).unwrap();
30 for interface in &schema.interfaces {
31 let _ = db.local().add_interface(interface.clone());
32
33 codegen::define_interface_config_and_state_structs(&mut db, interface);
35 }
36
37 for interface in &schema.interfaces {
38 let snake_interface_name = &camel_to_snake_case(&interface.name);
39 let kebab_interface_name = &camel_to_kebab_case(&interface.name);
40
41 for role_name in &interface.roles {
42 let snake_role_name = &camel_to_snake_case(role_name);
43 let kebab_role_name = &camel_to_kebab_case(role_name);
44 let kebab_interface_role_name =
45 &camel_to_kebab_case(&format!("{}{}", interface.name, role_name));
46
47 let role_root = wasm_root.join(kebab_interface_role_name);
48
49 let wit_dir = role_root.join("wit");
51 fs::create_dir_all(&wit_dir)?;
52 fs::write(
53 wit_dir.join(format!("{}.wit", kebab_interface_role_name)),
54 codegen::wasm::wit_interface(&db, interface, role_name).as_bytes(),
55 )?;
56
57 let role_rust_root = role_root.join("rust");
60 fs::create_dir_all(&role_rust_root)?;
61
62 let cargo_toml_path = role_rust_root.join("Cargo.toml");
64 if !cargo_toml_path.exists() {
65 fs::write(
66 cargo_toml_path,
67 RUST_GLUE_CARGO_TOML
68 .replace("PROJECT_NAME", project_name)
69 .replace("INTERFACE_NAME", kebab_interface_name)
70 .replace("ROLE_NAME", kebab_role_name)
71 .as_bytes(),
72 )?;
73 } else {
74 println!(
75 "{} already exists - skipping as it might contain hand-written code.",
76 cargo_toml_path.display(),
77 );
78 }
79
80 fs::write(
82 role_rust_root.join("build.sh"),
83 RUST_GLUE_BUILD_SH
84 .replace("INTERFACE_NAME", snake_interface_name)
85 .replace("ROLE_NAME", snake_role_name)
86 .as_bytes(),
87 )?;
88
89 let src_dir = role_rust_root.join("src");
91 fs::create_dir_all(&src_dir)?;
92 write_rust_file(
93 &src_dir.join("lib.rs"),
94 &indoc::indoc! {"
95 #![feature(type_alias_impl_trait)]
96 #![feature(thread_local)]
97 "},
98 &codegen::wasm::wit_impl_glue_rust(
99 &format!("{}_modrpc", project_name.replace("-", "_")),
100 &db,
101 interface,
102 role_name,
103 ),
104 )?;
105
106 let role_typescript_root = role_root.join("typescript");
109 fs::create_dir_all(&role_typescript_root)?;
110
111 let package_json_path = role_typescript_root.join("package.json");
113 if !package_json_path.exists() {
114 fs::write(
115 package_json_path,
116 TS_GLUE_PACKAGE_JSON
117 .replace("PROJECT_NAME", project_name)
118 .replace(
119 "PKG_NAME",
120 &format!("{}-{}", kebab_interface_name, kebab_role_name),
121 )
122 .as_bytes(),
123 )?;
124 } else {
125 println!(
126 "{} already exists - skipping as it might contain hand-written code.",
127 package_json_path.display(),
128 );
129 }
130
131 fs::write(
133 role_typescript_root.join("tsconfig.json"),
134 TS_GLUE_TSCONFIG_JSON.as_bytes(),
135 )?;
136
137 fs::write(
139 role_typescript_root.join("build.sh"),
140 TS_GLUE_BUILD_SH
141 .replace("INTERFACE_NAME", snake_interface_name)
142 .replace("ROLE_NAME", snake_role_name)
143 .as_bytes(),
144 )?;
145
146 let src_dir = role_typescript_root.join("src");
148 fs::create_dir_all(&src_dir)?;
149 crate::codegen::write_js_file(
150 &src_dir.join("index.ts"),
151 "",
152 &codegen::wasm::js_wit_glue(
153 &format!("{}-modrpc", project_name),
154 &db,
155 interface,
156 role_name,
157 ),
158 )?;
159 }
160 }
161
162 Ok(())
163}
164
165fn write_rust_file(
166 path: impl AsRef<Path> + std::fmt::Debug,
167 header: &str,
168 tokens: &genco::lang::rust::Tokens,
169) -> std::io::Result<()> {
170 let fmt = genco::fmt::Config::from_lang::<genco::lang::Rust>()
171 .with_indentation(genco::fmt::Indentation::Space(4));
172 let config = genco::lang::rust::Config::default();
173 let mut file = fs::File::create(&path)?;
174
175 file.write_all(header.as_bytes())?;
176
177 let mut w = genco::fmt::IoWriter::new(file);
178 tokens
179 .format_file(&mut w.as_formatter(&fmt), &config)
180 .expect(&format!("format {:?} file", path));
181
182 Ok(())
183}