Skip to content

Commit 7946969

Browse files
committed
implement
1 parent 5b3a50f commit 7946969

4 files changed

Lines changed: 77 additions & 19 deletions

File tree

crates/vite_global_cli/src/cli.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,8 @@ async fn managed_update(
671671
continue;
672672
}
673673

674-
let (package_name, _) = global::parse_package_spec(package);
674+
// It is not a local package, so `parse_package_spec` there won't return `Err()`
675+
let (package_name, _) = global::parse_package_spec(package).unwrap();
675676
if PackageMetadata::load(&package_name).await?.is_some() {
676677
managed_specs.push(package.clone());
677678
} else {

crates/vite_global_cli/src/commands/global/install.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ pub async fn install(
105105
let mut packages = IndexMap::<String, Package>::new();
106106
for package_spec in package_specs {
107107
// Parse package spec (e.g., "typescript", "typescript@5.0.0", "@scope/pkg")
108-
let (package_name, _version_spec) = parse_package_spec(package_spec);
108+
109+
let (package_name, _version_spec) = match parse_package_spec(package_spec) {
110+
Ok(result) => result,
111+
Err(error) => return Err((Some(package_spec.clone()), error)),
112+
};
109113
packages.insert(package_name, Package { spec: package_spec, staging_dir: None });
110114
}
111115
let packages_count = packages.len();
@@ -395,7 +399,7 @@ async fn install_one(
395399
/// 1. Try to use PackageMetadata for binary list
396400
/// 2. Fallback to scanning BinConfig files for orphaned binaries
397401
pub async fn uninstall(package_name: &str, dry_run: bool) -> Result<(), Error> {
398-
let (package_name, _) = parse_package_spec(package_name);
402+
let (package_name, _) = parse_package_spec(package_name)?;
399403

400404
// Phase 1: Try to use PackageMetadata for binary list
401405
let bins = if let Some(metadata) = PackageMetadata::load(&package_name).await? {
@@ -881,28 +885,35 @@ mod tests {
881885

882886
#[test]
883887
fn test_parse_package_spec_simple() {
884-
let (name, version) = parse_package_spec("typescript");
888+
let (name, version) = parse_package_spec("typescript").unwrap();
885889
assert_eq!(name, "typescript");
886890
assert_eq!(version, None);
887891
}
888892

889893
#[test]
890894
fn test_parse_package_spec_with_version() {
891-
let (name, version) = parse_package_spec("typescript@5.0.0");
895+
let (name, version) = parse_package_spec("typescript@5.0.0").unwrap();
892896
assert_eq!(name, "typescript");
893897
assert_eq!(version, Some("5.0.0".to_string()));
894898
}
895899

896900
#[test]
897901
fn test_parse_package_spec_scoped() {
898-
let (name, version) = parse_package_spec("@types/node");
902+
let (name, version) = parse_package_spec("@types/node").unwrap();
899903
assert_eq!(name, "@types/node");
900904
assert_eq!(version, None);
901905
}
902906

903907
#[test]
904908
fn test_parse_package_spec_scoped_with_version() {
905-
let (name, version) = parse_package_spec("@types/node@20.0.0");
909+
let (name, version) = parse_package_spec("@types/node@20.0.0").unwrap();
910+
assert_eq!(name, "@types/node");
911+
assert_eq!(version, Some("20.0.0".to_string()));
912+
}
913+
914+
#[test]
915+
fn test_parse_package_spec_local() {
916+
let (name, version) = parse_package_spec("./packages/cli").unwrap();
906917
assert_eq!(name, "@types/node");
907918
assert_eq!(version, Some("20.0.0".to_string()));
908919
}

crates/vite_global_cli/src/commands/global/mod.rs

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,20 +133,63 @@ pub(crate) fn is_local_package_spec(spec: &str) -> bool {
133133
}
134134

135135
/// Parse package spec into name and optional version.
136-
pub(crate) fn parse_package_spec(spec: &str) -> (String, Option<String>) {
137-
if spec.starts_with('@') {
138-
if let Some(idx) = spec[1..].find('@') {
139-
let idx = idx + 1;
140-
return (spec[..idx].to_string(), Some(spec[idx + 1..].to_string()));
136+
/// For local packages, it will read the package.json and return the real package name
137+
///
138+
/// It will never return an `Err()` if it is not a local package
139+
pub(crate) fn parse_package_spec(spec: &str) -> Result<(String, Option<String>), Error> {
140+
if is_local_package_spec(spec) {
141+
let path_spec = spec.strip_prefix("file:").unwrap_or(spec);
142+
let path = std::path::Path::new(path_spec);
143+
let package_dir = if path.is_absolute() {
144+
AbsolutePathBuf::new(path.to_path_buf()).ok_or_else(|| {
145+
Error::ConfigError(format!("Invalid local package path {spec}").into())
146+
})?
147+
} else {
148+
current_dir()
149+
.map_err(|error| {
150+
Error::ConfigError(format!("Cannot get current directory: {error}").into())
151+
})?
152+
.join(path)
153+
};
154+
let package_json_path = package_dir.join("package.json");
155+
let package_json_content =
156+
std::fs::read_to_string(package_json_path.as_path()).map_err(|error| {
157+
Error::ConfigError(
158+
format!(
159+
"Failed to read package.json for local package {spec} at {}: {error}",
160+
package_json_path.as_path().display()
161+
)
162+
.into(),
163+
)
164+
})?;
165+
let package_json: serde_json::Value =
166+
serde_json::from_str(&package_json_content).map_err(Error::JsonError)?;
167+
let Some(package_name) = package_json.get("name").and_then(|name| name.as_str()) else {
168+
return Err(Error::ConfigError(
169+
format!(
170+
"Local package {spec} must have a string name in {}",
171+
package_json_path.as_path().display()
172+
)
173+
.into(),
174+
));
175+
};
176+
177+
Ok((package_name.to_string(), None))
178+
} else {
179+
if spec.starts_with('@') {
180+
if let Some(idx) = spec[1..].find('@') {
181+
let idx = idx + 1;
182+
return Ok((spec[..idx].to_string(), Some(spec[idx + 1..].to_string())));
183+
}
184+
return Ok((spec.to_string(), None));
141185
}
142-
return (spec.to_string(), None);
143-
}
144186

145-
if let Some(idx) = spec.find('@') {
146-
return (spec[..idx].to_string(), Some(spec[idx + 1..].to_string()));
147-
}
187+
if let Some(idx) = spec.find('@') {
188+
return Ok((spec[..idx].to_string(), Some(spec[idx + 1..].to_string())));
189+
}
148190

149-
(spec.to_string(), None)
191+
Ok((spec.to_string(), None))
192+
}
150193
}
151194

152195
fn parse_npm_view_version(stdout: &[u8]) -> Result<String, Error> {

crates/vite_global_cli/src/commands/global/outdated.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ pub async fn get_outdated_packages(
5050
let installed = if !packages.is_empty() {
5151
let mut installed = Vec::new();
5252
for package in packages {
53-
let (package_name, _) = parse_package_spec(package);
53+
let Ok((package_name, _)) = parse_package_spec(package) else {
54+
// Silently skip, follow npm's behavior
55+
continue;
56+
};
5457
if let Some(metadata) = PackageMetadata::load(&package_name).await? {
5558
installed.push((metadata, Some(package.clone())));
5659
}

0 commit comments

Comments
 (0)