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:
Vitaly Slobodin authored and GitHub committed 2026-07-12 22:23:53 +00:00
1 parent 5b7b970d08
commit 8c4550cb95
1 file changed
+65 -3
+65 -3
View File
@@ -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(())
}
}