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