mirror of
https://github.com/denoland/deno.git
synced 2025-09-28 05:04:48 +00:00
fix(install): handle when bin entry info isn't present in package.json but is in registry (#28822)
Apparently things like the `bin` field can appear in the version info from the registry, but not the package's `package.json`. I'm still not sure how you actually achieve this, but it's the case for `esbuild-wasm`. This fixes the following panic: ``` ❯ deno i --node-modules-dir npm:esbuild-wasm Add npm:esbuild-wasm@0.25.2 Initialize ⣯ [00:00] - esbuild-wasm@0.25.2 ============================================================ Deno has panicked. This is a bug in Deno. Please report this at https://github.com/denoland/deno/issues/new. If you can reliably reproduce this panic, include the reproduction steps and re-run with the RUST_BACKTRACE=1 env var set and include the backtrace in your report. Platform: macos aarch64 Version: 2.2.8+58c6c0b Args: ["deno", "i", "--node-modules-dir", "npm:esbuild-wasm"] View stack trace at: https://panic.deno.com/v2.2.8+58c6c0bc9c1b4ee08645be936ff9268f17028f0f/aarch64-apple-darwin/g4h6Jo393pB4k4kqBo-3kqBg6klqBogtyLg13yLw_t0Lw549Hgj8-Hgw__H428-F4yv_HgjkpKww7gIon4gIw54rKwi5MorzMw5y7G42g7Iw---I40s-I4vu4Jw2rEw8z7Dwnr6J4tp7Bo_vvK thread 'main' panicked at cli/npm/installer/common/bin_entries.rs:108:30: called `Option::unwrap()` on a `None` value note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ```
This commit is contained in:
parent
ac5c6018a8
commit
ce5b9da11b
14 changed files with 226 additions and 38 deletions
|
@ -1225,7 +1225,15 @@ impl CliOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_npm_caching_strategy(&self) -> NpmCachingStrategy {
|
pub fn default_npm_caching_strategy(&self) -> NpmCachingStrategy {
|
||||||
if self.flags.unstable_config.npm_lazy_caching {
|
if matches!(
|
||||||
|
self.sub_command(),
|
||||||
|
DenoSubcommand::Install(InstallFlags::Local(
|
||||||
|
InstallFlagsLocal::TopLevel | InstallFlagsLocal::Add(_)
|
||||||
|
)) | DenoSubcommand::Add(_)
|
||||||
|
| DenoSubcommand::Outdated(_)
|
||||||
|
) {
|
||||||
|
NpmCachingStrategy::Manual
|
||||||
|
} else if self.flags.unstable_config.npm_lazy_caching {
|
||||||
NpmCachingStrategy::Lazy
|
NpmCachingStrategy::Lazy
|
||||||
} else {
|
} else {
|
||||||
NpmCachingStrategy::Eager
|
NpmCachingStrategy::Eager
|
||||||
|
|
|
@ -437,7 +437,10 @@ async fn resolve_custom_commands_from_packages<
|
||||||
extra.clone()
|
extra.clone()
|
||||||
} else {
|
} else {
|
||||||
let Ok(extra) = provider
|
let Ok(extra) = provider
|
||||||
.get_package_extra_info(&package.id.nv, package.is_deprecated)
|
.get_package_extra_info(
|
||||||
|
&package.id.nv,
|
||||||
|
super::ExpectedExtraInfo::from_package(package),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -8,6 +8,7 @@ use deno_core::parking_lot::RwLock;
|
||||||
use deno_error::JsErrorBox;
|
use deno_error::JsErrorBox;
|
||||||
use deno_npm::registry::NpmRegistryApi;
|
use deno_npm::registry::NpmRegistryApi;
|
||||||
use deno_npm::NpmPackageExtraInfo;
|
use deno_npm::NpmPackageExtraInfo;
|
||||||
|
use deno_npm::NpmResolutionPackage;
|
||||||
use deno_semver::package::PackageNv;
|
use deno_semver::package::PackageNv;
|
||||||
|
|
||||||
use super::PackageCaching;
|
use super::PackageCaching;
|
||||||
|
@ -29,7 +30,7 @@ pub trait NpmPackageExtraInfoProvider: std::fmt::Debug + Send + Sync {
|
||||||
async fn get_package_extra_info(
|
async fn get_package_extra_info(
|
||||||
&self,
|
&self,
|
||||||
package_id: &PackageNv,
|
package_id: &PackageNv,
|
||||||
is_deprecated: bool,
|
expected: ExpectedExtraInfo,
|
||||||
) -> Result<deno_npm::NpmPackageExtraInfo, JsErrorBox>;
|
) -> Result<deno_npm::NpmPackageExtraInfo, JsErrorBox>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,11 +40,11 @@ impl<T: NpmPackageExtraInfoProvider + ?Sized> NpmPackageExtraInfoProvider
|
||||||
async fn get_package_extra_info(
|
async fn get_package_extra_info(
|
||||||
&self,
|
&self,
|
||||||
package_id: &PackageNv,
|
package_id: &PackageNv,
|
||||||
is_deprecated: bool,
|
expected: ExpectedExtraInfo,
|
||||||
) -> Result<deno_npm::NpmPackageExtraInfo, JsErrorBox> {
|
) -> Result<deno_npm::NpmPackageExtraInfo, JsErrorBox> {
|
||||||
self
|
self
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.get_package_extra_info(package_id, is_deprecated)
|
.get_package_extra_info(package_id, expected)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,19 +77,28 @@ impl ExtraInfoProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::common::NpmPackageExtraInfoProvider for ExtraInfoProvider {
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
async fn get_package_extra_info(
|
pub struct ExpectedExtraInfo {
|
||||||
&self,
|
pub deprecated: bool,
|
||||||
package_nv: &PackageNv,
|
pub bin: bool,
|
||||||
deprecated: bool,
|
pub scripts: bool,
|
||||||
) -> Result<NpmPackageExtraInfo, JsErrorBox> {
|
|
||||||
if let Some(extra_info) = self.cache.read().get(package_nv) {
|
|
||||||
return Ok(extra_info.clone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if deprecated {
|
impl ExpectedExtraInfo {
|
||||||
// we need the registry version info to get the deprecated string, since it's not in the
|
pub fn from_package(package: &NpmResolutionPackage) -> Self {
|
||||||
// package's package.json
|
Self {
|
||||||
|
deprecated: package.is_deprecated,
|
||||||
|
bin: package.has_bin,
|
||||||
|
scripts: package.has_scripts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtraInfoProvider {
|
||||||
|
async fn fetch_from_registry(
|
||||||
|
&self,
|
||||||
|
package_nv: &PackageNv,
|
||||||
|
) -> Result<NpmPackageExtraInfo, JsErrorBox> {
|
||||||
let package_info = self
|
let package_info = self
|
||||||
.npm_registry_info_provider
|
.npm_registry_info_provider
|
||||||
.package_info(&package_nv.name)
|
.package_info(&package_nv.name)
|
||||||
|
@ -103,14 +113,56 @@ impl super::common::NpmPackageExtraInfoProvider for ExtraInfoProvider {
|
||||||
bin: version_info.bin.clone(),
|
bin: version_info.bin.clone(),
|
||||||
scripts: version_info.scripts.clone(),
|
scripts: version_info.scripts.clone(),
|
||||||
})
|
})
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
async fn fetch_from_package_json(
|
||||||
|
&self,
|
||||||
|
package_nv: &PackageNv,
|
||||||
|
) -> Result<NpmPackageExtraInfo, JsErrorBox> {
|
||||||
let folder_path = self.npm_cache.package_folder_for_nv(package_nv);
|
let folder_path = self.npm_cache.package_folder_for_nv(package_nv);
|
||||||
let package_json_path = folder_path.join("package.json");
|
let package_json_path = folder_path.join("package.json");
|
||||||
|
let extra_info: NpmPackageExtraInfo =
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
let package_json = std::fs::read_to_string(&package_json_path)
|
let package_json = std::fs::read_to_string(&package_json_path)
|
||||||
.map_err(JsErrorBox::from_err)?;
|
.map_err(JsErrorBox::from_err)?;
|
||||||
let extra_info: NpmPackageExtraInfo =
|
let extra_info: NpmPackageExtraInfo =
|
||||||
deno_core::serde_json::from_str(&package_json)
|
deno_core::serde_json::from_str(&package_json)
|
||||||
.map_err(JsErrorBox::from_err)?;
|
.map_err(JsErrorBox::from_err)?;
|
||||||
|
|
||||||
|
Ok::<_, JsErrorBox>(extra_info)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(JsErrorBox::from_err)??;
|
||||||
|
Ok(extra_info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::common::NpmPackageExtraInfoProvider for ExtraInfoProvider {
|
||||||
|
async fn get_package_extra_info(
|
||||||
|
&self,
|
||||||
|
package_nv: &PackageNv,
|
||||||
|
expected: ExpectedExtraInfo,
|
||||||
|
) -> Result<NpmPackageExtraInfo, JsErrorBox> {
|
||||||
|
if let Some(extra_info) = self.cache.read().get(package_nv) {
|
||||||
|
return Ok(extra_info.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let extra_info = if expected.deprecated {
|
||||||
|
// we need the registry version info to get the deprecated string, since it's not in the
|
||||||
|
// package's package.json
|
||||||
|
self.fetch_from_registry(package_nv).await?
|
||||||
|
} else {
|
||||||
|
let extra_info = self.fetch_from_package_json(package_nv).await?;
|
||||||
|
// some packages have bin in registry but not in package.json (e.g. esbuild-wasm)
|
||||||
|
// still not sure how that happens
|
||||||
|
if (expected.bin && extra_info.bin.is_none())
|
||||||
|
|| (expected.scripts && extra_info.scripts.is_empty())
|
||||||
|
{
|
||||||
|
self.fetch_from_registry(package_nv).await?
|
||||||
|
} else {
|
||||||
|
extra_info
|
||||||
|
}
|
||||||
|
};
|
||||||
self
|
self
|
||||||
.cache
|
.cache
|
||||||
.write()
|
.write()
|
||||||
|
@ -118,4 +170,3 @@ impl super::common::NpmPackageExtraInfoProvider for ExtraInfoProvider {
|
||||||
Ok(extra_info)
|
Ok(extra_info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -414,12 +414,16 @@ async fn sync_resolution_with_fs(
|
||||||
Ok::<_, SyncResolutionWithFsError>(())
|
Ok::<_, SyncResolutionWithFsError>(())
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let extra_fut = if package.has_bin
|
let extra_fut = if (package.has_bin
|
||||||
|| package.has_scripts
|
|| package.has_scripts
|
||||||
|| package.is_deprecated && package.extra.is_none()
|
|| package.is_deprecated)
|
||||||
|
&& package.extra.is_none()
|
||||||
{
|
{
|
||||||
extra_info_provider
|
extra_info_provider
|
||||||
.get_package_extra_info(&package.id.nv, package.is_deprecated)
|
.get_package_extra_info(
|
||||||
|
&package.id.nv,
|
||||||
|
super::common::ExpectedExtraInfo::from_package(package),
|
||||||
|
)
|
||||||
.boxed_local()
|
.boxed_local()
|
||||||
} else {
|
} else {
|
||||||
std::future::ready(Ok(package.extra.clone().unwrap_or_default()))
|
std::future::ready(Ok(package.extra.clone().unwrap_or_default()))
|
||||||
|
@ -465,7 +469,10 @@ async fn sync_resolution_with_fs(
|
||||||
cache_futures.push(
|
cache_futures.push(
|
||||||
async move {
|
async move {
|
||||||
let extra = extra_info_provider
|
let extra = extra_info_provider
|
||||||
.get_package_extra_info(&package.id.nv, package.is_deprecated)
|
.get_package_extra_info(
|
||||||
|
&package.id.nv,
|
||||||
|
super::common::ExpectedExtraInfo::from_package(package),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(JsErrorBox::from_err)?;
|
.map_err(JsErrorBox::from_err)?;
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "denotest-packagejson-missing-info",
|
||||||
|
"dist-tags": {
|
||||||
|
"latest": "1.0.0-missingbin"
|
||||||
|
},
|
||||||
|
"versions": {
|
||||||
|
"1.0.0-missingbin": {
|
||||||
|
"name": "denotest-packagejson-missing-info",
|
||||||
|
"version": "1.0.0-missingbin",
|
||||||
|
"bin": {
|
||||||
|
"denotest": "./bin/denotest.js"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"tarball": "http://localhost:4260/denotest-packagejson-missing-info/denotest-packagejson-missing-info-1.0.0-missingbin.tgz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0.5.0-missingscripts": {
|
||||||
|
"name": "denotest-packagejson-missing-info",
|
||||||
|
"version": "0.5.0-missingscripts",
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "echo 'postinstall'"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"tarball": "http://localhost:4260/denotest-packagejson-missing-info/denotest-packagejson-missing-info-0.5.0-missingscripts.tgz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0.2.5-missingdeprecated": {
|
||||||
|
"name": "denotest-packagejson-missing-info",
|
||||||
|
"version": "0.2.5-missingdeprecated",
|
||||||
|
"deprecated": "Use 0.5.0 or later",
|
||||||
|
"dist": {
|
||||||
|
"tarball": "http://localhost:4260/denotest-packagejson-missing-info/denotest-packagejson-missing-info-0.2.5-missingdeprecated.tgz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
tests/specs/install/packagejson_missing_extra/__test__.jsonc
Normal file
45
tests/specs/install/packagejson_missing_extra/__test__.jsonc
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"tempDir": true,
|
||||||
|
"tests": {
|
||||||
|
"missing_bin": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"args": "install npm:denotest-packagejson-missing-info@1.0.0-missingbin",
|
||||||
|
"output": "missingbin.out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": "run -A ./has-bin.ts denotest",
|
||||||
|
"output": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"missing_scripts": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"args": "install npm:denotest-packagejson-missing-info@0.5.0-missingscripts",
|
||||||
|
"output": "missingscripts.out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": "install --allow-scripts",
|
||||||
|
"output": "[WILDCARD]running 'postinstall' script\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"missing_deprecated": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"args": "install npm:denotest-packagejson-missing-info@0.2.5-missingdeprecated",
|
||||||
|
"output": "missingdeprecated.out"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"missing_deprecated_with_lazy_caching": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"args": "install --unstable-npm-lazy-caching npm:denotest-packagejson-missing-info@0.2.5-missingdeprecated",
|
||||||
|
"output": "missingdeprecated.out"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
tests/specs/install/packagejson_missing_extra/deno.json
Normal file
3
tests/specs/install/packagejson_missing_extra/deno.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"nodeModulesDir": "auto"
|
||||||
|
}
|
20
tests/specs/install/packagejson_missing_extra/has-bin.ts
Normal file
20
tests/specs/install/packagejson_missing_extra/has-bin.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
const name = Deno.args[0].trim();
|
||||||
|
|
||||||
|
function exists(path: string) {
|
||||||
|
try {
|
||||||
|
Deno.statSync(path);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(exists(`node_modules/.bin/${name}`) ||
|
||||||
|
exists(`node_modules/.bin/${name}.cmd`))
|
||||||
|
) {
|
||||||
|
console.log("missing bin");
|
||||||
|
console.log(`node_modules/.bin/${name}`);
|
||||||
|
console.log(Deno.readDirSync("node_modules/.bin").toArray());
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
Add npm:denotest-packagejson-missing-info@1.0.0-missingbin
|
||||||
|
Download http://localhost:4260/denotest-packagejson-missing-info
|
||||||
|
Download http://localhost:4260/denotest-packagejson-missing-info/denotest-packagejson-missing-info-1.0.0-missingbin.tgz
|
||||||
|
Initialize denotest-packagejson-missing-info@1.0.0-missingbin
|
|
@ -0,0 +1,6 @@
|
||||||
|
Add npm:denotest-packagejson-missing-info@0.2.5-missingdeprecated
|
||||||
|
Download http://localhost:4260/denotest-packagejson-missing-info
|
||||||
|
Download http://localhost:4260/denotest-packagejson-missing-info/denotest-packagejson-missing-info-0.2.5-missingdeprecated.tgz
|
||||||
|
Initialize denotest-packagejson-missing-info@0.2.5-missingdeprecated
|
||||||
|
Warning The following packages are deprecated:
|
||||||
|
┖─ npm:denotest-packagejson-missing-info@0.2.5-missingdeprecated (Use 0.5.0 or later)
|
|
@ -0,0 +1,5 @@
|
||||||
|
Add npm:denotest-packagejson-missing-info@0.5.0-missingscripts
|
||||||
|
Download http://localhost:4260/denotest-packagejson-missing-info
|
||||||
|
Download http://localhost:4260/denotest-packagejson-missing-info/denotest-packagejson-missing-info-0.5.0-missingscripts.tgz
|
||||||
|
Initialize denotest-packagejson-missing-info@0.5.0-missingscripts
|
||||||
|
Warning The following packages contained npm lifecycle scripts (preinstall/install/postinstall) that were not executed:[WILDCARD]
|
Loading…
Add table
Add a link
Reference in a new issue