1use crate::errors::*;
2use crate::export;
3use crate::install::Tools;
4use crate::parse::Opts;
5use crate::parse::{DeployOpts, ExportOpts, ServeOpts};
6use crate::serve;
7use fs_extra::copy_items;
8use fs_extra::dir::{copy as copy_dir, CopyOptions};
9use indicatif::MultiProgress;
10use minify_js::{minify, TopLevelMode};
11use std::fs;
12use std::path::Path;
13use std::path::PathBuf;
14
15pub fn deploy(
24 dir: PathBuf,
25 opts: &DeployOpts,
26 tools: &Tools,
27 global_opts: &Opts,
28) -> Result<i32, Error> {
29 let exit_code = if opts.export_static {
31 deploy_export(dir, opts.output.to_string(), opts, tools, global_opts)?
32 } else {
33 deploy_full(dir, opts.output.to_string(), opts, tools, global_opts)?
34 };
35
36 Ok(exit_code)
37}
38
39fn deploy_full(
43 dir: PathBuf,
44 output: String,
45 opts: &DeployOpts,
46 tools: &Tools,
47 global_opts: &Opts,
48) -> Result<i32, Error> {
49 let (serve_exit_code, server_path) = serve(
51 dir.clone(),
52 &ServeOpts {
53 no_run: true,
54 no_build: false,
55 release: true,
56 standalone: true,
57 watch: false,
58 custom_watch: Vec::new(),
59 host: "127.0.0.1".to_string(),
62 port: 8080,
63 },
64 tools,
65 global_opts,
66 &MultiProgress::new(),
67 true,
69 )?;
70 if serve_exit_code != 0 {
71 return Ok(serve_exit_code);
72 }
73 if let Some(server_path) = server_path {
74 let output_path = PathBuf::from(&output);
76 if output_path.exists() {
77 if let Err(err) = fs::remove_dir_all(&output_path) {
78 return Err(DeployError::ReplaceOutputDirFailed {
79 path: output,
80 source: err,
81 }
82 .into());
83 }
84 }
85 if let Err(err) = fs::create_dir(&output_path) {
86 return Err(DeployError::ReplaceOutputDirFailed {
87 path: output,
88 source: err,
89 }
90 .into());
91 }
92 #[cfg(target_os = "windows")]
94 let to = output_path.join("server.exe");
95 #[cfg(not(target_os = "windows"))]
96 let to = output_path.join("server");
97
98 if let Err(err) = fs::copy(&server_path, &to) {
99 return Err(DeployError::MoveAssetFailed {
100 to: to.to_str().map(|s| s.to_string()).unwrap(),
101 from: server_path,
102 source: err,
103 }
104 .into());
105 }
106 let from = dir.join("static");
108 if from.exists() {
109 if let Err(err) = copy_dir(&from, &output, &CopyOptions::new()) {
110 return Err(DeployError::MoveDirFailed {
111 to: output,
112 from: from.to_str().map(|s| s.to_string()).unwrap(),
113 source: err,
114 }
115 .into());
116 }
117 }
118 let from = dir.join("translations");
120 if from.exists() {
121 if let Err(err) = copy_dir(&from, &output, &CopyOptions::new()) {
122 return Err(DeployError::MoveDirFailed {
123 to: output,
124 from: from.to_str().map(|s| s.to_string()).unwrap(),
125 source: err,
126 }
127 .into());
128 }
129 }
130 if let Err(err) = fs::create_dir(output_path.join("dist")) {
132 return Err(DeployError::CreateDistDirFailed { source: err }.into());
133 }
134 let from = dir.join("dist/static");
137 if let Err(err) = copy_dir(&from, output_path.join("dist"), &CopyOptions::new()) {
138 return Err(DeployError::MoveDirFailed {
139 to: output,
140 from: from.to_str().map(|s| s.to_string()).unwrap(),
141 source: err,
142 }
143 .into());
144 }
145 let from = dir.join("dist/pkg"); if let Err(err) = copy_dir(&from, output_path.join("dist"), &CopyOptions::new()) {
147 return Err(DeployError::MoveDirFailed {
148 to: output,
149 from: from.to_str().map(|s| s.to_string()).unwrap(),
150 source: err,
151 }
152 .into());
153 }
154 let from = dir.join("dist/mutable");
155 if let Err(err) = copy_dir(&from, output_path.join("dist"), &CopyOptions::new()) {
156 return Err(DeployError::MoveDirFailed {
157 to: output,
158 from: from.to_str().map(|s| s.to_string()).unwrap(),
159 source: err,
160 }
161 .into());
162 }
163 let from = dir.join("dist/render_conf.json");
164 if let Err(err) = fs::copy(&from, output_path.join("dist/render_conf.json")) {
165 return Err(DeployError::MoveAssetFailed {
166 to: output,
167 from: from.to_str().map(|s| s.to_string()).unwrap(),
168 source: err,
169 }
170 .into());
171 }
172
173 if !opts.no_minify_js {
174 minify_js(
175 &dir.join("dist/pkg/perseus_engine.js"),
176 &output_path.join("dist/pkg/perseus_engine.js"),
177 )?
178 }
179
180 println!();
181 println!("Deployment complete 🚀! Your app is now available for serving in the standalone folder '{}'! You can run it by executing the `server` binary in that folder.", &output_path.to_str().map(|s| s.to_string()).unwrap());
182
183 Ok(0)
184 } else {
185 Err(ExecutionError::GetServerExecutableFailedSimple.into())
187 }
188}
189
190fn deploy_export(
193 dir: PathBuf,
194 output: String,
195 opts: &DeployOpts,
196 tools: &Tools,
197 global_opts: &Opts,
198) -> Result<i32, Error> {
199 let export_exit_code = export(
201 dir.clone(),
202 &ExportOpts {
203 release: true,
204 serve: false,
205 host: String::new(),
206 port: 0,
207 watch: false,
208 custom_watch: Vec::new(),
209 },
210 tools,
211 global_opts,
212 )?;
213 if export_exit_code != 0 {
214 return Ok(export_exit_code);
215 }
216 let from = dir.join("dist/exported");
219 let output_path = PathBuf::from(&output);
220 if output_path.exists() {
222 if let Err(err) = fs::remove_dir_all(&output_path) {
223 return Err(DeployError::ReplaceOutputDirFailed {
224 path: output,
225 source: err,
226 }
227 .into());
228 }
229 }
230 if let Err(err) = fs::create_dir(&output_path) {
231 return Err(DeployError::ReplaceOutputDirFailed {
232 path: output,
233 source: err,
234 }
235 .into());
236 }
237 let items = fs::read_dir(&from);
240 let items: Vec<PathBuf> = match items {
241 Ok(items) => {
242 let mut ok_items = Vec::new();
243 for item in items {
244 match item {
245 Ok(item) => ok_items.push(item.path()),
246 Err(err) => {
247 return Err(DeployError::ReadExportDirFailed {
248 path: from.to_str().map(|s| s.to_string()).unwrap(),
249 source: err,
250 }
251 .into())
252 }
253 }
254 }
255
256 ok_items
257 }
258 Err(err) => {
259 return Err(DeployError::ReadExportDirFailed {
260 path: from.to_str().map(|s| s.to_string()).unwrap(),
261 source: err,
262 }
263 .into())
264 }
265 };
266 if let Err(err) = copy_items(&items, &output, &CopyOptions::new()) {
268 return Err(DeployError::MoveExportDirFailed {
269 to: output,
270 from: from.to_str().map(|s| s.to_string()).unwrap(),
271 source: err,
272 }
273 .into());
274 }
275
276 if !opts.no_minify_js {
277 minify_js(
278 &dir.join("dist/exported/.perseus/bundle.js"),
279 &output_path.join(".perseus/bundle.js"),
280 )?
281 }
282
283 println!();
284 println!("Deployment complete 🚀! Your app is now available for serving in the standalone folder '{}'! You can run it by serving the contents of that folder however you'd like.", &output_path.to_str().map(|s| s.to_string()).unwrap());
285
286 Ok(0)
287}
288
289fn minify_js(from: &Path, to: &Path) -> Result<(), DeployError> {
291 let js_bundle = fs::read_to_string(from)
292 .map_err(|err| DeployError::ReadUnminifiedJsFailed { source: err })?;
293
294 let js_bundle = js_bundle.replace("export { initSync }", "// export { initSync }");
298
299 let mut minified = Vec::new();
300 minify(
301 TopLevelMode::Global,
302 js_bundle.as_bytes().to_vec(),
303 &mut minified,
305 )
306 .map_err(|err| DeployError::MinifyError { source: err })?;
307 let minified =
308 String::from_utf8(minified).map_err(|err| DeployError::MinifyNotUtf8 { source: err })?;
309 fs::write(to, minified).map_err(|err| DeployError::WriteMinifiedJsFailed { source: err })?;
310
311 Ok(())
312}