Skip to main content

vs_core/
lib.rs

1//! Application orchestration for the `vs` CLI.
2
3mod app;
4mod error;
5mod models;
6mod plugin_source;
7mod registry_source;
8mod service;
9
10pub use app::App;
11pub use error::CoreError;
12pub use models::{
13    CurrentTool, InstalledVersion, MigrateSummary, PluginInfo, SelfUpgradeSummary, UninstallResult,
14    UseScope, VersionInfo,
15};
16
17#[cfg(test)]
18mod tests {
19    #[cfg(feature = "lua")]
20    use std::error::Error;
21    #[cfg(feature = "lua")]
22    use std::fs;
23
24    #[cfg(feature = "lua")]
25    use tempfile::TempDir;
26    #[cfg(feature = "lua")]
27    use vs_config::{AppConfig, HomeLayout, RegistryConfig, write_app_config};
28    #[cfg(feature = "lua")]
29    use vs_plugin_api::PluginBackendKind;
30
31    #[cfg(feature = "lua")]
32    use crate::{App, UseScope};
33
34    #[cfg(feature = "lua")]
35    #[test]
36    fn use_tool_should_write_project_config() -> Result<(), Box<dyn Error>> {
37        let temp_dir = TempDir::new()?;
38        let home = temp_dir.path().join("home");
39        let cwd = temp_dir.path().join("project");
40        fs::create_dir_all(&cwd)?;
41        let app = App::new(
42            HomeLayout {
43                active_home: home,
44                migration_candidates: Vec::new(),
45            },
46            cwd.clone(),
47            Some(String::from("session")),
48        )?;
49
50        let source = temp_dir.path().join("nodejs-lua");
51        write_lua_fixture(&source);
52        app.add_plugin(
53            Some("nodejs"),
54            Some(source.display().to_string()),
55            Some(PluginBackendKind::Lua),
56            None,
57        )?;
58        app.install_plugin_version("nodejs", Some("20.11.1"))?;
59
60        app.use_tool("nodejs", "20.11.1", UseScope::Project, false)?;
61
62        let config = fs::read_to_string(cwd.join(".vs.toml"))?;
63        assert!(config.contains("nodejs = \"20.11.1\""));
64        Ok(())
65    }
66
67    #[cfg(feature = "lua")]
68    #[test]
69    fn available_plugins_should_bootstrap_registry_when_source_is_configured()
70    -> Result<(), Box<dyn Error>> {
71        let temp_dir = TempDir::new()?;
72        let home = temp_dir.path().join("home");
73        let cwd = temp_dir.path().join("project");
74        fs::create_dir_all(&cwd)?;
75        let registry_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
76            .join("../../fixtures/registry/index.json");
77
78        write_app_config(
79            &home,
80            &AppConfig {
81                proxy: Default::default(),
82                storage: Default::default(),
83                registry: RegistryConfig {
84                    address: registry_path.display().to_string(),
85                },
86                legacy_version_file: Default::default(),
87                cache: Default::default(),
88            },
89        )?;
90
91        let app = App::new(
92            HomeLayout {
93                active_home: home,
94                migration_candidates: Vec::new(),
95            },
96            cwd,
97            Some(String::from("session")),
98        )?;
99
100        let entries = app.available_plugins()?;
101        assert!(!entries.is_empty());
102        assert!(entries.iter().any(|entry| entry.name == "nodejs"));
103        Ok(())
104    }
105
106    #[cfg(feature = "lua")]
107    #[test]
108    fn available_plugins_should_fallback_to_cached_registry_when_refresh_fails()
109    -> Result<(), Box<dyn Error>> {
110        let temp_dir = TempDir::new()?;
111        let home = temp_dir.path().join("home");
112        let cwd = temp_dir.path().join("project");
113        fs::create_dir_all(&cwd)?;
114        let registry_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
115            .join("../../fixtures/registry/index.json");
116
117        write_app_config(
118            &home,
119            &AppConfig {
120                proxy: Default::default(),
121                storage: Default::default(),
122                registry: RegistryConfig {
123                    address: registry_path.display().to_string(),
124                },
125                legacy_version_file: Default::default(),
126                cache: Default::default(),
127            },
128        )?;
129
130        let app = App::new(
131            HomeLayout {
132                active_home: home.clone(),
133                migration_candidates: Vec::new(),
134            },
135            cwd.clone(),
136            Some(String::from("session")),
137        )?;
138        assert!(!app.available_plugins()?.is_empty());
139
140        write_app_config(
141            &home,
142            &AppConfig {
143                proxy: Default::default(),
144                storage: Default::default(),
145                registry: RegistryConfig {
146                    address: temp_dir
147                        .path()
148                        .join("missing/index.json")
149                        .display()
150                        .to_string(),
151                },
152                legacy_version_file: Default::default(),
153                cache: Default::default(),
154            },
155        )?;
156
157        let fallback = App::new(
158            HomeLayout {
159                active_home: home,
160                migration_candidates: Vec::new(),
161            },
162            cwd,
163            Some(String::from("session")),
164        )?;
165        let entries = fallback.available_plugins()?;
166        assert!(entries.iter().any(|entry| entry.name == "nodejs"));
167        Ok(())
168    }
169
170    #[cfg(feature = "lua")]
171    #[test]
172    fn add_plugin_should_fallback_to_cached_registry_when_refresh_fails()
173    -> Result<(), Box<dyn Error>> {
174        let temp_dir = TempDir::new()?;
175        let home = temp_dir.path().join("home");
176        let cwd = temp_dir.path().join("project");
177        fs::create_dir_all(&cwd)?;
178        let registry_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
179            .join("../../fixtures/registry/index.json");
180
181        write_app_config(
182            &home,
183            &AppConfig {
184                proxy: Default::default(),
185                storage: Default::default(),
186                registry: RegistryConfig {
187                    address: registry_path.display().to_string(),
188                },
189                legacy_version_file: Default::default(),
190                cache: Default::default(),
191            },
192        )?;
193
194        let app = App::new(
195            HomeLayout {
196                active_home: home.clone(),
197                migration_candidates: Vec::new(),
198            },
199            cwd.clone(),
200            Some(String::from("session")),
201        )?;
202        assert!(!app.available_plugins()?.is_empty());
203
204        write_app_config(
205            &home,
206            &AppConfig {
207                proxy: Default::default(),
208                storage: Default::default(),
209                registry: RegistryConfig {
210                    address: temp_dir
211                        .path()
212                        .join("missing/index.json")
213                        .display()
214                        .to_string(),
215                },
216                legacy_version_file: Default::default(),
217                cache: Default::default(),
218            },
219        )?;
220
221        let fallback = App::new(
222            HomeLayout {
223                active_home: home,
224                migration_candidates: Vec::new(),
225            },
226            cwd,
227            Some(String::from("session")),
228        )?;
229        let entry = fallback.add_plugin(Some("nodejs"), None, None, None)?;
230        assert_eq!(entry.name, "nodejs");
231        Ok(())
232    }
233
234    #[cfg(feature = "lua")]
235    fn write_lua_fixture(root: &std::path::Path) {
236        if let Err(error) = fs::create_dir_all(root.join("hooks")) {
237            panic!("failed to create hooks directory: {error}");
238        }
239        if let Err(error) = fs::create_dir_all(root.join("packages/20.11.1/bin")) {
240            panic!("failed to create package directory: {error}");
241        }
242        fs::write(
243            root.join("metadata.lua"),
244            "PLUGIN = {}\nPLUGIN.name = 'nodejs'\nPLUGIN.version = '0.1.0'\nPLUGIN.legacyFilenames = { '.nvmrc' }\n",
245        )
246        .unwrap_or_else(|error| panic!("failed to write metadata fixture: {error}"));
247        fs::write(
248            root.join("hooks/available.lua"),
249            "function PLUGIN:Available(ctx)\n  return { { version = '20.11.1' } }\nend\n",
250        )
251        .unwrap_or_else(|error| panic!("failed to write available fixture: {error}"));
252        fs::write(
253            root.join("hooks/pre_install.lua"),
254            "function PLUGIN:PreInstall(ctx)\n  return { version = '20.11.1', url = 'packages/20.11.1' }\nend\n",
255        )
256        .unwrap_or_else(|error| panic!("failed to write pre_install fixture: {error}"));
257        fs::write(
258            root.join("hooks/env_keys.lua"),
259            "function PLUGIN:EnvKeys(ctx)\n  return { { key = 'NODEJS_HOME', value = ctx.path }, { key = 'PATH', value = ctx.path .. '/bin' } }\nend\n",
260        )
261        .unwrap_or_else(|error| panic!("failed to write env_keys fixture: {error}"));
262    }
263}