|
1 | | -//! Version command (Category B: JavaScript Command). |
| 1 | +//! Version command. |
2 | 2 |
|
3 | | -use std::process::ExitStatus; |
| 3 | +use std::{ |
| 4 | + collections::BTreeMap, |
| 5 | + fs, |
| 6 | + path::{Path, PathBuf}, |
| 7 | + process::ExitStatus, |
| 8 | +}; |
4 | 9 |
|
| 10 | +use owo_colors::OwoColorize; |
| 11 | +use serde::Deserialize; |
5 | 12 | use vite_path::AbsolutePathBuf; |
6 | 13 |
|
7 | | -use crate::{error::Error, js_executor::JsExecutor}; |
| 14 | +use crate::{error::Error, help}; |
8 | 15 |
|
9 | | -/// Execute the `--version` command by delegating to local or global vite-plus. |
10 | | -/// |
11 | | -/// Uses the CLI's own runtime instead of the project's runtime to avoid |
12 | | -/// side effects (e.g., writing `.node-version` in the caller's directory). |
13 | | -pub async fn execute(cwd: AbsolutePathBuf) -> Result<ExitStatus, Error> { |
14 | | - // Pass the Rust binary's version to JS so it can display the correct global version, |
15 | | - // even when delegation resolves to a local node_modules copy. |
16 | | - unsafe { |
17 | | - std::env::set_var( |
18 | | - vite_shared::env_vars::VITE_PLUS_GLOBAL_VERSION, |
19 | | - env!("CARGO_PKG_VERSION"), |
20 | | - ); |
| 16 | +#[derive(Debug, Deserialize)] |
| 17 | +#[serde(rename_all = "camelCase")] |
| 18 | +struct PackageJson { |
| 19 | + version: String, |
| 20 | + #[serde(default)] |
| 21 | + bundled_versions: BTreeMap<String, String>, |
| 22 | +} |
| 23 | + |
| 24 | +#[derive(Debug)] |
| 25 | +struct LocalVitePlus { |
| 26 | + version: String, |
| 27 | + package_dir: PathBuf, |
| 28 | +} |
| 29 | + |
| 30 | +#[derive(Debug, Clone, Copy)] |
| 31 | +struct ToolSpec { |
| 32 | + display_name: &'static str, |
| 33 | + package_name: &'static str, |
| 34 | + bundled_version_key: Option<&'static str>, |
| 35 | +} |
| 36 | + |
| 37 | +const TOOL_SPECS: [ToolSpec; 7] = [ |
| 38 | + ToolSpec { |
| 39 | + display_name: "vite", |
| 40 | + package_name: "@voidzero-dev/vite-plus-core", |
| 41 | + bundled_version_key: Some("vite"), |
| 42 | + }, |
| 43 | + ToolSpec { |
| 44 | + display_name: "rolldown", |
| 45 | + package_name: "@voidzero-dev/vite-plus-core", |
| 46 | + bundled_version_key: Some("rolldown"), |
| 47 | + }, |
| 48 | + ToolSpec { |
| 49 | + display_name: "vitest", |
| 50 | + package_name: "@voidzero-dev/vite-plus-test", |
| 51 | + bundled_version_key: Some("vitest"), |
| 52 | + }, |
| 53 | + ToolSpec { display_name: "oxfmt", package_name: "oxfmt", bundled_version_key: None }, |
| 54 | + ToolSpec { display_name: "oxlint", package_name: "oxlint", bundled_version_key: None }, |
| 55 | + ToolSpec { |
| 56 | + display_name: "oxlint-tsgolint", |
| 57 | + package_name: "oxlint-tsgolint", |
| 58 | + bundled_version_key: None, |
| 59 | + }, |
| 60 | + ToolSpec { |
| 61 | + display_name: "tsdown", |
| 62 | + package_name: "@voidzero-dev/vite-plus-core", |
| 63 | + bundled_version_key: Some("tsdown"), |
| 64 | + }, |
| 65 | +]; |
| 66 | + |
| 67 | +fn read_package_json(package_json_path: &Path) -> Option<PackageJson> { |
| 68 | + let content = fs::read_to_string(package_json_path).ok()?; |
| 69 | + serde_json::from_str(&content).ok() |
| 70 | +} |
| 71 | + |
| 72 | +fn find_local_vite_plus(start: &Path) -> Option<LocalVitePlus> { |
| 73 | + let mut current = Some(start); |
| 74 | + while let Some(dir) = current { |
| 75 | + let package_json_path = dir.join("node_modules").join("vite-plus").join("package.json"); |
| 76 | + if let Some(pkg) = read_package_json(&package_json_path) { |
| 77 | + let package_dir = package_json_path.parent()?.to_path_buf(); |
| 78 | + return Some(LocalVitePlus { version: pkg.version, package_dir }); |
| 79 | + } |
| 80 | + current = dir.parent(); |
| 81 | + } |
| 82 | + None |
| 83 | +} |
| 84 | + |
| 85 | +fn resolve_package_json(base_dir: &Path, package_name: &str) -> Option<PackageJson> { |
| 86 | + let mut current = Some(base_dir); |
| 87 | + while let Some(dir) = current { |
| 88 | + let package_json_path = dir.join("node_modules").join(package_name).join("package.json"); |
| 89 | + if let Some(pkg) = read_package_json(&package_json_path) { |
| 90 | + return Some(pkg); |
| 91 | + } |
| 92 | + current = dir.parent(); |
| 93 | + } |
| 94 | + None |
| 95 | +} |
| 96 | + |
| 97 | +fn resolve_tool_version(local: &LocalVitePlus, tool: ToolSpec) -> Option<String> { |
| 98 | + let pkg = resolve_package_json(&local.package_dir, tool.package_name)?; |
| 99 | + if let Some(key) = tool.bundled_version_key |
| 100 | + && let Some(version) = pkg.bundled_versions.get(key) |
| 101 | + { |
| 102 | + return Some(version.clone()); |
21 | 103 | } |
22 | | - let mut executor = JsExecutor::new(None); |
23 | | - executor.delegate_with_cli_runtime(&cwd, &["--version".to_string()]).await |
| 104 | + Some(pkg.version) |
| 105 | +} |
| 106 | + |
| 107 | +fn accent(text: &str) -> String { |
| 108 | + if help::should_style_help() { text.bright_blue().to_string() } else { text.to_string() } |
| 109 | +} |
| 110 | + |
| 111 | +fn print_rows(title: &str, rows: &[(&str, String)]) { |
| 112 | + println!("{}", help::render_heading(title)); |
| 113 | + let label_width = rows.iter().map(|(label, _)| label.chars().count()).max().unwrap_or(0); |
| 114 | + for (label, value) in rows { |
| 115 | + let padding = " ".repeat(label_width.saturating_sub(label.chars().count())); |
| 116 | + println!(" {}{} {value}", accent(label), padding); |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +fn format_version(version: Option<String>) -> String { |
| 121 | + match version { |
| 122 | + Some(v) => format!("v{v}"), |
| 123 | + None => "Not found".to_string(), |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +/// Execute the `--version` command. |
| 128 | +pub async fn execute(cwd: AbsolutePathBuf) -> Result<ExitStatus, Error> { |
| 129 | + let header = if help::should_style_help() { |
| 130 | + "VITE+ - The Unified Toolchain for the Web".bold().to_string() |
| 131 | + } else { |
| 132 | + "VITE+ - The Unified Toolchain for the Web".to_string() |
| 133 | + }; |
| 134 | + println!("{header}"); |
| 135 | + println!(); |
| 136 | + |
| 137 | + println!("vp v{}", env!("CARGO_PKG_VERSION")); |
| 138 | + println!(); |
| 139 | + |
| 140 | + let local = find_local_vite_plus(cwd.as_path()); |
| 141 | + print_rows( |
| 142 | + "Local vite-plus", |
| 143 | + &[("vite-plus", format_version(local.as_ref().map(|pkg| pkg.version.clone())))], |
| 144 | + ); |
| 145 | + println!(); |
| 146 | + |
| 147 | + let tool_rows = TOOL_SPECS |
| 148 | + .iter() |
| 149 | + .map(|tool| { |
| 150 | + let version = |
| 151 | + local.as_ref().and_then(|local_pkg| resolve_tool_version(local_pkg, *tool)); |
| 152 | + (tool.display_name, format_version(version)) |
| 153 | + }) |
| 154 | + .collect::<Vec<_>>(); |
| 155 | + print_rows("Tools", &tool_rows); |
| 156 | + |
| 157 | + Ok(ExitStatus::default()) |
24 | 158 | } |
25 | 159 |
|
26 | 160 | #[cfg(test)] |
27 | 161 | mod tests { |
| 162 | + use super::format_version; |
| 163 | + |
28 | 164 | #[test] |
29 | | - fn test_version_command_module_exists() { |
30 | | - // Basic test to ensure the module compiles |
31 | | - assert!(true); |
| 165 | + fn format_version_values() { |
| 166 | + assert_eq!(format_version(Some("1.2.3".to_string())), "v1.2.3"); |
| 167 | + assert_eq!(format_version(None), "Not found"); |
32 | 168 | } |
33 | 169 | } |
0 commit comments