1#[cfg(feature = "nodejs")]
2mod nodejs;
3#[cfg(feature = "tailwindcss")]
4mod tailwindcss;
5
6#[cfg(feature = "nodejs")]
7use nodejs::NodeJSPlugin;
8
9#[cfg(feature = "tailwindcss")]
10use tailwindcss::TailwindCSSPlugin;
11
12use crate::{Bundler, Installer};
13use anyhow::Context;
14use bevy::app::{App, Plugin};
15use std::{fs, path::PathBuf};
16
17pub struct VersionManagerPlugin;
18
19impl Plugin for VersionManagerPlugin {
20 fn build(&self, app: &mut App) {
21 #[cfg(feature = "tailwindcss")]
22 app.add_plugin(TailwindCSSPlugin);
23
24 #[cfg(feature = "nodejs")]
25 app.add_plugin(NodeJSPlugin);
26 }
27}
28
29pub trait VersionManager: Bundler + Installer {
30 fn file_name(&self, version: &String) -> String;
31
32 fn file_name_without_ext(&self, version: &String) -> String;
33
34 fn download_file_name(&self, version: &String) -> String;
35
36 fn download_url(&self, version: &String) -> String;
37
38 fn installs_dir(&self) -> PathBuf {
39 self.bundle_config().install_root().join(Self::key())
40 }
41
42 fn shims_dir(&self) -> PathBuf {
43 self.bundle_config().shim_root()
44 }
45
46 fn versions(&self) -> &Vec<String>;
47
48 fn version_dir(&self, version: &String) -> PathBuf {
49 self.installs_dir().join(version)
50 }
51
52 fn install_path(&self, version: &String) -> PathBuf {
53 self.installs_dir().join(version)
54 }
55
56 fn list_shims() -> Vec<&'static str>;
57
58 fn shim_paths(&self) -> Vec<PathBuf> {
59 Self::list_shims()
60 .iter()
61 .map(|bin| self.shims_dir().join(bin))
62 .collect()
63 }
64
65 fn shim(&self, version: &String) -> anyhow::Result<()>;
67
68 fn remove_shim(&self) -> anyhow::Result<()>;
69
70 fn format_shim(path: &PathBuf) -> anyhow::Result<String> {
71 let sh = format!(
72 "#!/bin/sh\n\
73 \"{}\" \"$@\"\
74 ",
75 &path.canonicalize()?.display()
76 );
77
78 Ok(sh)
79 }
80
81 fn install_all(&self) -> anyhow::Result<()> {
83 for v in self.versions().iter() {
84 let p = self.version_dir(v);
85
86 if p.is_dir() {
88 continue;
89 }
90
91 self.install(
92 &self.download_url(v),
93 &self.install_path(v),
94 &self.download_file_name(v),
95 self.checksum(v)?.to_owned().as_ref(),
96 )?;
97 println!("Installed: {}", &p.display());
98 }
99
100 self.shim(
102 self.versions()
103 .first()
104 .context("Cannot find find any runtime versions")?,
105 )?;
106
107 Ok(())
108 }
109
110 fn checksum(&self, version: &String) -> anyhow::Result<Option<String>>;
111
112 fn clean_all(&self) -> anyhow::Result<()> {
114 if self.installs_dir().is_dir() {
115 let installs = fs::read_dir(self.installs_dir())?;
116
117 installs
118 .filter_map(Result::ok)
119 .filter(|dir| dir.path().is_dir())
120 .filter(|dir| {
121 let v = &dir
122 .path()
123 .file_name()
124 .unwrap()
125 .to_os_string()
126 .into_string()
127 .unwrap();
128 !self.versions().contains(v)
129 })
130 .for_each(|dir| {
131 let path = dir.path();
132 if let Err(e) = fs::remove_dir_all(&path) {
133 eprintln!("Failed to cleanup directory: {e}");
134 } else {
135 println!("Cleaned: {}", path.display());
136 }
137 });
138
139 if fs::read_dir(self.installs_dir())?.next().is_none() {
140 fs::remove_dir(self.installs_dir())
141 .context("Failed to clean empty installs directory")?;
142 }
143 }
144
145 self.remove_shim()?;
146
147 Ok(())
148 }
149}