dip_bundle/tool/
vm.rs

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    /// Create shim file: e.g. $DATA_DIR/dip/bundle/shims/node
66    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    /// Iterate over version set defined in user config. Install only if bin doesn't exist.
82    fn install_all(&self) -> anyhow::Result<()> {
83        for v in self.versions().iter() {
84            let p = self.version_dir(v);
85
86            // Skip install if the version already exists
87            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        // Create shim with default version
101        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    /// Iterate over each versions currently installed but removed from the user bundle config
113    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}