mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
fix(add): better error message when adding package that only has pre-release versions (#26724)
Fixes https://github.com/denoland/deno/issues/26597 A small refactor as well to reduce some code duplication
This commit is contained in:
parent
44eca0505c
commit
706b1dfcea
8 changed files with 187 additions and 64 deletions
|
@ -12,7 +12,9 @@ use deno_core::futures::StreamExt;
|
||||||
use deno_path_util::url_to_file_path;
|
use deno_path_util::url_to_file_path;
|
||||||
use deno_semver::jsr::JsrPackageReqReference;
|
use deno_semver::jsr::JsrPackageReqReference;
|
||||||
use deno_semver::npm::NpmPackageReqReference;
|
use deno_semver::npm::NpmPackageReqReference;
|
||||||
|
use deno_semver::package::PackageNv;
|
||||||
use deno_semver::package::PackageReq;
|
use deno_semver::package::PackageReq;
|
||||||
|
use deno_semver::Version;
|
||||||
use deno_semver::VersionReq;
|
use deno_semver::VersionReq;
|
||||||
use jsonc_parser::cst::CstObject;
|
use jsonc_parser::cst::CstObject;
|
||||||
use jsonc_parser::cst::CstObjectProp;
|
use jsonc_parser::cst::CstObjectProp;
|
||||||
|
@ -455,15 +457,32 @@ pub async fn add(
|
||||||
match package_and_version {
|
match package_and_version {
|
||||||
PackageAndVersion::NotFound {
|
PackageAndVersion::NotFound {
|
||||||
package: package_name,
|
package: package_name,
|
||||||
found_npm_package,
|
help,
|
||||||
package_req,
|
package_req,
|
||||||
} => {
|
} => match help {
|
||||||
if found_npm_package {
|
Some(NotFoundHelp::NpmPackage) => {
|
||||||
bail!("{} was not found, but a matching npm package exists. Did you mean `{}`?", crate::colors::red(package_name), crate::colors::yellow(format!("deno {cmd_name} npm:{package_req}")));
|
bail!(
|
||||||
} else {
|
"{} was not found, but a matching npm package exists. Did you mean `{}`?",
|
||||||
bail!("{} was not found.", crate::colors::red(package_name));
|
crate::colors::red(package_name),
|
||||||
|
crate::colors::yellow(format!("deno {cmd_name} npm:{package_req}"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
Some(NotFoundHelp::JsrPackage) => {
|
||||||
|
bail!(
|
||||||
|
"{} was not found, but a matching jsr package exists. Did you mean `{}`?",
|
||||||
|
crate::colors::red(package_name),
|
||||||
|
crate::colors::yellow(format!("deno {cmd_name} jsr:{package_req}"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(NotFoundHelp::PreReleaseVersion(version)) => {
|
||||||
|
bail!(
|
||||||
|
"{} has only pre-release versions available. Try specifying a version: `{}`",
|
||||||
|
crate::colors::red(&package_name),
|
||||||
|
crate::colors::yellow(format!("deno {cmd_name} {package_name}@^{version}"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => bail!("{} was not found.", crate::colors::red(package_name)),
|
||||||
|
},
|
||||||
PackageAndVersion::Selected(selected) => {
|
PackageAndVersion::Selected(selected) => {
|
||||||
selected_packages.push(selected);
|
selected_packages.push(selected);
|
||||||
}
|
}
|
||||||
|
@ -511,76 +530,144 @@ struct SelectedPackage {
|
||||||
selected_version: String,
|
selected_version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum NotFoundHelp {
|
||||||
|
NpmPackage,
|
||||||
|
JsrPackage,
|
||||||
|
PreReleaseVersion(Version),
|
||||||
|
}
|
||||||
|
|
||||||
enum PackageAndVersion {
|
enum PackageAndVersion {
|
||||||
NotFound {
|
NotFound {
|
||||||
package: String,
|
package: String,
|
||||||
found_npm_package: bool,
|
|
||||||
package_req: PackageReq,
|
package_req: PackageReq,
|
||||||
|
help: Option<NotFoundHelp>,
|
||||||
},
|
},
|
||||||
Selected(SelectedPackage),
|
Selected(SelectedPackage),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn best_version<'a>(
|
||||||
|
versions: impl Iterator<Item = &'a Version>,
|
||||||
|
) -> Option<&'a Version> {
|
||||||
|
let mut maybe_best_version: Option<&Version> = None;
|
||||||
|
for version in versions {
|
||||||
|
let is_best_version = maybe_best_version
|
||||||
|
.as_ref()
|
||||||
|
.map(|best_version| (*best_version).cmp(version).is_lt())
|
||||||
|
.unwrap_or(true);
|
||||||
|
if is_best_version {
|
||||||
|
maybe_best_version = Some(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maybe_best_version
|
||||||
|
}
|
||||||
|
|
||||||
|
trait PackageInfoProvider {
|
||||||
|
const SPECIFIER_PREFIX: &str;
|
||||||
|
/// The help to return if a package is found by this provider
|
||||||
|
const HELP: NotFoundHelp;
|
||||||
|
async fn req_to_nv(&self, req: &PackageReq) -> Option<PackageNv>;
|
||||||
|
async fn latest_version<'a>(&self, req: &PackageReq) -> Option<Version>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageInfoProvider for Arc<JsrFetchResolver> {
|
||||||
|
const HELP: NotFoundHelp = NotFoundHelp::JsrPackage;
|
||||||
|
const SPECIFIER_PREFIX: &str = "jsr";
|
||||||
|
async fn req_to_nv(&self, req: &PackageReq) -> Option<PackageNv> {
|
||||||
|
(**self).req_to_nv(req).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn latest_version<'a>(&self, req: &PackageReq) -> Option<Version> {
|
||||||
|
let info = self.package_info(&req.name).await?;
|
||||||
|
best_version(
|
||||||
|
info
|
||||||
|
.versions
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, version_info)| !version_info.yanked)
|
||||||
|
.map(|(version, _)| version),
|
||||||
|
)
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageInfoProvider for Arc<NpmFetchResolver> {
|
||||||
|
const HELP: NotFoundHelp = NotFoundHelp::NpmPackage;
|
||||||
|
const SPECIFIER_PREFIX: &str = "npm";
|
||||||
|
async fn req_to_nv(&self, req: &PackageReq) -> Option<PackageNv> {
|
||||||
|
(**self).req_to_nv(req).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn latest_version<'a>(&self, req: &PackageReq) -> Option<Version> {
|
||||||
|
let info = self.package_info(&req.name).await?;
|
||||||
|
best_version(info.versions.keys()).cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn find_package_and_select_version_for_req(
|
async fn find_package_and_select_version_for_req(
|
||||||
jsr_resolver: Arc<JsrFetchResolver>,
|
jsr_resolver: Arc<JsrFetchResolver>,
|
||||||
npm_resolver: Arc<NpmFetchResolver>,
|
npm_resolver: Arc<NpmFetchResolver>,
|
||||||
add_package_req: AddRmPackageReq,
|
add_package_req: AddRmPackageReq,
|
||||||
) -> Result<PackageAndVersion, AnyError> {
|
) -> Result<PackageAndVersion, AnyError> {
|
||||||
match add_package_req.value {
|
async fn select<T: PackageInfoProvider, S: PackageInfoProvider>(
|
||||||
AddRmPackageReqValue::Jsr(req) => {
|
main_resolver: T,
|
||||||
let jsr_prefixed_name = format!("jsr:{}", &req.name);
|
fallback_resolver: S,
|
||||||
let Some(nv) = jsr_resolver.req_to_nv(&req).await else {
|
add_package_req: AddRmPackageReq,
|
||||||
if npm_resolver.req_to_nv(&req).await.is_some() {
|
) -> Result<PackageAndVersion, AnyError> {
|
||||||
|
let req = match &add_package_req.value {
|
||||||
|
AddRmPackageReqValue::Jsr(req) => req,
|
||||||
|
AddRmPackageReqValue::Npm(req) => req,
|
||||||
|
};
|
||||||
|
let prefixed_name = format!("{}:{}", T::SPECIFIER_PREFIX, req.name);
|
||||||
|
let help_if_found_in_fallback = S::HELP;
|
||||||
|
let Some(nv) = main_resolver.req_to_nv(req).await else {
|
||||||
|
if fallback_resolver.req_to_nv(req).await.is_some() {
|
||||||
|
// it's in the other registry
|
||||||
|
return Ok(PackageAndVersion::NotFound {
|
||||||
|
package: prefixed_name,
|
||||||
|
help: Some(help_if_found_in_fallback),
|
||||||
|
package_req: req.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if req.version_req.version_text() == "*" {
|
||||||
|
if let Some(pre_release_version) =
|
||||||
|
main_resolver.latest_version(req).await
|
||||||
|
{
|
||||||
return Ok(PackageAndVersion::NotFound {
|
return Ok(PackageAndVersion::NotFound {
|
||||||
package: jsr_prefixed_name,
|
package: prefixed_name,
|
||||||
found_npm_package: true,
|
package_req: req.clone(),
|
||||||
package_req: req,
|
help: Some(NotFoundHelp::PreReleaseVersion(
|
||||||
|
pre_release_version.clone(),
|
||||||
|
)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(PackageAndVersion::NotFound {
|
return Ok(PackageAndVersion::NotFound {
|
||||||
package: jsr_prefixed_name,
|
package: prefixed_name,
|
||||||
found_npm_package: false,
|
help: None,
|
||||||
package_req: req,
|
package_req: req.clone(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
let range_symbol = if req.version_req.version_text().starts_with('~') {
|
let range_symbol = if req.version_req.version_text().starts_with('~') {
|
||||||
"~"
|
"~"
|
||||||
} else if req.version_req.version_text() == nv.version.to_string() {
|
} else if req.version_req.version_text() == nv.version.to_string() {
|
||||||
""
|
""
|
||||||
} else {
|
} else {
|
||||||
"^"
|
"^"
|
||||||
};
|
};
|
||||||
Ok(PackageAndVersion::Selected(SelectedPackage {
|
Ok(PackageAndVersion::Selected(SelectedPackage {
|
||||||
import_name: add_package_req.alias,
|
import_name: add_package_req.alias,
|
||||||
package_name: jsr_prefixed_name,
|
package_name: prefixed_name,
|
||||||
version_req: format!("{}{}", range_symbol, &nv.version),
|
version_req: format!("{}{}", range_symbol, &nv.version),
|
||||||
selected_version: nv.version.to_string(),
|
selected_version: nv.version.to_string(),
|
||||||
}))
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
match &add_package_req.value {
|
||||||
|
AddRmPackageReqValue::Jsr(_) => {
|
||||||
|
select(jsr_resolver, npm_resolver, add_package_req).await
|
||||||
}
|
}
|
||||||
AddRmPackageReqValue::Npm(req) => {
|
AddRmPackageReqValue::Npm(_) => {
|
||||||
let npm_prefixed_name = format!("npm:{}", &req.name);
|
select(npm_resolver, jsr_resolver, add_package_req).await
|
||||||
let Some(nv) = npm_resolver.req_to_nv(&req).await else {
|
|
||||||
return Ok(PackageAndVersion::NotFound {
|
|
||||||
package: npm_prefixed_name,
|
|
||||||
found_npm_package: false,
|
|
||||||
package_req: req,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let range_symbol = if req.version_req.version_text().starts_with('~') {
|
|
||||||
"~"
|
|
||||||
} else if req.version_req.version_text() == nv.version.to_string() {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
"^"
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(PackageAndVersion::Selected(SelectedPackage {
|
|
||||||
import_name: add_package_req.alias,
|
|
||||||
package_name: npm_prefixed_name,
|
|
||||||
version_req: format!("{}{}", range_symbol, &nv.version),
|
|
||||||
selected_version: nv.version.to_string(),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function doThing() {
|
||||||
|
return "thing";
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"exports": {
|
||||||
|
".": "./mod.ts"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function doThing() {
|
||||||
|
return "thing2";
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"exports": {
|
||||||
|
".": "./mod.ts"
|
||||||
|
}
|
||||||
|
}
|
6
tests/registry/jsr/@denotest/unstable/meta.json
Normal file
6
tests/registry/jsr/@denotest/unstable/meta.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"versions": {
|
||||||
|
"1.0.0-beta.1": {},
|
||||||
|
"1.0.0-beta.2": {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,22 @@
|
||||||
{
|
{
|
||||||
"tempDir": true,
|
"tempDir": true,
|
||||||
"steps": [
|
"tests": {
|
||||||
{
|
"npm_package": {
|
||||||
"args": "add npm:@denotest/unstable",
|
"steps": [
|
||||||
"output": "add.out"
|
{
|
||||||
|
"args": "add npm:@denotest/unstable",
|
||||||
|
"output": "add.out"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"jsr_package": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"args": "add jsr:@denotest/unstable",
|
||||||
|
"output": "add_jsr.out",
|
||||||
|
"exitCode": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
|
|
1
tests/specs/add/only_unstable_versions/add_jsr.out
Normal file
1
tests/specs/add/only_unstable_versions/add_jsr.out
Normal file
|
@ -0,0 +1 @@
|
||||||
|
error: jsr:@denotest/unstable has only pre-release versions available. Try specifying a version: `deno add jsr:@denotest/unstable@^1.0.0-beta.2`
|
Loading…
Reference in a new issue