node_runtime: Fix npm v12 output deserialization (#60798)
# Objective - Fix npm v12 output deserialization - Fixes #60705 ## Solution Initially I was thinking about checking the `npm` version and then parsing its output accordingly but that would include a call to `npm --version`. Zed's mojo is to be blazingly fast so just checking the structure of the output should be enough: 1. `npm info --json` in v12 produces an array with 1 object: `[ { "name": "something" } ]` 2. `npm info --json` in v11 and older produces an object: `{ "name": "something" }` So the trick is to check the output: if it's an array, we have npm v12, otherwise npm v11. ## Testing - Did you test these changes? If so, how? 1. Ensure you have `npm` v12 installed. 2. Install a Node.js based language server, for example, `markdownlint` or `oxfmt` 3. Open a project and trigger start of a language server - Are there any parts that need more testing? no - How can other people (reviewers) test your changes? Is there anything specific they need to know? Use steps above - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? I've tested this on macOS and Linux. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [ ] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase N/A --- Release Notes: - Fixed npm v12 output deserialization
This commit is contained in:
1 parent
5b7b970d08
commit
8c4550cb95
1 file changed
+65
-3
@@ -273,7 +273,12 @@ impl NodeRuntime {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let info: NpmInfo = serde_json::from_slice(&output.stdout)?;
|
||||
let info: NpmInfo = deserialize_npm_info_from_response(&output.stdout).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to parse npm info response: {e}\nstdout: {}",
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
)
|
||||
})?;
|
||||
let before = npm_config_before(instance.as_ref(), http.proxy())
|
||||
.await
|
||||
.context("getting npm before config")
|
||||
@@ -412,6 +417,21 @@ pub struct NpmInfo {
|
||||
time: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// Parse NpmInfo from npm info --json output, handling both v11 and >= v12 formats.
|
||||
fn deserialize_npm_info_from_response(data: &[u8]) -> Result<NpmInfo, serde_json::Error> {
|
||||
let value: serde_json::Value = serde_json::from_slice(data)?;
|
||||
|
||||
// npm >= 12 returns an array with one object: [ { ... } ]
|
||||
if let serde_json::Value::Array(arr) = &value {
|
||||
if arr.len() == 1 {
|
||||
return NpmInfo::deserialize(&arr[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// npm <= v11 returns a bare JSON object: { ... }
|
||||
NpmInfo::deserialize(value)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
pub struct NpmInfoDistTags {
|
||||
latest: Option<Version>,
|
||||
@@ -1104,8 +1124,8 @@ mod tests {
|
||||
use semver::Version;
|
||||
|
||||
use super::{
|
||||
NpmInfo, VersionStrategy, build_npm_command_args, proxy_argument,
|
||||
select_npm_package_version, should_install_npm_package_version,
|
||||
NpmInfo, VersionStrategy, build_npm_command_args, deserialize_npm_info_from_response,
|
||||
proxy_argument, select_npm_package_version, should_install_npm_package_version,
|
||||
};
|
||||
|
||||
// Map localhost to 127.0.0.1
|
||||
@@ -1384,4 +1404,46 @@ mod tests {
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_npm_info_npm11_format() -> Result<()> {
|
||||
let json = r#"{
|
||||
"dist-tags": { "latest": "3.0.0" },
|
||||
"versions": ["1.0.0", "2.0.0", "3.0.0"]
|
||||
}"#;
|
||||
|
||||
let info = deserialize_npm_info_from_response(json.as_bytes())?;
|
||||
assert_eq!(info.dist_tags.latest, Some(Version::parse("3.0.0")?));
|
||||
assert_eq!(
|
||||
info.versions,
|
||||
vec![
|
||||
Version::parse("1.0.0")?,
|
||||
Version::parse("2.0.0")?,
|
||||
Version::parse("3.0.0")?
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_npm_v12_format() -> Result<()> {
|
||||
let json = r#"[
|
||||
{
|
||||
"dist-tags": { "latest": "3.0.0" },
|
||||
"versions": ["1.0.0", "2.0.0", "3.0.0"]
|
||||
}
|
||||
]"#;
|
||||
|
||||
let info = deserialize_npm_info_from_response(json.as_bytes())?;
|
||||
assert_eq!(info.dist_tags.latest, Some(Version::parse("3.0.0")?));
|
||||
assert_eq!(
|
||||
info.versions,
|
||||
vec![
|
||||
Version::parse("1.0.0")?,
|
||||
Version::parse("2.0.0")?,
|
||||
Version::parse("3.0.0")?
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user