mirror of
https://github.com/pesde-pkg/pesde.git
synced 2025-04-10 22:00:55 +01:00
feat: show available targets when none fit
This will help prevent user confusion with targets. Instead of a cryptic "no matching versions available" error, it'll now list the available targets (if the source decides to output them, so if the specifier has a target option)
This commit is contained in:
parent
0e73db2831
commit
e6ee935c11
15 changed files with 148 additions and 93 deletions
|
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Changed
|
||||
- Binary linkers are now done in Rust to simplify their implementation and cross-runtime portability by @daimond113
|
||||
- Show available targets in add and install commands if the specifier hasn't matched any by @daimond113
|
||||
|
||||
## [0.6.0] - 2025-02-22
|
||||
### Added
|
||||
|
|
|
@ -143,22 +143,35 @@ impl AddCommand {
|
|||
.await
|
||||
.context("failed to refresh package source")?;
|
||||
|
||||
let Some(version_id) = source
|
||||
let (_, mut versions, suggestions) = source
|
||||
.resolve(
|
||||
&specifier,
|
||||
&ResolveOptions {
|
||||
project: project.clone(),
|
||||
target: manifest.target.kind(),
|
||||
refreshed_sources,
|
||||
loose_target: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.context("failed to resolve package")?
|
||||
.1
|
||||
.pop_last()
|
||||
.map(|(v_id, _)| v_id)
|
||||
else {
|
||||
anyhow::bail!("no versions found for package");
|
||||
.context("failed to resolve package")?;
|
||||
|
||||
let Some((version_id, _)) = versions.pop_last() else {
|
||||
anyhow::bail!(
|
||||
"no matching versions found for package{}",
|
||||
if suggestions.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
format!(
|
||||
". available targets: {}",
|
||||
suggestions
|
||||
.into_iter()
|
||||
.map(|t| t.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let project_target = manifest.target.kind();
|
||||
|
|
|
@ -82,56 +82,28 @@ impl ExecuteCommand {
|
|||
.context("failed to refresh source")?;
|
||||
|
||||
let version_req = self.package.1.unwrap_or(VersionReq::STAR);
|
||||
let Some((id, pkg_ref)) = ('finder: {
|
||||
let specifier = PesdeDependencySpecifier {
|
||||
name: self.package.0.clone(),
|
||||
version: version_req.clone(),
|
||||
index: DEFAULT_INDEX_NAME.into(),
|
||||
target: None,
|
||||
};
|
||||
|
||||
if let Some((v_id, pkg_ref)) = source
|
||||
.resolve(
|
||||
&specifier,
|
||||
&ResolveOptions {
|
||||
project: project.clone(),
|
||||
target: TargetKind::Lune,
|
||||
refreshed_sources: refreshed_sources.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.context("failed to resolve package")?
|
||||
.1
|
||||
.pop_last()
|
||||
{
|
||||
break 'finder Some((
|
||||
PackageId::new(PackageNames::Pesde(self.package.0.clone()), v_id),
|
||||
pkg_ref,
|
||||
));
|
||||
}
|
||||
|
||||
source
|
||||
.resolve(
|
||||
&specifier,
|
||||
&ResolveOptions {
|
||||
project: project.clone(),
|
||||
target: TargetKind::Luau,
|
||||
refreshed_sources: refreshed_sources.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.context("failed to resolve package")?
|
||||
.1
|
||||
.pop_last()
|
||||
.map(|(v_id, pkg_ref)| {
|
||||
(
|
||||
PackageId::new(PackageNames::Pesde(self.package.0.clone()), v_id),
|
||||
pkg_ref,
|
||||
)
|
||||
})
|
||||
}) else {
|
||||
let Some((v_id, pkg_ref)) = source
|
||||
.resolve(
|
||||
&PesdeDependencySpecifier {
|
||||
name: self.package.0.clone(),
|
||||
version: version_req.clone(),
|
||||
index: DEFAULT_INDEX_NAME.into(),
|
||||
target: None,
|
||||
},
|
||||
&ResolveOptions {
|
||||
project: project.clone(),
|
||||
target: TargetKind::Luau,
|
||||
refreshed_sources: refreshed_sources.clone(),
|
||||
loose_target: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.context("failed to resolve package")?
|
||||
.1
|
||||
.pop_last()
|
||||
else {
|
||||
anyhow::bail!(
|
||||
"no Lune or Luau package could be found for {}@{version_req}",
|
||||
"no compatible package could be found for {}@{version_req}",
|
||||
self.package.0,
|
||||
);
|
||||
};
|
||||
|
@ -151,7 +123,10 @@ impl ExecuteCommand {
|
|||
project.auth_config().clone(),
|
||||
);
|
||||
|
||||
let id = Arc::new(id);
|
||||
let id = Arc::new(PackageId::new(
|
||||
PackageNames::Pesde(self.package.0.clone()),
|
||||
v_id,
|
||||
));
|
||||
|
||||
let fs = source
|
||||
.download(
|
||||
|
|
|
@ -204,8 +204,9 @@ impl InitCommand {
|
|||
},
|
||||
&ResolveOptions {
|
||||
project: project.clone(),
|
||||
target: TargetKind::Lune,
|
||||
target: TargetKind::Luau,
|
||||
refreshed_sources,
|
||||
loose_target: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -92,6 +92,7 @@ impl OutdatedCommand {
|
|||
project: project.clone(),
|
||||
target: manifest_target_kind,
|
||||
refreshed_sources: refreshed_sources.clone(),
|
||||
loose_target: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -465,6 +465,7 @@ info: otherwise, the file was deemed unnecessary, if you don't understand why, p
|
|||
project: project.clone(),
|
||||
target: target_kind,
|
||||
refreshed_sources: refreshed_sources.clone(),
|
||||
loose_target: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -234,11 +234,12 @@ impl Project {
|
|||
.await
|
||||
.map_err(|e| Box::new(e.into()))?;
|
||||
|
||||
let (name, resolved) = source
|
||||
let (name, resolved, suggestions) = source
|
||||
.resolve(&specifier, &ResolveOptions {
|
||||
project: self.clone(),
|
||||
target,
|
||||
refreshed_sources: refreshed_sources.clone(),
|
||||
loose_target: false,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| Box::new(e.into()))?;
|
||||
|
@ -251,7 +252,21 @@ impl Project {
|
|||
.or_else(|| resolved.last_key_value().map(|(ver, _)| PackageId::new(name, ver.clone())))
|
||||
else {
|
||||
return Err(Box::new(errors::DependencyGraphError::NoMatchingVersion(
|
||||
format!("{specifier} ({target})"),
|
||||
format!(
|
||||
"{specifier} {target}{}",
|
||||
if suggestions.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
format!(
|
||||
" available targets: {}",
|
||||
suggestions
|
||||
.into_iter()
|
||||
.map(|t| t.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
),
|
||||
)));
|
||||
};
|
||||
|
||||
|
|
|
@ -20,7 +20,12 @@ use crate::{
|
|||
use fs_err::tokio as fs;
|
||||
use gix::{bstr::BStr, traverse::tree::Recorder, ObjectId, Url};
|
||||
use relative_path::RelativePathBuf;
|
||||
use std::{collections::BTreeMap, fmt::Debug, hash::Hash, path::PathBuf};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
path::PathBuf,
|
||||
};
|
||||
use tokio::task::{spawn_blocking, JoinSet};
|
||||
use tracing::instrument;
|
||||
|
||||
|
@ -322,6 +327,7 @@ impl PackageSource for GitPackageSource {
|
|||
dependencies,
|
||||
},
|
||||
)]),
|
||||
BTreeSet::new(),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
manifest::target::Target,
|
||||
manifest::target::{Target, TargetKind},
|
||||
names::PackageNames,
|
||||
reporters::DownloadProgressReporter,
|
||||
source::{
|
||||
|
@ -7,7 +7,10 @@ use crate::{
|
|||
traits::*,
|
||||
},
|
||||
};
|
||||
use std::{collections::BTreeMap, fmt::Debug};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt::Debug,
|
||||
};
|
||||
|
||||
/// Packages' filesystems
|
||||
pub mod fs;
|
||||
|
@ -43,7 +46,7 @@ pub const ADDITIONAL_FORBIDDEN_FILES: &[&str] = &["default.project.json"];
|
|||
pub const IGNORED_DIRS: &[&str] = &[".git"];
|
||||
|
||||
/// The result of resolving a package
|
||||
pub type ResolveResult<Ref> = (PackageNames, BTreeMap<VersionId, Ref>);
|
||||
pub type ResolveResult<Ref> = (PackageNames, BTreeMap<VersionId, Ref>, BTreeSet<TargetKind>);
|
||||
|
||||
/// All possible package sources
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
|
@ -98,13 +101,14 @@ impl PackageSource for PackageSources {
|
|||
(PackageSources::Pesde(source), DependencySpecifiers::Pesde(specifier)) => source
|
||||
.resolve(specifier, options)
|
||||
.await
|
||||
.map(|(name, results)| {
|
||||
.map(|(name, results, suggestions)| {
|
||||
(
|
||||
name,
|
||||
results
|
||||
.into_iter()
|
||||
.map(|(version, pkg_ref)| (version, PackageRefs::Pesde(pkg_ref)))
|
||||
.collect(),
|
||||
suggestions,
|
||||
)
|
||||
})
|
||||
.map_err(Into::into),
|
||||
|
@ -113,13 +117,14 @@ impl PackageSource for PackageSources {
|
|||
(PackageSources::Wally(source), DependencySpecifiers::Wally(specifier)) => source
|
||||
.resolve(specifier, options)
|
||||
.await
|
||||
.map(|(name, results)| {
|
||||
.map(|(name, results, suggestions)| {
|
||||
(
|
||||
name,
|
||||
results
|
||||
.into_iter()
|
||||
.map(|(version, pkg_ref)| (version, PackageRefs::Wally(pkg_ref)))
|
||||
.collect(),
|
||||
suggestions,
|
||||
)
|
||||
})
|
||||
.map_err(Into::into),
|
||||
|
@ -127,13 +132,14 @@ impl PackageSource for PackageSources {
|
|||
(PackageSources::Git(source), DependencySpecifiers::Git(specifier)) => source
|
||||
.resolve(specifier, options)
|
||||
.await
|
||||
.map(|(name, results)| {
|
||||
.map(|(name, results, suggestions)| {
|
||||
(
|
||||
name,
|
||||
results
|
||||
.into_iter()
|
||||
.map(|(version, pkg_ref)| (version, PackageRefs::Git(pkg_ref)))
|
||||
.collect(),
|
||||
suggestions,
|
||||
)
|
||||
})
|
||||
.map_err(Into::into),
|
||||
|
@ -142,7 +148,7 @@ impl PackageSource for PackageSources {
|
|||
source
|
||||
.resolve(specifier, options)
|
||||
.await
|
||||
.map(|(name, results)| {
|
||||
.map(|(name, results, suggestions)| {
|
||||
(
|
||||
name,
|
||||
results
|
||||
|
@ -151,6 +157,7 @@ impl PackageSource for PackageSources {
|
|||
(version, PackageRefs::Workspace(pkg_ref))
|
||||
})
|
||||
.collect(),
|
||||
suggestions,
|
||||
)
|
||||
})
|
||||
.map_err(Into::into)
|
||||
|
@ -159,13 +166,14 @@ impl PackageSource for PackageSources {
|
|||
(PackageSources::Path(source), DependencySpecifiers::Path(specifier)) => source
|
||||
.resolve(specifier, options)
|
||||
.await
|
||||
.map(|(name, results)| {
|
||||
.map(|(name, results, suggestions)| {
|
||||
(
|
||||
name,
|
||||
results
|
||||
.into_iter()
|
||||
.map(|(version, pkg_ref)| (version, PackageRefs::Path(pkg_ref)))
|
||||
.collect(),
|
||||
suggestions,
|
||||
)
|
||||
})
|
||||
.map_err(Into::into),
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
|||
Project,
|
||||
};
|
||||
use futures::TryStreamExt as _;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use tracing::instrument;
|
||||
|
||||
/// The path package reference
|
||||
|
@ -122,6 +122,7 @@ impl PackageSource for PathPackageSource {
|
|||
VersionId::new(manifest.version, manifest.target.kind()),
|
||||
pkg_ref,
|
||||
)]),
|
||||
BTreeSet::new(),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,10 @@ use specifier::PesdeDependencySpecifier;
|
|||
|
||||
use crate::{
|
||||
engine::EngineKind,
|
||||
manifest::{target::Target, Alias, DependencyType},
|
||||
manifest::{
|
||||
target::{Target, TargetKind},
|
||||
Alias, DependencyType,
|
||||
},
|
||||
names::{PackageName, PackageNames},
|
||||
reporters::{response_to_async_read, DownloadProgressReporter},
|
||||
source::{
|
||||
|
@ -147,6 +150,7 @@ impl PackageSource for PesdePackageSource {
|
|||
let ResolveOptions {
|
||||
project,
|
||||
target: project_target,
|
||||
loose_target,
|
||||
..
|
||||
} = options;
|
||||
|
||||
|
@ -158,14 +162,28 @@ impl PackageSource for PesdePackageSource {
|
|||
|
||||
tracing::debug!("{} has {} possible entries", specifier.name, entries.len());
|
||||
|
||||
let suggestions = entries
|
||||
.iter()
|
||||
.filter(|(_, entry)| !entry.yanked)
|
||||
.filter(|(v_id, _)| version_matches(&specifier.version, v_id.version()))
|
||||
.map(|(v_id, _)| v_id.target())
|
||||
.collect();
|
||||
|
||||
let specifier_target = specifier.target.unwrap_or(*project_target);
|
||||
|
||||
Ok((
|
||||
PackageNames::Pesde(specifier.name.clone()),
|
||||
entries
|
||||
.into_iter()
|
||||
.filter(|(_, entry)| !entry.yanked)
|
||||
.filter(|(VersionId(version, target), _)| {
|
||||
version_matches(&specifier.version, version)
|
||||
&& specifier.target.unwrap_or(*project_target) == *target
|
||||
.filter(|(v_id, _)| version_matches(&specifier.version, v_id.version()))
|
||||
.filter(|(v_id, _)| {
|
||||
// we want anything which might contain bins, scripts (so not Roblox)
|
||||
if *loose_target && specifier_target == TargetKind::Luau {
|
||||
!matches!(v_id.target(), TargetKind::Roblox | TargetKind::RobloxServer)
|
||||
} else {
|
||||
specifier_target == v_id.target()
|
||||
}
|
||||
})
|
||||
.map(|(id, entry)| {
|
||||
(
|
||||
|
@ -177,6 +195,7 @@ impl PackageSource for PesdePackageSource {
|
|||
)
|
||||
})
|
||||
.collect(),
|
||||
suggestions,
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,9 @@ pub struct ResolveOptions {
|
|||
pub target: TargetKind,
|
||||
/// The sources that have been refreshed
|
||||
pub refreshed_sources: RefreshedSources,
|
||||
/// Whether to find any compatible target instead of a strict equal. Each source defines its
|
||||
/// own loose rules.
|
||||
pub loose_target: bool,
|
||||
}
|
||||
|
||||
/// Options for downloading a package
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{
|
||||
manifest::{errors, Alias, DependencyType},
|
||||
manifest::{errors, target::TargetKind, Alias, DependencyType},
|
||||
names::wally::WallyPackageName,
|
||||
source::{specifiers::DependencySpecifiers, wally::specifier::WallyDependencySpecifier},
|
||||
};
|
||||
|
@ -9,7 +9,7 @@ use semver::{Version, VersionReq};
|
|||
use serde::{Deserialize, Deserializer};
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[derive(Deserialize, Copy, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Realm {
|
||||
#[serde(alias = "dev")]
|
||||
|
@ -17,6 +17,15 @@ pub enum Realm {
|
|||
Server,
|
||||
}
|
||||
|
||||
impl Realm {
|
||||
pub fn to_target(self) -> TargetKind {
|
||||
match self {
|
||||
Realm::Shared => TargetKind::Roblox,
|
||||
Realm::Server => TargetKind::RobloxServer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct WallyPackage {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
manifest::target::{Target, TargetKind},
|
||||
manifest::target::Target,
|
||||
names::{wally::WallyPackageName, PackageNames},
|
||||
reporters::{response_to_async_read, DownloadProgressReporter},
|
||||
source::{
|
||||
|
@ -9,11 +9,7 @@ use crate::{
|
|||
traits::{
|
||||
DownloadOptions, GetTargetOptions, PackageSource, RefreshOptions, ResolveOptions,
|
||||
},
|
||||
wally::{
|
||||
compat_util::get_target,
|
||||
manifest::{Realm, WallyManifest},
|
||||
pkg_ref::WallyPackageRef,
|
||||
},
|
||||
wally::{compat_util::get_target, manifest::WallyManifest, pkg_ref::WallyPackageRef},
|
||||
PackageSources, ResolveResult, IGNORED_DIRS, IGNORED_FILES,
|
||||
},
|
||||
util::hash,
|
||||
|
@ -24,7 +20,10 @@ use gix::Url;
|
|||
use relative_path::RelativePathBuf;
|
||||
use reqwest::header::AUTHORIZATION;
|
||||
use serde::Deserialize;
|
||||
use std::{collections::BTreeMap, path::PathBuf};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
path::PathBuf,
|
||||
};
|
||||
use tokio::{io::AsyncReadExt as _, pin, task::spawn_blocking};
|
||||
use tokio_util::compat::FuturesAsyncReadCompatExt as _;
|
||||
use tracing::instrument;
|
||||
|
@ -198,13 +197,7 @@ impl PackageSource for WallyPackageSource {
|
|||
})?;
|
||||
|
||||
Ok((
|
||||
VersionId(
|
||||
manifest.package.version,
|
||||
match manifest.package.realm {
|
||||
Realm::Server => TargetKind::RobloxServer,
|
||||
Realm::Shared => TargetKind::Roblox,
|
||||
},
|
||||
),
|
||||
VersionId(manifest.package.version, manifest.package.realm.to_target()),
|
||||
WallyPackageRef {
|
||||
index_url: index_url.clone(),
|
||||
dependencies,
|
||||
|
@ -212,6 +205,7 @@ impl PackageSource for WallyPackageSource {
|
|||
))
|
||||
})
|
||||
.collect::<Result<_, errors::ResolveError>>()?,
|
||||
BTreeSet::new(),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
|||
};
|
||||
use futures::StreamExt as _;
|
||||
use relative_path::RelativePathBuf;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use tokio::pin;
|
||||
use tracing::instrument;
|
||||
|
||||
|
@ -47,6 +47,8 @@ impl PackageSource for WorkspacePackageSource {
|
|||
..
|
||||
} = options;
|
||||
|
||||
let mut suggestions = BTreeSet::new();
|
||||
|
||||
let (path, manifest) = 'finder: {
|
||||
let target = specifier.target.unwrap_or(*project_target);
|
||||
|
||||
|
@ -54,8 +56,13 @@ impl PackageSource for WorkspacePackageSource {
|
|||
pin!(members);
|
||||
|
||||
while let Some((path, manifest)) = members.next().await.transpose()? {
|
||||
if manifest.name == specifier.name && manifest.target.kind() == target {
|
||||
break 'finder (path, manifest);
|
||||
let member_target = manifest.target.kind();
|
||||
if manifest.name == specifier.name {
|
||||
suggestions.insert(member_target);
|
||||
|
||||
if member_target == target {
|
||||
break 'finder (path, manifest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,6 +128,7 @@ impl PackageSource for WorkspacePackageSource {
|
|||
VersionId::new(manifest.version, manifest_target_kind),
|
||||
pkg_ref,
|
||||
)]),
|
||||
suggestions,
|
||||
))
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue