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