1#![doc = include_str!("../README.md")]
2
3use std::ffi::OsStr;
4use std::path::Path;
5use std::path::PathBuf;
6
7use nameth::NamedEnumValues as _;
8use nameth::NamedType as _;
9use nameth::nameth;
10
11pub struct BuildOptions<'t> {
13 pub client_dir: PathBuf,
19
20 pub server_dir: PathBuf,
26
27 pub wasm_pack_options: &'t [&'t str],
32}
33
34pub fn build(options: BuildOptions) -> Result<(), BuildError> {
38 let BuildOptions {
44 client_dir,
45 server_dir,
46 wasm_pack_options,
47 } = options;
48
49 let client_src_dir = client_dir
52 .join("src")
53 .to_str()
54 .ok_or(BuildErrorInner::InvalidClientSrcDir)?
55 .to_owned();
56 println!("cargo::rerun-if-changed={client_src_dir}");
57
58 let client_pkg_dir = client_dir.join("pkg");
60
61 rm(&client_pkg_dir, BuildErrorInner::RmClientPkgError)?;
63
64 let mut wasm_pack = std::process::Command::new("wasm-pack");
67 wasm_pack
68 .args(["build", "--target", "web"])
69 .args(wasm_pack_options)
70 .args(["--target-dir", "target/wasm"])
71 .current_dir(&client_dir);
72 for (key, value) in std::env::vars() {
73 if !key.starts_with("CARGO_") && key != "DEBUG" && key != "OPT_LEVEL" && key != "PROFILE" {
74 wasm_pack.env(key, value);
75 }
76 }
77 let () = wasm_pack
81 .status()
82 .map_err(|_| BuildErrorInner::WasmPackError)?
83 .success()
84 .then_some(())
85 .ok_or(BuildErrorInner::WasmPackError)?;
86
87 let assets_dir = server_dir.join("assets");
89 let assets_wasm_dir = assets_dir.join("wasm");
90
91 rm(&assets_wasm_dir, BuildErrorInner::RmServerAssetsWasmError)?;
93
94 mv(
95 &client_pkg_dir,
96 &assets_wasm_dir,
97 BuildErrorInner::MvWasmError,
98 )?;
99
100 let cargo_manifest_dir =
101 PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
102 let debug_or_release = if cfg!(debug_assertions) {
103 "debug"
104 } else {
105 "release"
106 };
107 let target_dir = cargo_manifest_dir.join("target").join(debug_or_release);
108 let target_asset_dir = target_dir.join("assets");
109 rm(&target_asset_dir, BuildErrorInner::RmTargetAssetsError)?;
110 mkdir(&target_dir, BuildErrorInner::MkdirTargetAssetsError)?;
111 cp(
112 &assets_dir,
113 &target_asset_dir,
114 BuildErrorInner::CpTargetAssetsError,
115 )?;
116
117 Ok(())
118}
119
120fn cp<E>(from: &Path, to: &Path, error: E) -> Result<(), E> {
121 let Ok(status) = std::process::Command::new("cp")
122 .args([OsStr::new("-R"), from.as_os_str(), to.as_os_str()])
123 .status()
124 else {
125 return Err(error);
126 };
127 status.success().then_some(()).ok_or(error)
128}
129
130fn mkdir<E>(path: &Path, error: E) -> Result<(), E> {
131 let Ok(status) = std::process::Command::new("mkdir")
132 .args([OsStr::new("-p"), path.as_os_str()])
133 .status()
134 else {
135 return Err(error);
136 };
137 status.success().then_some(()).ok_or(error)
138}
139
140fn mv<E>(from: &Path, to: &Path, error: E) -> Result<(), E> {
141 let Ok(status) = std::process::Command::new("mv")
142 .args([from.as_os_str(), to.as_os_str()])
143 .status()
144 else {
145 return Err(error);
146 };
147 status.success().then_some(()).ok_or(error)
148}
149
150fn rm<E>(path: &Path, error: E) -> Result<(), E> {
151 let Ok(status) = std::process::Command::new("rm")
152 .args([OsStr::new("-rf"), path.as_os_str()])
153 .status()
154 else {
155 return Err(error);
156 };
157 status.success().then_some(()).ok_or(error)
158}
159
160#[nameth]
162#[derive(thiserror::Error, Debug)]
163#[error("[{t}] {0}", t = Self::type_name())]
164pub struct BuildError(#[from] BuildErrorInner);
165
166#[nameth]
167#[derive(thiserror::Error, Debug)]
168enum BuildErrorInner {
169 #[error("[{n}] Client src dir is invalid UTF-8", n = self.name())]
170 InvalidClientSrcDir,
171
172 #[error("[{n}] Failed to eraze old client pkg folder", n = self.name())]
173 RmClientPkgError,
174
175 #[error("[{n}] Failed build the WASM", n = self.name())]
176 WasmPackError,
177
178 #[error("[{n}] Failed to eraze server assets wasm folder", n = self.name())]
179 RmServerAssetsWasmError,
180
181 #[error("[{n}] Failed to move the wasm to the server assets folder", n = self.name())]
182 MvWasmError,
183
184 #[error("[{n}] Failed to erase the target assets folder", n = self.name())]
185 RmTargetAssetsError,
186
187 #[error("[{n}] Failed to make the target assets folder", n = self.name())]
188 MkdirTargetAssetsError,
189
190 #[error("[{n}] Failed to copy to the target assets folder", n = self.name())]
191 CpTargetAssetsError,
192}
193
194pub fn build_css() {
196 let dir: PathBuf = std::env::var("CARGO_MANIFEST_DIR").unwrap().into();
197 let status = std::process::Command::new("stylance")
198 .current_dir(&dir)
199 .arg(".")
200 .status();
201 assert!(status.unwrap().success());
202}