use crate::package::*;
fn lookup_registry_package<'a>(
index: &'a PackageRegistryIndex,
name: &str,
) -> Result<&'a RegistryPackage, PackageError> {
if let Some(package) = index.packages.iter().find(|package| package.name == name) {
return Ok(package);
}
let matches: Vec<&RegistryPackage> = index
.packages
.iter()
.filter(|package| {
package
.versions
.iter()
.any(|entry| entry.package.as_deref() == Some(name))
})
.collect();
match matches.as_slice() {
[package] => Ok(package),
[] => Err(format!("package registry does not contain {name}").into()),
many => Err(format!(
"package alias {name} is ambiguous in the registry — found {} packages; use the scoped name (e.g. {})",
many.len(),
many[0].name,
)
.into()),
}
}
pub(crate) fn find_registry_package_version(
index: &PackageRegistryIndex,
name: &str,
version: Option<&str>,
) -> Result<RegistryPackageInfo, PackageError> {
let package = lookup_registry_package(index, name)?;
let selected_version = match version {
Some(version) => Some(
package
.versions
.iter()
.find(|entry| entry.version == version)
.ok_or_else(|| format!("package registry does not contain {name}@{version}"))?
.clone(),
),
None => latest_registry_version(package).cloned(),
};
Ok(RegistryPackageInfo {
package: package.clone(),
selected_version,
})
}
pub(crate) fn find_registry_package_version_matching(
index: &PackageRegistryIndex,
name: &str,
requirement: &str,
) -> Result<RegistryPackageInfo, PackageError> {
let package = lookup_registry_package(index, name)?;
let req = parse_registry_version_req(requirement)?;
let selected_version = package
.versions
.iter()
.filter(|entry| !entry.yanked)
.filter_map(|entry| {
parse_registry_semver(&entry.version)
.ok()
.filter(|version| req.matches(version))
.map(|version| (version, entry.clone()))
})
.max_by(|(left, _), (right, _)| left.cmp(right))
.map(|(_, entry)| entry)
.ok_or_else(|| {
format!("package registry does not contain {name} matching {requirement}")
})?;
Ok(RegistryPackageInfo {
package: package.clone(),
selected_version: Some(selected_version),
})
}
pub(crate) fn search_package_registry_impl(
query: Option<&str>,
registry: Option<&str>,
) -> Result<Vec<RegistryPackage>, PackageError> {
search_package_registry_in(&PackageWorkspace::from_current_dir()?, query, registry)
}
pub(crate) fn search_package_registry_in(
workspace: &PackageWorkspace,
query: Option<&str>,
registry: Option<&str>,
) -> Result<Vec<RegistryPackage>, PackageError> {
let (_, index) = load_package_registry_in(workspace, registry)?;
Ok(index
.packages
.into_iter()
.filter(|package| registry_package_matches(package, query.unwrap_or("")))
.collect())
}
pub(crate) fn search_rule_package_registry_impl(
query: Option<&str>,
registry: Option<&str>,
) -> Result<Vec<RegistryPackage>, PackageError> {
search_rule_package_registry_in(&PackageWorkspace::from_current_dir()?, query, registry)
}
pub(crate) fn search_rule_package_registry_in(
workspace: &PackageWorkspace,
query: Option<&str>,
registry: Option<&str>,
) -> Result<Vec<RegistryPackage>, PackageError> {
Ok(search_package_registry_in(workspace, query, registry)?
.into_iter()
.filter(registry_package_is_rule_pack)
.collect())
}
fn registry_package_is_rule_pack(package: &RegistryPackage) -> bool {
package.rule_pack.is_some()
}
pub(crate) fn package_registry_info_impl(
spec: &str,
registry: Option<&str>,
) -> Result<RegistryPackageInfo, PackageError> {
package_registry_info_in(&PackageWorkspace::from_current_dir()?, spec, registry)
}
pub(crate) fn package_registry_info_in(
workspace: &PackageWorkspace,
spec: &str,
registry: Option<&str>,
) -> Result<RegistryPackageInfo, PackageError> {
let Some((name, version)) = parse_registry_package_spec(spec) else {
return Err(format!(
"invalid registry package name '{spec}'; use names like @burin/notion-sdk or acme-lib"
)
.into());
};
let (_, index) = load_package_registry_in(workspace, registry)?;
find_registry_package_version(&index, name, version)
}
pub(crate) fn registry_dependency_from_spec_in(
workspace: &PackageWorkspace,
spec: &str,
alias: Option<&str>,
registry: Option<&str>,
) -> Result<(String, Dependency), PackageError> {
let Some((name, Some(version))) = parse_registry_package_spec(spec) else {
return Err(format!(
"registry dependency '{spec}' must include a version, for example {spec}@1.2.3"
)
.into());
};
let registry_source = workspace.resolve_registry_source(registry)?;
let (_, index) = load_package_registry_in(workspace, registry)?;
let info = if is_exact_semver(version) {
find_registry_package_version(&index, name, Some(version))?
} else {
find_registry_package_version_matching(&index, name, version)?
};
let selected = info
.selected_version
.ok_or_else(|| format!("package registry does not contain {name}@{version}"))?;
if selected.yanked {
return Err(format!("{name}@{version} is yanked in the package registry").into());
}
let package_name = registry_package_version_alias(&info.package.name, &selected)?;
let alias = alias.unwrap_or(package_name.as_str()).to_string();
let resolved_version = selected.version.clone();
Ok((
alias.clone(),
registry_dependency_table(
info.package.name,
selected,
package_name,
alias,
registry_source,
resolved_version,
)?,
))
}
fn is_exact_semver(spec: &str) -> bool {
parse_registry_semver(spec).is_ok()
}
pub(crate) fn registry_dependency_from_manifest_constraint_in(
workspace: &PackageWorkspace,
alias: &str,
table: &DepTable,
) -> Result<Dependency, PackageError> {
let requirement = table
.version
.as_deref()
.ok_or_else(|| format!("dependency {alias} is missing `version`"))?;
let registry_source = workspace.resolve_registry_source(table.registry.as_deref())?;
let registry_name = table.registry_name.as_deref().unwrap_or(alias);
let (_, index) = load_package_registry_in(workspace, Some(®istry_source))?;
let info = find_registry_package_version_matching(&index, registry_name, requirement)?;
let selected = info.selected_version.ok_or_else(|| {
format!("package registry does not contain {registry_name} matching {requirement}")
})?;
let package_name = selected
.package
.clone()
.or_else(|| table.package.clone())
.unwrap_or_else(|| alias.to_string());
let resolved_version = selected.version.clone();
registry_dependency_table(
registry_name.to_string(),
selected,
package_name,
alias.to_string(),
registry_source,
resolved_version,
)
}
fn registry_package_version_alias(
registry_name: &str,
selected: &RegistryPackageVersion,
) -> Result<String, PackageError> {
if let Some(package) = selected.package.clone() {
return Ok(package);
}
if let Some(git) = selected.git.as_deref() {
return derive_repo_name_from_source(&normalize_git_url(git)?);
}
derive_registry_alias_from_name(registry_name)
}
fn derive_registry_alias_from_name(registry_name: &str) -> Result<String, PackageError> {
let alias = registry_name
.strip_prefix('@')
.and_then(|scoped| scoped.split_once('/').map(|(_, package)| package))
.unwrap_or(registry_name)
.to_string();
validate_package_alias(&alias)?;
Ok(alias)
}
fn registry_dependency_table(
registry_name: String,
selected: RegistryPackageVersion,
package_name: String,
alias: String,
registry_source: String,
resolved_version: String,
) -> Result<Dependency, PackageError> {
let package = (alias != package_name).then_some(package_name);
let RegistryPackageVersion {
git,
archive,
tag,
rev,
checksum,
provenance,
..
} = selected;
let table = if let Some(git) = git {
let git = normalize_git_url(&git)?;
DepTable {
git: Some(git),
tag,
registry_commit: rev,
registry_provenance: provenance,
package,
registry: Some(registry_source),
registry_name: Some(registry_name),
registry_version: Some(resolved_version),
..DepTable::default()
}
} else if let Some(archive) = archive {
DepTable {
archive: Some(normalize_archive_url(&archive)?),
package,
checksum,
registry_provenance: provenance,
registry: Some(registry_source),
registry_name: Some(registry_name),
registry_version: Some(resolved_version),
..DepTable::default()
}
} else {
return Err("registry package version is missing git or archive source"
.to_string()
.into());
};
Ok(Dependency::Table(Box::new(table)))
}