use crate::lockfile::package::PackageColumns as _;
use core::mem::ManuallyDrop;
use bun_core::Output;
use bun_core::strings;
use bun_paths::PathBuffer;
use bun_semver as semver;
use bun_semver::{SlicedString, String as SemverString};
use crate::_folder_resolver::{self as folder_resolver, GlobalOrRelative};
use crate::dependency;
use crate::lockfile::{DependencyIDSlice, DependencySlice};
use crate::npm;
use crate::resolution::Tag as ResolutionTag;
use crate::{DependencyID, PackageID, PackageNameHash, Resolution, invalid_package_id};
use super::PackageManager;
use super::options::LogLevel;
#[inline]
pub fn format_later_version_in_cache<'a>(
this: &'a mut PackageManager,
package_name: &[u8],
name_hash: PackageNameHash,
resolution: &Resolution,
) -> Option<semver::version::Formatter<'a, u64>> {
this.format_later_version_in_cache(package_name, name_hash, resolution)
}
#[inline]
pub fn scope_for_package_name<'a>(
this: &'a PackageManager,
name: &[u8],
) -> &'a npm::registry::Scope {
this.scope_for_package_name(name)
}
#[inline]
pub fn get_installed_versions_from_disk_cache(
this: &mut PackageManager,
tags_buf: &mut Vec<u8>,
package_name: &[u8],
) -> Result<Vec<semver::Version>, bun_core::Error> {
this.get_installed_versions_from_disk_cache(tags_buf, package_name)
}
#[inline]
pub fn resolve_from_disk_cache(
this: &mut PackageManager,
package_name: &[u8],
version: &dependency::Version,
) -> Option<PackageID> {
this.resolve_from_disk_cache(package_name, version)
}
#[inline]
pub fn assign_resolution(
this: &mut PackageManager,
dependency_id: DependencyID,
package_id: PackageID,
) {
this.assign_resolution(dependency_id, package_id)
}
#[inline]
pub fn assign_root_resolution(
this: &mut PackageManager,
dependency_id: DependencyID,
package_id: PackageID,
) {
this.assign_root_resolution(dependency_id, package_id)
}
#[inline]
pub fn verify_resolutions(this: &mut PackageManager, log_level: LogLevel) {
this.verify_resolutions(log_level)
}
impl PackageManager {
pub fn format_later_version_in_cache(
&mut self,
package_name: &[u8],
name_hash: PackageNameHash,
resolution: &Resolution,
) -> Option<semver::version::Formatter<'_, u64>> {
let _ = package_name;
match resolution.tag {
ResolutionTag::Npm => {
let npm_version = resolution.npm().version;
if npm_version.tag.has_pre() {
return None;
}
let manifest = self.manifests.by_name_hash_in_memory(name_hash)?;
if let Some(latest_version) = manifest
.find_by_dist_tag_with_filter(
b"latest",
self.options.minimum_release_age_ms,
self.options.minimum_release_age_excludes,
)
.unwrap()
{
if latest_version.version.order(
npm_version,
&manifest.string_buf,
self.lockfile.buffers.string_bytes.as_slice(),
) != core::cmp::Ordering::Greater
{
return None;
}
return Some(latest_version.version.fmt(&manifest.string_buf));
}
None
}
_ => None,
}
}
pub fn scope_for_package_name(&self, name: &[u8]) -> &npm::registry::Scope {
self.options.scope_for_package_name(name)
}
pub fn get_installed_versions_from_disk_cache(
&mut self,
tags_buf: &mut Vec<u8>,
package_name: &[u8],
) -> Result<Vec<semver::Version>, bun_core::Error> {
let mut list: Vec<semver::Version> = Vec::new();
let cache_dir = super::get_cache_directory(self);
let dir = match bun_sys::Dir::borrow(&cache_dir)
.open_at(package_name)
.map_err(bun_core::Error::from)
{
Ok(d) => d,
Err(e)
if e == bun_core::err!("FileNotFound")
|| e == bun_core::err!("NotDir")
|| e == bun_core::err!("AccessDenied")
|| e == bun_core::err!("DeviceBusy") =>
{
return Ok(list);
}
Err(e) => return Err(e),
};
let mut iter = bun_sys::iterate_dir(dir.fd);
loop {
let entry = match iter.next() {
Ok(Some(e)) => e,
Ok(None) => break,
Err(e) => {
return Err(e.into());
}
};
if entry.kind != bun_sys::EntryKind::Directory
&& entry.kind != bun_sys::EntryKind::SymLink
{
continue;
}
let name: &[u8] = entry.name.slice_u8();
let sliced = SlicedString::init(name, name);
let parsed = semver::Version::parse(sliced);
if !parsed.valid || parsed.wildcard != semver::query::Wildcard::None {
continue;
}
let mut version = parsed.version.min();
let total = (version.tag.build.len() + version.tag.pre.len()) as usize;
if total > 0 {
let len_before = tags_buf.len();
tags_buf.resize(len_before + total, 0);
let mut available = &mut tags_buf[len_before..];
let new_version = version.clone_into(name, &mut available);
version = new_version;
}
list.push(version);
}
Ok(list)
}
pub fn resolve_from_disk_cache(
&mut self,
package_name: &[u8],
version: &dependency::Version,
) -> Option<PackageID> {
if version.tag != dependency::Tag::Npm {
return None;
}
let mut tags_buf: Vec<u8> = Vec::new();
let mut installed_versions =
match self.get_installed_versions_from_disk_cache(&mut tags_buf, package_name) {
Ok(v) => v,
Err(err) => {
Output::debug(format_args!(
"error getting installed versions from disk cache: {}",
err.name()
));
return None;
}
};
{
let tags_slice: &[u8] = tags_buf.as_slice();
installed_versions.sort_by(|a, b| semver::Version::order_fn(tags_slice, *b, *a));
}
let npm_query = version.npm();
for installed_version in installed_versions.iter().copied() {
if npm_query.version.satisfies(
installed_version,
self.lockfile.buffers.string_bytes.as_slice(),
tags_buf.as_slice(),
) {
let mut buf = PathBuffer::uninit();
let npm_package_path = match super::path_for_cached_npm_path(
self,
&mut buf,
package_name,
installed_version,
) {
Ok(p) => p,
Err(err) => {
Output::debug(format_args!(
"error getting path for cached npm path: {}",
err.name()
));
return None;
}
};
let dep_version = dependency::Version {
tag: dependency::Tag::Npm,
literal: SemverString::default(),
value: dependency::Value {
npm: ManuallyDrop::new(dependency::NpmInfo {
name: SemverString::init(package_name, package_name),
version: semver::query::Group::from(installed_version),
is_alias: false,
}),
},
};
match folder_resolver::get_or_put(
GlobalOrRelative::CacheFolder(npm_package_path),
&dep_version,
b".",
self,
) {
folder_resolver::FolderResolution::NewPackageId(id) => {
let deps = self.lockfile.packages.items_dependencies()[id as usize];
super::enqueue_dependency_list(self, deps);
return Some(id);
}
folder_resolver::FolderResolution::PackageId(id) => {
let deps = self.lockfile.packages.items_dependencies()[id as usize];
super::enqueue_dependency_list(self, deps);
return Some(id);
}
folder_resolver::FolderResolution::Err(err) => {
Output::debug(format_args!(
"error getting or putting folder resolution: {}",
err.name()
));
return None;
}
}
}
}
None
}
pub fn assign_resolution(&mut self, dependency_id: DependencyID, package_id: PackageID) {
if cfg!(debug_assertions) {
debug_assert!(
(dependency_id as usize) < self.lockfile.buffers.resolutions.as_slice().len()
);
debug_assert!((package_id as usize) < self.lockfile.packages.len());
}
let buffers = &mut self.lockfile.buffers;
buffers.resolutions.as_mut_slice()[dependency_id as usize] = package_id;
let string_buf = buffers.string_bytes.as_slice();
let dep = &mut buffers.dependencies.as_mut_slice()[dependency_id as usize];
if dep.name.is_empty()
|| dep.name.slice(string_buf) == dep.version.literal.slice(string_buf)
{
dep.name = self.lockfile.packages.items_name()[package_id as usize];
dep.name_hash = self.lockfile.packages.items_name_hash()[package_id as usize];
}
}
pub fn assign_root_resolution(&mut self, dependency_id: DependencyID, package_id: PackageID) {
if cfg!(debug_assertions) {
debug_assert!(
(dependency_id as usize) < self.lockfile.buffers.resolutions.as_slice().len()
);
debug_assert!((package_id as usize) < self.lockfile.packages.len());
debug_assert!(
self.lockfile.buffers.resolutions.as_slice()[dependency_id as usize]
== invalid_package_id
);
}
let buffers = &mut self.lockfile.buffers;
buffers.resolutions.as_mut_slice()[dependency_id as usize] = package_id;
let string_buf = buffers.string_bytes.as_slice();
let dep = &mut buffers.dependencies.as_mut_slice()[dependency_id as usize];
if dep.name.is_empty()
|| dep.name.slice(string_buf) == dep.version.literal.slice(string_buf)
{
dep.name = self.lockfile.packages.items_name()[package_id as usize];
dep.name_hash = self.lockfile.packages.items_name_hash()[package_id as usize];
}
}
pub fn verify_resolutions(&mut self, log_level: LogLevel) {
let lockfile = &self.lockfile;
let resolutions_lists: &[DependencyIDSlice] = lockfile.packages.items_resolutions();
let dependency_lists: &[DependencySlice] = lockfile.packages.items_dependencies();
let pkg_resolutions = lockfile.packages.items_resolution();
let dependencies_buffer = lockfile.buffers.dependencies.as_slice();
let resolutions_buffer = lockfile.buffers.resolutions.as_slice();
let end: PackageID = lockfile.packages.len() as PackageID;
let mut any_failed = false;
let string_buf = lockfile.buffers.string_bytes.as_slice();
debug_assert_eq!(resolutions_lists.len(), dependency_lists.len());
for (parent_id, (resolution_list, dependency_list)) in resolutions_lists
.iter()
.zip(dependency_lists.iter())
.enumerate()
{
let res_slice = resolution_list.get(resolutions_buffer);
let dep_slice = dependency_list.get(dependencies_buffer);
debug_assert_eq!(res_slice.len(), dep_slice.len());
for (package_id, failed_dep) in res_slice.iter().copied().zip(dep_slice.iter()) {
if package_id < end {
continue;
}
if failed_dep.behavior.is_peer() {
continue;
}
let features = match pkg_resolutions[parent_id].tag {
ResolutionTag::Root | ResolutionTag::Workspace | ResolutionTag::Folder => {
self.options.local_package_features
}
_ => self.options.remote_package_features,
};
if failed_dep.behavior.is_optional() || !failed_dep.behavior.is_enabled(features) {
continue;
}
if log_level != LogLevel::Silent {
if failed_dep.name.is_empty()
|| strings::eql_long(
failed_dep.name.slice(string_buf),
failed_dep.version.literal.slice(string_buf),
true,
)
{
Output::err_generic(
"<b>{}<r><d> failed to resolve<r>",
(failed_dep.version.literal.fmt(string_buf),),
);
} else {
Output::err_generic(
"<b>{}<r><d>@<b>{}<r><d> failed to resolve<r>",
(
bstr::BStr::new(failed_dep.name.slice(string_buf)),
failed_dep.version.literal.fmt(string_buf),
),
);
}
}
any_failed = true;
}
}
if any_failed {
self.crash();
}
}
}