static_files/mods/
npm_build.rs1use std::{
5 io::{self},
6 path::{Path, PathBuf},
7 process::{Command, Stdio},
8};
9
10use super::resource_dir::ResourceDir;
11
12#[cfg(not(windows))]
13const NPM_CMD: &str = "npm";
14
15#[cfg(windows)]
16const NPM_CMD: &str = "npm.cmd";
17
18pub fn npm_resource_dir<P: AsRef<Path>>(resource_dir: P) -> io::Result<ResourceDir> {
23 let mut npm_build = NpmBuild::new(resource_dir).install()?;
24
25 #[cfg(feature = "change-detection")]
26 {
27 npm_build = npm_build.change_detection();
28 }
29
30 Ok(npm_build.into())
31}
32
33#[derive(Default, Debug)]
56pub struct NpmBuild {
57 package_json_dir: PathBuf,
58 executable: String,
59 target_dir: Option<PathBuf>,
60 stderr: Option<Stdio>,
61 stdout: Option<Stdio>,
62}
63
64impl NpmBuild {
65 pub fn new<P: AsRef<Path>>(package_json_dir: P) -> Self {
66 Self {
67 package_json_dir: package_json_dir.as_ref().into(),
68 executable: String::from(NPM_CMD),
69 ..Default::default()
70 }
71 }
72
73 pub fn executable(self, executable: &str) -> Self {
75 let executable = String::from(executable);
76 Self { executable, ..self }
77 }
78
79 #[cfg(feature = "change-detection")]
89 pub fn change_detection(self) -> Self {
90 use ::change_detection::{
91 path_matchers::{any, equal, func, starts_with, PathMatcherExt},
92 ChangeDetection,
93 };
94
95 let package_json_dir = self.package_json_dir.clone();
96 let default_exclude_filter = any!(
97 equal(package_json_dir.clone()),
98 starts_with(self.package_json_dir.join("node_modules")),
99 equal(self.package_json_dir.join("package.json")),
100 equal(self.package_json_dir.join("package-lock.json")),
101 func(move |p| { p.is_file() && p.parent() != Some(package_json_dir.as_path()) })
102 );
103
104 {
105 let change_detection = if self.target_dir.is_none() {
106 ChangeDetection::exclude(default_exclude_filter)
107 } else {
108 let mut target_dir = self.target_dir.clone().unwrap();
109
110 if let Some(target_dir_parent) = target_dir.parent() {
111 if target_dir_parent.starts_with(&self.package_json_dir) {
112 while target_dir.parent() != Some(&self.package_json_dir) {
113 target_dir = target_dir.parent().unwrap().into();
114 }
115 }
116 }
117
118 let exclude_filter = default_exclude_filter.or(starts_with(target_dir));
119 ChangeDetection::exclude(exclude_filter)
120 };
121
122 change_detection.path(&self.package_json_dir).generate();
123 }
124 self
125 }
126
127 pub fn install(mut self) -> io::Result<Self> {
129 self.package_command()
130 .arg("install")
131 .status()
132 .map_err(|err| {
133 eprintln!("Cannot execute {} install: {:?}", &self.executable, err);
134 err
135 })
136 .map(|_| self)
137 }
138
139 pub fn run(mut self, cmd: &str) -> io::Result<Self> {
141 self.package_command()
142 .arg("run")
143 .arg(cmd)
144 .status()
145 .map_err(|err| {
146 eprintln!("Cannot execute {} run {}: {:?}", &self.executable, cmd, err);
147 err
148 })
149 .map(|_| self)
150 }
151
152 pub fn target<P: AsRef<Path>>(mut self, target_dir: P) -> Self {
154 self.target_dir = Some(target_dir.as_ref().into());
155 self
156 }
157
158 pub fn stderr<S: Into<Stdio>>(mut self, stdio: S) -> Self {
162 self.stderr = Some(stdio.into());
163 self
164 }
165
166 pub fn stdout<S: Into<Stdio>>(mut self, stdio: S) -> Self {
170 self.stdout = Some(stdio.into());
171 self
172 }
173
174 pub fn to_resource_dir(self) -> ResourceDir {
176 self.into()
177 }
178
179 #[cfg(not(windows))]
180 fn command(&self) -> Command {
181 Command::new(&self.executable)
182 }
183
184 #[cfg(windows)]
185 fn command(&self) -> Command {
186 let mut cmd = Command::new("cmd");
187
188 cmd.arg("/c").arg(&self.executable);
189
190 cmd
191 }
192
193 fn package_command(&mut self) -> Command {
194 let mut cmd = self.command();
195
196 cmd.stderr(self.stderr.take().unwrap_or_else(|| Stdio::inherit()))
197 .stdout(self.stdout.take().unwrap_or_else(|| Stdio::inherit()))
198 .current_dir(&self.package_json_dir);
199
200 cmd
201 }
202}
203
204impl From<NpmBuild> for ResourceDir {
205 fn from(mut npm_build: NpmBuild) -> Self {
206 Self {
207 resource_dir: npm_build
208 .target_dir
209 .take()
210 .unwrap_or_else(|| npm_build.package_json_dir.join("node_modules")),
211 ..Default::default()
212 }
213 }
214}