use core::cmp::Ordering;
use core::fmt;
use std::io::Write as _;
use bun_alloc::AllocError;
use bun_collections::{
ArrayHashMap, ArrayIdentityContext, ArrayIdentityContextU64, DynamicBitSet,
HashMap as BunHashMap, IdentityContext, LinearFifo, linear_fifo::DynamicBuffer,
};
use bun_core::fmt::PathSep;
use bun_core::{Error as BunError, Global, Output, err};
use bun_paths::{MAX_PATH_BYTES, PathBuffer, SEP, SEP_STR, platform, resolve_path};
use crate::bun_json as JSON;
use bun_core::zstr;
use bun_core::{ZStr, strings};
use bun_dotenv as DotEnv;
use bun_perf::system_timer::Timer;
use bun_resolver::fs::{self as Fs, FileSystem};
use bun_semver::{self as Semver, ExternalString, String as SemverString};
use bun_sha_hmac as Crypto;
use bun_sys::{self as sys, Fd, File};
use crate::config_version::ConfigVersion;
use crate::migration;
use crate::package_manager::WorkspaceFilter;
use crate::package_manager_real::{
Options as PackageManagerOptions, options::LogLevel, populate_manifest_cache,
};
use crate::resolution_real::{self as resolution, Resolution};
use crate::string_builder;
use crate::update_request::UpdateRequest;
use crate::{
self as Install, DependencyID, ExternalSlice, Features, PackageID, PackageManager,
PackageNameAndVersionHash, PackageNameHash, TruncatedPackageNameHash, dependency,
dependency::Dependency, initialize_store, invalid_dependency_id, invalid_package_id,
npm as Npm,
};
#[path = "lockfile/Buffers.rs"]
pub mod buffers;
#[path = "lockfile/bun.lock.rs"]
pub mod bun_lock;
#[path = "lockfile/bun.lockb.rs"]
pub mod bun_lockb;
#[path = "lockfile/CatalogMap.rs"]
pub mod catalog_map;
#[path = "lockfile/lockfile_json_stringify_for_debugging.rs"]
pub mod lockfile_json_stringify_for_debugging;
#[path = "lockfile/OverrideMap.rs"]
pub mod override_map;
#[path = "lockfile/Package.rs"]
pub mod package;
#[path = "lockfile/Tree.rs"]
pub mod tree;
#[path = "lockfile/printer"]
pub mod printer_mods {
#[path = "tree_printer.rs"]
pub mod tree_printer;
#[path = "Yarn.rs"]
pub mod yarn;
}
pub use self::buffers::Buffers;
use self::bun_lock as TextLockfile;
pub use self::bun_lockb as Serializer;
pub use self::catalog_map::CatalogMap;
pub use self::lockfile_json_stringify_for_debugging::json_stringify;
pub use self::override_map::OverrideMap;
pub use self::package::Package; pub use self::tree::Tree;
pub use crate::padding_checker::assert_no_uninitialized_padding;
use self::package::PackageColumns as _;
type DependencyVersion = dependency::Version;
type ResolutionTag = resolution::Tag;
type SemverStringBuf<'a> = bun_semver::semver_string::Buf<'a>;
type SemverStringBuilder = bun_semver::semver_string::Builder;
pub(crate) type PackageIDSlice = ExternalSlice<PackageID>;
pub type DependencySlice = ExternalSlice<Dependency>;
pub(crate) type DependencyIDSlice = ExternalSlice<DependencyID>;
pub(crate) type PackageIDList = Vec<PackageID>;
pub(crate) type DependencyList = Vec<Dependency>;
pub(crate) type DependencyIDList = Vec<DependencyID>;
pub(crate) type StringBuffer = Vec<u8>;
pub(crate) type ExternalStringBuffer = Vec<ExternalString>;
pub(crate) type NameHashMap = ArrayHashMap<PackageNameHash, SemverString, ArrayIdentityContextU64>;
pub(crate) type TrustedDependenciesSet =
ArrayHashMap<TruncatedPackageNameHash, Box<[u8]>, ArrayIdentityContext>;
pub(crate) type VersionHashMap =
ArrayHashMap<PackageNameHash, Semver::Version, ArrayIdentityContextU64>;
pub(crate) type PatchedDependenciesMap =
ArrayHashMap<PackageNameAndVersionHash, PatchedDep, ArrayIdentityContextU64>;
pub(crate) type StringPool = bun_semver::string::StringPool;
pub(crate) type MetaHash = [u8; 32]; pub(crate) const ZERO_HASH: MetaHash = [0u8; 32];
pub enum Cleaned<'a> {
Same(&'a mut Lockfile),
New(Box<Lockfile>),
}
pub type Stream = bun_io::FixedBufferStream<Vec<u8>>;
pub trait PositionalStream {
fn get_pos(&self) -> Result<usize, BunError>;
fn pwrite(&mut self, data: &[u8], index: usize) -> usize;
}
impl<'a> PositionalStream for Serializer::StreamType<'a> {
#[inline]
fn get_pos(&self) -> Result<usize, BunError> {
Serializer::StreamType::get_pos(self)
}
#[inline]
fn pwrite(&mut self, data: &[u8], index: usize) -> usize {
Serializer::StreamType::pwrite(self, data, index)
}
}
pub struct Lockfile {
pub format: FormatVersion,
pub text_lockfile_version: bun_lock::Version,
pub meta_hash: MetaHash,
pub packages: PackageList,
pub buffers: Buffers,
pub package_index: PackageIndexMap,
pub string_pool: StringPool,
pub scratch: Scratch,
pub scripts: Scripts,
pub workspace_paths: NameHashMap,
pub workspace_versions: VersionHashMap,
pub trusted_dependencies: Option<TrustedDependenciesSet>,
pub patched_dependencies: PatchedDependenciesMap,
pub overrides: OverrideMap,
pub catalogs: CatalogMap,
pub saved_config_version: Option<ConfigVersion>,
pub loaded_package_count: PackageID,
pub exact_pinned: Vec<bool>,
}
pub(crate) type PackageList = self::package::List<u64>;
pub(crate) struct DepSorter<'a> {
pub lockfile: &'a Lockfile,
}
impl<'a> DepSorter<'a> {
pub(crate) fn is_less_than(&self, l: DependencyID, r: DependencyID) -> bool {
let deps_buf = self.lockfile.buffers.dependencies.as_slice();
let string_buf = self.lockfile.buffers.string_bytes.as_slice();
let l_dep = &deps_buf[l as usize];
let r_dep = &deps_buf[r as usize];
match l_dep.behavior.cmp(r_dep.behavior) {
Ordering::Less => true,
Ordering::Greater => false,
Ordering::Equal => {
strings::order(l_dep.name.slice(string_buf), r_dep.name.slice(string_buf))
== Ordering::Less
}
}
}
}
#[derive(Default)]
pub struct Scripts {
pub preinstall: Vec<Box<[u8]>>,
pub install: Vec<Box<[u8]>>,
pub postinstall: Vec<Box<[u8]>>,
pub preprepare: Vec<Box<[u8]>>,
pub prepare: Vec<Box<[u8]>>,
pub postprepare: Vec<Box<[u8]>>,
}
impl Scripts {
pub const NAMES: [&'static str; 6] = [
"preinstall",
"install",
"postinstall",
"preprepare",
"prepare",
"postprepare",
];
pub fn hook_mut(&mut self, i: usize) -> &mut Vec<Box<[u8]>> {
match i {
0 => &mut self.preinstall,
1 => &mut self.install,
2 => &mut self.postinstall,
3 => &mut self.preprepare,
4 => &mut self.prepare,
5 => &mut self.postprepare,
_ => unreachable!(),
}
}
fn fields(&self) -> [(&'static str, &Vec<Box<[u8]>>); 6] {
[
(Self::NAMES[0], &self.preinstall),
(Self::NAMES[1], &self.install),
(Self::NAMES[2], &self.postinstall),
(Self::NAMES[3], &self.preprepare),
(Self::NAMES[4], &self.prepare),
(Self::NAMES[5], &self.postprepare),
]
}
pub fn has_any(&self) -> bool {
for (_, list) in self.fields() {
if !list.is_empty() {
return true;
}
}
false
}
pub fn count(&self) -> usize {
let mut res: usize = 0;
for (_, list) in self.fields() {
res += list.len();
}
res
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum LockfileFormat {
Text,
Binary,
}
impl LockfileFormat {
pub fn filename(self) -> &'static ZStr {
match self {
LockfileFormat::Text => zstr!("bun.lock"),
LockfileFormat::Binary => zstr!("bun.lockb"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum LoadStep {
OpenFile,
ReadFile,
ParseFile,
Migrating,
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum Migrated {
#[default]
None,
Npm,
Yarn,
Pnpm,
}
pub struct LoadResultErr {
pub step: LoadStep,
pub value: BunError,
pub lockfile_path: &'static ZStr,
pub format: LockfileFormat,
}
pub struct LoadResultOk<'a> {
pub lockfile: &'a mut Lockfile,
pub loaded_from_binary_lockfile: bool,
pub migrated: Migrated,
pub serializer_result: Serializer::SerializerLoadResult,
pub format: LockfileFormat,
}
pub enum LoadResult<'a> {
NotFound,
Err(LoadResultErr),
Ok(LoadResultOk<'a>),
}
impl<'a> LoadResult<'a> {
pub fn loaded_from_text_lockfile(&self) -> bool {
match self {
LoadResult::NotFound => false,
LoadResult::Err(err) => err.format == LockfileFormat::Text,
LoadResult::Ok(ok) => ok.format == LockfileFormat::Text,
}
}
pub fn loaded_from_binary_lockfile(&self) -> bool {
match self {
LoadResult::NotFound => false,
LoadResult::Err(err) => err.format == LockfileFormat::Binary,
LoadResult::Ok(ok) => ok.format == LockfileFormat::Binary,
}
}
pub fn migrated_from_npm(&self) -> bool {
match self {
LoadResult::Ok(ok) => ok.migrated == Migrated::Npm,
_ => false,
}
}
pub fn save_format(&self, options: &PackageManagerOptions) -> LockfileFormat {
match self {
LoadResult::NotFound => {
let save_text_lockfile = options.save_text_lockfile.unwrap_or(true);
if save_text_lockfile {
LockfileFormat::Text
} else {
LockfileFormat::Binary
}
}
LoadResult::Err(err) => {
if let Some(save_text_lockfile) = options.save_text_lockfile {
if save_text_lockfile {
return LockfileFormat::Text;
}
}
err.format
}
LoadResult::Ok(ok) => {
if let Some(save_text_lockfile) = options.save_text_lockfile {
if save_text_lockfile {
return LockfileFormat::Text;
}
if ok.migrated != Migrated::None {
return LockfileFormat::Binary;
}
}
if ok.migrated != Migrated::None {
return LockfileFormat::Text;
}
ok.format
}
}
}
pub fn choose_config_version(&self) -> (ConfigVersion, bool) {
match self {
LoadResult::NotFound | LoadResult::Err(_) => (ConfigVersion::CURRENT, true),
LoadResult::Ok(ok) => match ok.migrated {
Migrated::None => {
if let Some(config_version) = ok.lockfile.saved_config_version {
return (config_version, false);
}
(ConfigVersion::V0, true)
}
Migrated::Pnpm => (ConfigVersion::V1, true),
Migrated::Npm => (ConfigVersion::V0, true),
Migrated::Yarn => (ConfigVersion::V0, true),
},
}
}
bun_core::enum_unwrap!(pub LoadResult, Ok => fn ok / ok_mut -> LoadResultOk<'a>);
}
impl Lockfile {
pub fn is_empty(&self) -> bool {
self.packages.len() == 0
|| (self.packages.len() == 1 && self.packages.get(0).resolutions.len == 0)
}
pub fn load_from_cwd<'a, const ATTEMPT_LOADING_FROM_OTHER_LOCKFILE: bool>(
&'a mut self,
manager: Option<&mut PackageManager>,
log: &mut bun_ast::Log,
) -> LoadResult<'a> {
self.load_from_dir::<ATTEMPT_LOADING_FROM_OTHER_LOCKFILE>(Fd::cwd(), manager, log)
}
pub fn load_from_dir<'a, const ATTEMPT_LOADING_FROM_OTHER_LOCKFILE: bool>(
&'a mut self,
dir: Fd,
manager: Option<&mut PackageManager>,
log: &mut bun_ast::Log,
) -> LoadResult<'a> {
debug_assert!(Fs::INSTANCE_LOADED.load(core::sync::atomic::Ordering::Relaxed));
let mut lockfile_format = LockfileFormat::Text;
let file: File = 'file: {
match File::openat(dir, zstr!("bun.lock"), sys::O::RDONLY, 0) {
sys::Result::Ok(f) => break 'file f,
sys::Result::Err(text_open_err) => {
if text_open_err.errno != sys::SystemErrno::ENOENT as u16 {
return LoadResult::Err(LoadResultErr {
step: LoadStep::OpenFile,
value: BunError::from(text_open_err),
lockfile_path: zstr!("bun.lock"),
format: LockfileFormat::Text,
});
}
lockfile_format = LockfileFormat::Binary;
match File::openat(dir, zstr!("bun.lockb"), sys::O::RDONLY, 0) {
sys::Result::Ok(f) => break 'file f,
sys::Result::Err(binary_open_err) => {
if binary_open_err.errno != sys::SystemErrno::ENOENT as u16 {
return LoadResult::Err(LoadResultErr {
step: LoadStep::OpenFile,
value: BunError::from(binary_open_err),
lockfile_path: zstr!("bun.lockb"),
format: LockfileFormat::Binary,
});
}
if ATTEMPT_LOADING_FROM_OTHER_LOCKFILE {
if let Some(pm) = manager {
return migration::detect_and_load_other_lockfile(
self, dir, pm, log,
);
}
}
return LoadResult::NotFound;
}
}
}
}
};
let buf = match file.read_to_end() {
Ok(bytes) => bytes,
Err(e) => {
return LoadResult::Err(LoadResultErr {
step: LoadStep::ReadFile,
value: BunError::from(e),
lockfile_path: if lockfile_format == LockfileFormat::Text {
zstr!("bun.lock")
} else {
zstr!("bun.lockb")
},
format: lockfile_format,
});
}
};
if lockfile_format == LockfileFormat::Text {
let source = bun_ast::Source::init_path_string_owned(b"bun.lock", buf);
initialize_store();
let bump = bun_alloc::Arena::new();
let json = match JSON::parse_package_json_utf8(&source, log, &bump) {
Ok(j) => j,
Err(e) => {
return LoadResult::Err(LoadResultErr {
step: LoadStep::ParseFile,
value: e,
lockfile_path: zstr!("bun.lock"),
format: lockfile_format,
});
}
};
if let Err(e) =
TextLockfile::parse_into_binary_lockfile(self, json, &source, log, manager)
{
if matches!(e, TextLockfile::ParseError::OutOfMemory) {
bun_core::out_of_memory();
}
return LoadResult::Err(LoadResultErr {
step: LoadStep::ParseFile,
value: BunError::from(e),
lockfile_path: zstr!("bun.lock"),
format: lockfile_format,
});
}
bun_core::analytics::Features::text_lockfile_inc();
return LoadResult::Ok(LoadResultOk {
lockfile: self,
serializer_result: Serializer::SerializerLoadResult::default(),
loaded_from_binary_lockfile: false,
migrated: Migrated::None,
format: lockfile_format,
});
}
self.load_from_bytes(manager, buf, log)
}
pub fn load_from_bytes<'a>(
&'a mut self,
pm: Option<&mut PackageManager>,
buf: Vec<u8>,
log: &mut bun_ast::Log,
) -> LoadResult<'a> {
let mut stream = Stream::new(buf);
self.format = FormatVersion::current();
self.scripts = Scripts::default();
self.trusted_dependencies = None;
self.workspace_paths = NameHashMap::default();
self.workspace_versions = VersionHashMap::default();
self.overrides = OverrideMap::default();
self.catalogs = CatalogMap::default();
self.patched_dependencies = PatchedDependenciesMap::default();
let load_result = match Serializer::load(self, &mut stream, log, pm) {
Ok(r) => r,
Err(e) => {
return LoadResult::Err(LoadResultErr {
step: LoadStep::ParseFile,
value: e,
lockfile_path: zstr!("bun.lockb"),
format: LockfileFormat::Binary,
});
}
};
if cfg!(debug_assertions) {
self.verify_data().expect("lockfile data is corrupt");
}
LoadResult::Ok(LoadResultOk {
lockfile: self,
serializer_result: load_result,
loaded_from_binary_lockfile: true,
migrated: Migrated::None,
format: LockfileFormat::Binary,
})
}
pub fn is_resolved_dependency_disabled(
&self,
dep_id: DependencyID,
features: Features,
meta: &package::Meta,
cpu: Npm::Architecture,
os: Npm::OperatingSystem,
) -> bool {
if meta.is_disabled(cpu, os) {
return true;
}
let dep = &self.buffers.dependencies[dep_id as usize];
dep.behavior.is_bundled() || !dep.behavior.is_enabled(features)
}
pub fn maybe_clone_filtering_root_packages<'a>(
old: &'a mut Lockfile,
manager: &'a mut PackageManager,
features: Features,
exact_versions: bool,
log_level: LogLevel,
) -> Result<Cleaned<'a>, BunError> {
let old_packages = old.packages.slice();
let old_dependencies_lists = old_packages.items_dependencies();
let old_resolutions_lists = old_packages.items_resolutions();
let old_resolutions = old_packages.items_resolution();
let mut any_changes = false;
let end: PackageID = old.packages.len() as PackageID;
for package_id in 0..end as usize {
if package_id != 0 && old_resolutions[package_id].tag != ResolutionTag::Workspace {
continue;
}
let old_workspace_dependencies_list = old_dependencies_lists[package_id];
let old_workspace_resolutions_list = old_resolutions_lists[package_id];
let old_workspace_dependencies =
old_workspace_dependencies_list.get(old.buffers.dependencies.as_slice());
let old_workspace_resolutions =
old_workspace_resolutions_list.mut_(old.buffers.resolutions.as_mut_slice());
debug_assert_eq!(
old_workspace_dependencies.len(),
old_workspace_resolutions.len()
);
for (dependency, resolution) in old_workspace_dependencies
.iter()
.zip(old_workspace_resolutions.iter_mut())
{
if !dependency.behavior.is_enabled(features) && *resolution < end {
*resolution = invalid_package_id;
any_changes = true;
}
}
}
if !any_changes {
return Ok(Cleaned::Same(old));
}
old.clean(manager, &mut [], exact_versions, log_level)
.map(Cleaned::New)
}
fn preprocess_update_requests(
old: &mut Lockfile,
manager: &mut PackageManager,
updates: &mut [UpdateRequest],
exact_versions: bool,
) -> Result<(), BunError> {
let workspace_package_id = manager
.root_package_id
.get(old, manager.workspace_name_hash);
let root_deps_list: DependencySlice =
old.packages.items_dependencies()[workspace_package_id as usize];
if (root_deps_list.off as usize) < old.buffers.dependencies.len() {
let mut string_builder = string_builder!(old);
{
let root_deps: &[Dependency] =
root_deps_list.get(old.buffers.dependencies.as_slice());
let old_resolutions_list =
old.packages.items_resolutions()[workspace_package_id as usize];
let old_resolutions: &[PackageID] =
old_resolutions_list.get(old.buffers.resolutions.as_slice());
let resolutions_of_yore: &[Resolution] = old.packages.items_resolution();
let packages_len = old.packages.len();
for update in updates.iter() {
if update.package_id == invalid_package_id {
debug_assert_eq!(root_deps.len(), old_resolutions.len());
for (dep, &old_resolution) in root_deps.iter().zip(old_resolutions.iter()) {
if dep.name_hash == SemverStringBuilder::string_hash(update.name) {
if old_resolution as usize >= packages_len {
continue;
}
let res = resolutions_of_yore[old_resolution as usize];
if res.tag != ResolutionTag::Npm
|| update.version.tag != dependency::Tag::DistTag
{
continue;
}
let npm_ver = res.npm().version;
let len = bun_core::fmt::count(format_args!(
"{}{}",
if exact_versions { "" } else { "^" },
npm_ver.fmt(string_builder.string_bytes.as_slice()),
));
if len >= SemverString::MAX_INLINE_LEN {
string_builder.cap += len;
}
}
}
}
}
}
string_builder.allocate()?;
{
let mut temp_buf = [0u8; 513];
let root_deps: &mut [Dependency] =
root_deps_list.mut_(old.buffers.dependencies.as_mut_slice());
let old_resolutions_list_lists = old.packages.items_resolutions();
let old_resolutions_list =
old_resolutions_list_lists[workspace_package_id as usize];
let old_resolutions: &[PackageID] =
old_resolutions_list.get(old.buffers.resolutions.as_slice());
let resolutions_of_yore: &[Resolution] = old.packages.items_resolution();
let packages_len = old.packages.len();
for update in updates.iter_mut() {
if update.package_id == invalid_package_id {
debug_assert_eq!(root_deps.len(), old_resolutions.len());
for (dep, &old_resolution) in
root_deps.iter_mut().zip(old_resolutions.iter())
{
if dep.name_hash == SemverStringBuilder::string_hash(update.name) {
if old_resolution as usize >= packages_len {
continue;
}
let res = resolutions_of_yore[old_resolution as usize];
if res.tag != ResolutionTag::Npm
|| update.version.tag != dependency::Tag::DistTag
{
continue;
}
let npm_ver = res.npm().version;
let buf = {
let mut cursor: &mut [u8] = &mut temp_buf[..];
let start_len = cursor.len();
if write!(
cursor,
"{}{}",
if exact_versions { "" } else { "^" },
npm_ver.fmt(string_builder.string_bytes.as_slice()),
)
.is_err()
{
break;
}
let written = start_len - cursor.len();
&temp_buf[..written]
};
let external_version = string_builder.append::<ExternalString>(buf);
let sliced = external_version
.value
.sliced(string_builder.string_bytes.as_slice());
dep.version = dependency::parse(
dep.name,
dep.name_hash,
sliced.slice,
&sliced,
None,
&mut *manager,
)
.unwrap_or_default();
}
}
}
update.e_string = None;
}
}
string_builder.clamp();
}
Ok(())
}
pub fn clean(
&mut self,
manager: &mut PackageManager,
updates: &mut [UpdateRequest],
exact_versions: bool,
log_level: LogLevel,
) -> Result<Box<Lockfile>, BunError> {
let mut log = bun_ast::Log::init();
self.clean_with_logger(manager, updates, &mut log, exact_versions, log_level)
}
pub fn resolve_catalog_dependency(&self, dep: &Dependency) -> Option<DependencyVersion> {
if dep.version.tag != dependency::Tag::Catalog {
return Some(dep.version.clone());
}
let catalog_name = *dep.version.catalog();
let catalog_dep = self.catalogs.get(self, catalog_name, dep.name)?;
Some(catalog_dep.version)
}
pub fn is_workspace_root_dependency(&self, id: DependencyID) -> bool {
self.packages.items_dependencies()[0].contains(id)
}
pub fn is_root_dependency(&self, manager: &mut PackageManager, id: DependencyID) -> bool {
let root_id = manager
.root_package_id
.get(self, manager.workspace_name_hash);
self.packages.items_dependencies()[root_id as usize].contains(id)
}
pub fn is_workspace_dependency(&self, id: DependencyID) -> bool {
self.get_workspace_pkg_if_workspace_dep(id) != invalid_package_id
}
pub fn get_workspace_pkg_if_workspace_dep(&self, id: DependencyID) -> PackageID {
let packages = self.packages.slice();
let resolutions = packages.items_resolution();
let dependencies_lists = packages.items_dependencies();
for (pkg_id, (resolution, dependencies)) in resolutions
.iter()
.zip(dependencies_lists.iter())
.enumerate()
{
if resolution.tag != ResolutionTag::Workspace && resolution.tag != ResolutionTag::Root {
continue;
}
if dependencies.contains(id) {
return PackageID::try_from(pkg_id).expect("int cast");
}
}
invalid_package_id
}
pub fn is_workspace_tree_id(&self, id: tree::Id) -> bool {
id == 0
|| self.buffers.dependencies[self.buffers.trees[id as usize].dependency_id as usize]
.behavior
.is_workspace()
}
pub fn get_workspace_package_id(
&self,
workspace_name_hash: Option<PackageNameHash>,
) -> PackageID {
if let Some(workspace_name_hash_) = workspace_name_hash {
let packages = self.packages.slice();
let name_hashes = packages.items_name_hash();
let resolutions = packages.items_resolution();
for (i, (res, name_hash)) in resolutions.iter().zip(name_hashes.iter()).enumerate() {
if res.tag == ResolutionTag::Workspace && *name_hash == workspace_name_hash_ {
return PackageID::try_from(i).expect("int cast");
}
}
0
} else {
0
}
}
#[inline(never)]
pub fn clean_with_logger(
&mut self,
manager: &mut PackageManager,
updates: &mut [UpdateRequest],
log: &mut bun_ast::Log,
exact_versions: bool,
log_level: LogLevel,
) -> Result<Box<Lockfile>, BunError> {
let old: &mut Lockfile = self;
let timer: Option<Timer> = if log_level.is_verbose() {
Some(clean_verbose_timer_start()?)
} else {
None
};
let old_trusted_dependencies = old.trusted_dependencies.take();
let old_scripts = core::mem::take(&mut old.scripts);
manager.ensure_preinstall_state_list_capacity(old.packages.len());
let preinstall_state = &mut manager.preinstall_state;
let old_preinstall_state = preinstall_state.clone();
preinstall_state.fill(Install::PreinstallState::Unknown);
if !updates.is_empty() {
clean_preprocess_update_requests_cold(old, manager, updates, exact_versions)?;
}
let mut new: Box<Lockfile> = Box::default();
new.string_pool
.ensure_total_capacity(old.string_pool.capacity())?;
new.package_index
.ensure_total_capacity(old.package_index.capacity())?;
new.packages.ensure_total_capacity(old.packages.len())?;
new.buffers.preallocate(&old.buffers)?;
new.patched_dependencies
.ensure_total_capacity(old.patched_dependencies.count())?;
let queued = old.scratch.dependency_list_queue.readable_length();
old.scratch.dependency_list_queue.discard(queued);
{
let old_buf = old.buffers.string_bytes.as_slice();
let (mut builder, lf) = new.string_builder_split();
old.overrides.count(old_buf, &mut builder);
old.catalogs.count(old_buf, &mut builder);
builder.allocate()?;
*lf.overrides = old.overrides.clone(manager, old_buf, &mut builder)?;
*lf.catalogs = old.catalogs.clone(manager, old_buf, &mut builder)?;
}
let root = old.root_package().ok_or_else(|| err!("NoPackage"))?;
let mut package_id_mapping = vec![invalid_package_id; old.packages.len()];
let clone_queue_ = PendingResolutions::new();
let mut cloner = Cloner {
old: &mut *old,
lockfile: &mut *new,
mapping: &mut package_id_mapping,
clone_queue: clone_queue_,
log,
old_preinstall_state,
manager: &mut *manager,
trees: tree::List::default(),
trees_count: 1,
};
let _ = root.clone(&mut cloner)?;
{
let old = &mut *cloner.old;
let new = &mut *cloner.lockfile;
if old.workspace_paths.count() > 0 || old.workspace_versions.count() > 0 {
new.workspace_paths
.ensure_total_capacity(old.workspace_paths.count())?;
new.workspace_versions
.ensure_total_capacity(old.workspace_versions.count())?;
let mut workspace_paths_builder = string_builder!(new);
{
let string_buf = old.buffers.string_bytes.as_slice();
old.workspace_paths.sort(|_keys, values, a, b| {
let left = values[a];
let right = values[b];
strings::order(left.slice(string_buf), right.slice(string_buf))
== Ordering::Less
});
}
let old_string_buf = old.buffers.string_bytes.as_slice();
for path in old.workspace_paths.values() {
workspace_paths_builder.count(path.slice(old_string_buf));
}
let versions: &[Semver::Version] = old.workspace_versions.values();
for version in versions {
version.count(old_string_buf, &mut workspace_paths_builder);
}
workspace_paths_builder.allocate()?;
unsafe {
new.workspace_paths
.set_entries_len(old.workspace_paths.count())
};
debug_assert_eq!(
old.workspace_paths.values().len(),
new.workspace_paths.values().len()
);
for (src, dest) in old
.workspace_paths
.values()
.iter()
.zip(new.workspace_paths.values_mut().iter_mut())
{
*dest =
workspace_paths_builder.append::<SemverString>(src.slice(old_string_buf));
}
new.workspace_paths
.keys_mut()
.copy_from_slice(old.workspace_paths.keys());
new.workspace_versions
.ensure_total_capacity(old.workspace_versions.count())?;
unsafe {
new.workspace_versions
.set_entries_len(old.workspace_versions.count())
};
for (src, dest) in versions
.iter()
.zip(new.workspace_versions.values_mut().iter_mut())
{
*dest = src.append(old_string_buf, &mut workspace_paths_builder);
}
new.workspace_versions
.keys_mut()
.copy_from_slice(old.workspace_versions.keys());
workspace_paths_builder.clamp();
new.workspace_versions.re_index()?;
new.workspace_paths.re_index()?;
}
}
cloner.flush()?;
drop(cloner);
new.trusted_dependencies = old_trusted_dependencies;
new.scripts = old_scripts;
new.meta_hash = old.meta_hash;
if old.patched_dependencies.count() > 0 {
clean_migrate_patched_dependencies_cold(old, &mut new)?;
}
if !updates.is_empty() {
let string_buf = new.buffers.string_bytes.as_slice();
let string_buf_ptr = bun_ptr::RawSlice::new(string_buf);
let slice = new.packages.slice();
let workspace_package_id = manager
.root_package_id
.get(&new, manager.workspace_name_hash);
let dep_list = slice.items_dependencies()[workspace_package_id as usize];
let res_list = slice.items_resolutions()[workspace_package_id as usize];
let workspace_deps: &[Dependency] = dep_list.get(new.buffers.dependencies.as_slice());
let resolved_ids: &[PackageID] = res_list.get(new.buffers.resolutions.as_slice());
'request_updated: for update in updates.iter_mut() {
if update.package_id == invalid_package_id {
debug_assert_eq!(resolved_ids.len(), workspace_deps.len());
for (&package_id, dep) in resolved_ids.iter().zip(workspace_deps.iter()) {
if update.matches(dep, string_buf) {
if package_id as usize > new.packages.len() {
continue;
}
update.version_buf = string_buf_ptr;
update.version = dep.version.clone();
update.package_id = package_id;
continue 'request_updated;
}
}
}
}
}
if log_level.is_verbose() {
clean_verbose_report_cold(old, &new, timer.as_ref());
}
Ok(new)
}
}
#[cold]
#[inline(never)]
fn clean_preprocess_update_requests_cold(
old: &mut Lockfile,
manager: &mut PackageManager,
updates: &mut [UpdateRequest],
exact_versions: bool,
) -> Result<(), BunError> {
Lockfile::preprocess_update_requests(old, manager, updates, exact_versions)
}
#[cold]
#[inline(never)]
fn clean_verbose_timer_start() -> Result<Timer, BunError> {
Timer::start()
}
#[cold]
#[inline(never)]
fn clean_verbose_report_cold(old: &Lockfile, new: &Lockfile, timer: Option<&Timer>) {
Output::pretty_errorln(format_args!(
"Clean lockfile: {} packages -> {} packages in {}\n",
old.packages.len(),
new.packages.len(),
bun_core::fmt::fmt_duration_one_decimal(timer.unwrap().read()),
));
}
#[cold]
#[inline(never)]
fn clean_migrate_patched_dependencies_cold(
old: &Lockfile,
new: &mut Lockfile,
) -> Result<(), BunError> {
let mut builder = string_builder!(new);
for patched_dep in old.patched_dependencies.values() {
builder.count(patched_dep.path.slice(old.buffers.string_bytes.as_slice()));
}
builder.allocate()?;
for (k, v) in old
.patched_dependencies
.keys()
.iter()
.zip(old.patched_dependencies.values().iter())
{
debug_assert!(!v.patchfile_hash_is_null);
let mut patchdep = *v;
patchdep.path = builder
.append::<SemverString>(patchdep.path.slice(old.buffers.string_bytes.as_slice()));
new.patched_dependencies.put(*k, patchdep)?;
}
Ok(())
}
pub struct MetaHashFormatter<'a> {
pub meta_hash: &'a MetaHash,
}
impl<'a> fmt::Display for MetaHashFormatter<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let remain: &[u8] = &self.meta_hash[..];
write!(
f,
"{}-{}-{}-{}",
bun_core::fmt::HexBytes::<false>(&remain[0..8]),
bun_core::fmt::HexBytes::<true>(&remain[8..16]),
bun_core::fmt::HexBytes::<false>(&remain[16..24]),
bun_core::fmt::HexBytes::<true>(&remain[24..32]),
)
}
}
impl Lockfile {
pub fn fmt_meta_hash(&self) -> MetaHashFormatter<'_> {
MetaHashFormatter {
meta_hash: &self.meta_hash,
}
}
}
pub struct Cloner<'a> {
pub clone_queue: PendingResolutions,
pub lockfile: &'a mut Lockfile,
pub old: &'a mut Lockfile,
pub mapping: &'a mut [PackageID],
pub trees: tree::List,
pub trees_count: u32,
pub log: &'a mut bun_ast::Log,
pub old_preinstall_state: Vec<Install::PreinstallState>,
pub manager: &'a mut PackageManager,
}
impl<'a> Cloner<'a> {
pub fn flush(&mut self) -> Result<(), BunError> {
let max_package_id = self.old.packages.len();
while let Some(to_clone) = self.clone_queue.pop() {
let mapping = self.mapping[to_clone.old_resolution as usize];
if (mapping as usize) < max_package_id {
self.lockfile.buffers.resolutions[to_clone.resolve_id as usize] = mapping;
continue;
}
let old_package = *self.old.packages.get(to_clone.old_resolution as usize);
let new_id = old_package.clone(self)?;
self.lockfile.buffers.resolutions[to_clone.resolve_id as usize] = new_id;
}
self.manager
.clear_cached_items_depending_on_lockfile_buffer();
if self.lockfile.packages.len() != 0 {
self.lockfile.resolve(self.log)?;
}
if self.lockfile.packages.capacity() != self.lockfile.packages.len()
&& self.lockfile.packages.len() > 0
{
self.lockfile
.packages
.shrink_and_free(self.lockfile.packages.len());
}
Ok(())
}
}
impl Lockfile {
pub fn resolve(&mut self, log: &mut bun_ast::Log) -> Result<(), tree::SubtreeError> {
self.hoist::<{ tree::BuilderMethod::Resolvable }>(log, None, true, &[], None)
}
pub fn filter(
&mut self,
log: &mut bun_ast::Log,
manager: &mut PackageManager,
install_root_dependencies: bool,
workspace_filters: &[WorkspaceFilter],
packages_to_install: Option<&[PackageID]>,
) -> Result<(), tree::SubtreeError> {
self.hoist::<{ tree::BuilderMethod::Filter }>(
log,
Some(manager),
install_root_dependencies,
workspace_filters,
packages_to_install,
)
}
pub fn hoist<const METHOD: tree::BuilderMethod>(
&mut self,
log: &mut bun_ast::Log,
manager: Option<&PackageManager>,
install_root_dependencies: bool,
workspace_filters: &[WorkspaceFilter],
packages_to_install: Option<&[PackageID]>,
) -> Result<(), tree::SubtreeError> {
let slice = self.packages.slice();
let lockfile_ref = bun_ptr::ParentRef::<Lockfile>::new(&*self);
let mut builder = tree::Builder::<METHOD> {
queue: tree::TreeFiller::init(),
resolution_lists: slice.items_resolutions(),
resolutions: self.buffers.resolutions.as_mut_slice(),
dependencies: self.buffers.dependencies.as_slice(),
log,
lockfile: lockfile_ref,
manager,
install_root_dependencies,
workspace_filters,
packages_to_install,
pending_optional_peers: Default::default(),
list: Default::default(),
sort_buf: Default::default(),
};
Tree::default().process_subtree(tree::ROOT_DEP_ID, tree::INVALID_ID, &mut builder)?;
while let Some(item) = builder.queue.read_item() {
use tree::BuilderEntryColumns as _;
let tree = builder.list.items_tree()[item.tree_id as usize];
tree.process_subtree(item.dependency_id, item.hoist_root_id, &mut builder)?;
}
let cleaned = builder.clean()?;
self.buffers.trees = cleaned.trees;
self.buffers.hoisted_dependencies = cleaned.dep_ids;
Ok(())
}
}
#[derive(Clone, Copy)]
pub struct PendingResolution {
pub old_resolution: PackageID,
pub resolve_id: PackageID,
pub parent: PackageID,
}
pub(crate) type PendingResolutions = Vec<PendingResolution>;
impl Lockfile {
pub fn fetch_necessary_package_metadata_after_yarn_or_pnpm_migration<
const UPDATE_OS_CPU: bool,
>(
&mut self,
manager: &mut PackageManager,
) -> Result<(), AllocError> {
if populate_manifest_cache::populate_manifest_cache(
manager,
populate_manifest_cache::Packages::All,
)
.is_err()
{
return Ok(());
}
manager.network_dedupe_map.clear();
let cache_ctx = manager.manifest_disk_cache_ctx();
let manager_ptr: *mut PackageManager = manager;
let mgr_ref = bun_ptr::ParentRef::<PackageManager>::from(
core::ptr::NonNull::new(manager_ptr).expect("derived from &mut, non-null"),
);
let mut pkgs = self.packages.slice();
let len = pkgs.len();
let self::package::PackageColumnsMut {
name: pkg_names,
name_hash: pkg_name_hashes,
resolution: pkg_resolutions,
bin: pkg_bins,
meta,
..
} = pkgs.split_mut();
let pkg_metas: &mut [self::package::meta::Meta] =
if UPDATE_OS_CPU { meta } else { &mut [] };
for i in 0..len {
let pkg_name = pkg_names[i];
let pkg_name_hash = pkg_name_hashes[i];
let pkg_res = pkg_resolutions[i];
let pkg_bin = &mut pkg_bins[i];
match pkg_res.tag {
ResolutionTag::Npm => {
let scope = mgr_ref.options.scope_for_package_name(
pkg_name.slice(self.buffers.string_bytes.as_slice()),
);
let Some(manifest) = unsafe { &mut (*manager_ptr).manifests }.by_name_hash(
cache_ctx,
scope,
pkg_name_hash,
Install::ManifestLoad::LoadFromMemoryFallbackToDisk,
false,
) else {
continue;
};
let npm_ver = pkg_res.npm().version;
let Some(pkg) = manifest.find_by_version(npm_ver) else {
continue;
};
let mut builder = string_builder!(self);
let mut bin_extern_strings_count: u32 = 0;
bin_extern_strings_count += pkg.package.bin.count(
&manifest.string_buf,
&manifest.extern_strings_bin_entries,
&mut builder,
);
builder.allocate()?;
let extern_strings_list = &mut self.buffers.extern_strings;
let start = extern_strings_list.len();
bun_core::vec::grow_default(
extern_strings_list,
bin_extern_strings_count as usize,
);
let new_len = extern_strings_list.len();
let extern_strings_slice = &mut extern_strings_list[start..new_len];
*pkg_bin = pkg.package.bin.clone_with_buffers(
&manifest.string_buf,
&manifest.extern_strings_bin_entries,
start as u32,
extern_strings_slice,
&mut builder,
);
builder.clamp();
if UPDATE_OS_CPU {
let pkg_meta = &mut pkg_metas[i];
if pkg_meta.os == Npm::OperatingSystem::ALL {
pkg_meta.os = pkg.package.os;
}
if pkg_meta.arch == Npm::Architecture::ALL {
pkg_meta.arch = pkg.package.cpu;
}
}
}
_ => {}
}
}
Ok(())
}
}
pub struct Printer<'a> {
pub lockfile: &'a Lockfile,
pub options: &'a PackageManagerOptions,
pub successfully_installed: Option<&'a DynamicBitSet>,
pub updates: &'a [UpdateRequest],
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum PrinterFormat {
Yarn,
}
pub mod printer {
pub use super::printer_mods::tree_printer as Tree;
pub use super::printer_mods::yarn as Yarn;
}
impl<'a> Printer<'a> {
#[cold]
pub fn print(
log: &mut bun_ast::Log,
input_lockfile_path: &[u8],
format: PrinterFormat,
) -> Result<(), BunError> {
let path = &input_lockfile_path[..input_lockfile_path.len().min(MAX_PATH_BYTES)];
let mut lockfile_path_buf1 = PathBuffer::uninit();
let mut lockfile_path_buf2 = PathBuffer::uninit();
let mut lockfile_path: &ZStr = ZStr::EMPTY;
let mut path_in_buf2 = false;
if !bun_paths::is_absolute(path) {
let cwd_len = bun_sys::getcwd(&mut lockfile_path_buf1[..])?;
let parts = [path];
let cwd = &lockfile_path_buf1[..cwd_len];
let lockfile_path__len = resolve_path::join_abs_string_buf::<platform::Auto>(
cwd,
&mut lockfile_path_buf2.0,
&parts,
)
.len();
lockfile_path_buf2[lockfile_path__len] = 0;
lockfile_path =
unsafe { ZStr::from_raw(lockfile_path_buf2.as_ptr(), lockfile_path__len) };
path_in_buf2 = true;
} else if !path.is_empty() {
lockfile_path_buf1[..path.len()].copy_from_slice(path);
lockfile_path_buf1[path.len()] = 0;
lockfile_path = unsafe { ZStr::from_raw(lockfile_path_buf1.as_ptr(), path.len()) };
}
if !lockfile_path.as_bytes().is_empty() && lockfile_path.as_bytes()[0] == SEP {
let dir = bun_paths::dirname(lockfile_path.as_bytes()).unwrap_or(SEP_STR.as_bytes());
let dir_z = if path_in_buf2 {
resolve_path::z(dir, &mut lockfile_path_buf1)
} else {
resolve_path::z(dir, &mut lockfile_path_buf2)
};
let _ = sys::chdir(dir_z);
}
let _ = FileSystem::init(None)?;
let mut lockfile = Box::<Lockfile>::default();
let load_from_disk = lockfile.load_from_cwd::<false>(None, log);
match load_from_disk {
crate::lockfile::LoadResult::Err(cause) => {
match cause.step {
crate::lockfile::LoadStep::OpenFile => Output::pretty_errorln(format_args!(
"<r><red>error<r> opening lockfile:<r> {}.",
cause.value.name()
)),
crate::lockfile::LoadStep::ParseFile => Output::pretty_errorln(format_args!(
"<r><red>error<r> parsing lockfile:<r> {}",
cause.value.name()
)),
crate::lockfile::LoadStep::ReadFile => Output::pretty_errorln(format_args!(
"<r><red>error<r> reading lockfile:<r> {}",
cause.value.name()
)),
crate::lockfile::LoadStep::Migrating => Output::pretty_errorln(format_args!(
"<r><red>error<r> while migrating lockfile:<r> {}",
cause.value.name()
)),
}
if log.errors > 0 {
let ew: *mut bun_core::io::Writer = Output::error_writer();
log.print(ew)?;
}
Global::crash();
}
crate::lockfile::LoadResult::NotFound => {
Output::pretty_errorln(format_args!(
"<r><red>lockfile not found:<r> {}",
bun_core::fmt::QuotedFormatter {
text: lockfile_path.as_bytes()
},
));
Global::crash();
}
crate::lockfile::LoadResult::Ok(_) => {}
}
let writer = Output::writer_buffered();
match Self::print_with_lockfile(&lockfile, format, writer) {
Ok(()) => {}
Err(e) if e == err!("OutOfMemory") => bun_core::out_of_memory(),
Err(e) if e == err!("BrokenPipe") || e == err!("WriteFailed") => return Ok(()),
Err(e) => return Err(e),
}
Output::flush();
Ok(())
}
pub fn print_with_lockfile<W: bun_io::Write>(
lockfile: &Lockfile,
format: PrinterFormat,
writer: W,
) -> Result<(), BunError> {
let fs = FileSystem::instance();
let mut options = PackageManagerOptions {
max_concurrent_lifecycle_scripts: 1,
..Default::default()
};
let top_level_dir = fs.top_level_dir;
let entries_option = fs.fs.read_directory(top_level_dir, None, 0, true)?;
let entries: &mut Fs::DirEntry = match entries_option {
Fs::EntriesOption::Entries(e) => &mut **e,
Fs::EntriesOption::Err(e) => return Err(e.canonical_error),
};
let mut map = DotEnv::Map::init();
let mut env_loader = DotEnv::Loader::init(&mut map);
env_loader.quiet = true;
env_loader.load_process()?;
env_loader.load(
&*entries,
&[] as &[&[u8]],
DotEnv::DotEnvFileSuffix::Production,
false,
)?;
let mut log = bun_ast::Log::init();
options.load(
&mut log,
&mut env_loader,
None,
None,
crate::Subcommand::Install,
)?;
let mut printer = Printer {
lockfile,
options: &options,
successfully_installed: None,
updates: &[],
};
let mut writer = writer;
match format {
PrinterFormat::Yarn => {
printer::Yarn::print(&mut printer, &mut writer)?;
}
}
Ok(())
}
}
impl Lockfile {
pub fn verify_data(&self) -> Result<(), BunError> {
debug_assert!(self.format == FormatVersion::current());
let mut i: usize = 0;
while i < self.packages.len() {
let package: Package = *self.packages.get(i);
debug_assert!(self.str(&package.name).len() == package.name.len());
debug_assert!(
SemverStringBuilder::string_hash(self.str(&package.name)) == package.name_hash
);
debug_assert!(
package
.dependencies
.get(self.buffers.dependencies.as_slice())
.len()
== package.dependencies.len as usize
);
debug_assert!(
package
.resolutions
.get(self.buffers.resolutions.as_slice())
.len()
== package.resolutions.len as usize
);
debug_assert!(
package
.resolutions
.get(self.buffers.resolutions.as_slice())
.len()
== package.dependencies.len as usize
);
let dependencies = package
.dependencies
.get(self.buffers.dependencies.as_slice());
for dependency in dependencies {
debug_assert!(self.str(&dependency.name).len() == dependency.name.len());
debug_assert!(
SemverStringBuilder::string_hash(self.str(&dependency.name))
== dependency.name_hash
);
}
i += 1;
}
Ok(())
}
pub fn save_to_disk(&mut self, load_result: &LoadResult<'_>, options: &PackageManagerOptions) {
let save_format = load_result.save_format(options);
if cfg!(debug_assertions) {
if let Err(e) = self.verify_data() {
Output::pretty_errorln(format_args!(
"<r><red>error:<r> failed to verify lockfile: {}",
e.name()
));
Global::crash();
}
debug_assert!(Fs::INSTANCE_LOADED.load(core::sync::atomic::Ordering::Relaxed));
}
let bytes: Vec<u8> = 'bytes: {
if save_format == LockfileFormat::Text {
let mut writer_buf: Vec<u8> = Vec::new();
if let Err(_e) = TextLockfile::Stringifier::save_from_binary(
self,
load_result,
options,
&mut writer_buf,
) {
bun_core::out_of_memory();
}
break 'bytes writer_buf;
}
let mut bytes: Vec<u8> = Vec::new();
let mut total_size: usize = 0;
let mut end_pos: usize = 0;
if let Err(e) =
Serializer::save(self, options, &mut bytes, &mut total_size, &mut end_pos)
{
Output::err(e, "failed to serialize lockfile", format_args!(""));
Global::crash();
}
if bytes.len() >= end_pos {
bytes[end_pos..end_pos + core::mem::size_of::<usize>()]
.copy_from_slice(&total_size.to_ne_bytes());
}
break 'bytes bytes;
};
let mut tmpname_buf = [0u8; 512];
let mut base64_bytes = [0u8; 8];
bun_core::csprng(&mut base64_bytes);
let tmpname: &ZStr = {
let mut cursor: &mut [u8] = &mut tmpname_buf[..];
let start_len = cursor.len();
if save_format == LockfileFormat::Text {
write!(
cursor,
".lock-{}.tmp\0",
bun_core::fmt::HexBytes::<true>(&base64_bytes)
)
.expect("unreachable");
} else {
write!(
cursor,
".lockb-{}.tmp\0",
bun_core::fmt::HexBytes::<true>(&base64_bytes)
)
.expect("unreachable");
}
let written = start_len - cursor.len();
ZStr::from_buf(&tmpname_buf, written - 1)
};
let file = match File::openat(Fd::cwd(), tmpname, sys::O::CREAT | sys::O::WRONLY, 0o777) {
sys::Result::Err(e) => {
Output::err(
e,
"failed to create temporary file to save lockfile",
format_args!(""),
);
Global::crash();
}
sys::Result::Ok(f) => f,
};
match file.write_all(&bytes) {
sys::Result::Err(e) => {
let _ = file.close(); let _ = sys::unlink(tmpname);
Output::err(e, "failed to write lockfile", format_args!(""));
Global::crash();
}
sys::Result::Ok(()) => {}
}
#[cfg(unix)]
{
let mut filemode: sys::Mode = 0o755;
if save_format == LockfileFormat::Text {
filemode = 0o644;
}
match sys::fchmod(file.handle, filemode) {
sys::Result::Err(e) => {
let _ = file.close(); let _ = sys::unlink(tmpname);
Output::err(e, "failed to change lockfile permissions", format_args!(""));
Global::crash();
}
sys::Result::Ok(()) => {}
}
}
if let Err(e) = file.close_and_move_to(tmpname, save_format.filename()) {
bun_core::handle_error_return_trace(e);
let _ = sys::unlink(tmpname);
Output::err(
e,
"Failed to replace old lockfile with new lockfile on disk",
format_args!(""),
);
Global::crash();
}
}
pub fn root_package(&self) -> Option<Package> {
if self.packages.len() == 0 {
return None;
}
Some(*self.packages.get(0))
}
#[inline]
pub fn str<'a, T: bun_semver::Slicable>(&'a self, slicable: &'a T) -> &'a [u8] {
slicable.slice(self.buffers.string_bytes.as_slice())
}
#[inline]
pub fn str_detached<'a, T: bun_semver::Slicable>(&self, slicable: &T) -> &'a [u8] {
unsafe { bun_ptr::detach_lifetime(slicable.slice(self.buffers.string_bytes.as_slice())) }
}
pub fn init_empty(&mut self) {
*self = Self::init_empty_value();
}
}
impl Default for Lockfile {
#[inline]
fn default() -> Self {
Self::init_empty_value()
}
}
impl Lockfile {
pub fn init_empty_value() -> Self {
Lockfile {
format: FormatVersion::current(),
text_lockfile_version: bun_lock::Version::current(),
packages: Default::default(),
buffers: Buffers::default(),
package_index: PackageIndexMap::default(),
string_pool: StringPool::default(),
scratch: Scratch::init(),
scripts: Scripts::default(),
trusted_dependencies: None,
workspace_paths: NameHashMap::default(),
workspace_versions: VersionHashMap::default(),
overrides: OverrideMap::default(),
catalogs: CatalogMap::default(),
meta_hash: ZERO_HASH,
patched_dependencies: PatchedDependenciesMap::default(),
saved_config_version: None,
loaded_package_count: 0,
exact_pinned: Vec::new(),
}
}
#[inline]
pub fn mark_loaded_packages(&mut self) {
self.loaded_package_count = self.packages.len() as PackageID;
}
#[inline]
pub fn mark_exact_pin(&mut self, id: PackageID) {
let i = id as usize;
if self.exact_pinned.len() <= i {
self.exact_pinned.resize(i + 1, false);
}
self.exact_pinned[i] = true;
}
pub fn get_package_id(
&self,
name_hash: u64,
version: Option<&DependencyVersion>,
resolution: &Resolution,
) -> Option<PackageID> {
let entry = self.package_index.get(&name_hash)?;
let resolutions: &[Resolution] = self.packages.items_resolution();
let npm_version = match version {
Some(v) if v.tag == dependency::Tag::Npm => Some(&v.npm().version),
_ => None,
};
let resolved_npm_floor = if resolution.tag == ResolutionTag::Npm {
Some(resolution.npm().version)
} else {
None
};
let buf = self.buffers.string_bytes.as_slice();
let loaded_watermark = self.loaded_package_count;
let exact_pinned = self.exact_pinned.as_slice();
let try_satisfies_dedupe = |id: PackageID| -> bool {
let existing = &resolutions[id as usize];
if existing.tag != ResolutionTag::Npm {
return false;
}
let Some(npm_v) = npm_version else {
return false;
};
let existing_ver = existing.npm().version;
if !npm_v.satisfies(existing_ver, buf, buf) {
return false;
}
if id >= loaded_watermark && !exact_pinned.get(id as usize).copied().unwrap_or(false) {
if let Some(floor) = resolved_npm_floor {
if existing_ver.order(floor, buf, buf) == Ordering::Less
&& existing_ver.major != floor.major
{
return false;
}
}
}
true
};
match entry {
PackageIndexEntry::Id(id) => {
if cfg!(debug_assertions) {
debug_assert!((*id as usize) < resolutions.len());
}
if resolutions[*id as usize].eql(resolution, buf, buf) {
return Some(*id);
}
if try_satisfies_dedupe(*id) {
return Some(*id);
}
}
PackageIndexEntry::Ids(ids) => {
for &id in ids.iter() {
if cfg!(debug_assertions) {
debug_assert!((id as usize) < resolutions.len());
}
if resolutions[id as usize].eql(resolution, buf, buf) {
return Some(id);
}
if try_satisfies_dedupe(id) {
return Some(id);
}
}
}
}
None
}
pub fn append_package_dedupe(&mut self, pkg: &mut Package) -> Result<PackageID, AllocError> {
let entry = self.package_index.get_or_put(pkg.name_hash)?;
if !entry.found_existing {
let new_id: PackageID = PackageID::try_from(self.packages.len()).expect("int cast");
pkg.meta.id = new_id;
self.packages.append(*pkg)?;
*entry.value_ptr = PackageIndexEntry::Id(new_id);
return Ok(new_id);
}
let buf = self.buffers.string_bytes.as_slice();
let mut resolutions = self.packages.items_resolution();
match entry.value_ptr {
PackageIndexEntry::Id(existing_id) => {
let existing_id = *existing_id;
if pkg
.resolution
.eql(&resolutions[existing_id as usize], buf, buf)
{
pkg.meta.id = existing_id;
return Ok(existing_id);
}
let new_id: PackageID = PackageID::try_from(self.packages.len()).expect("int cast");
pkg.meta.id = new_id;
self.packages.append(*pkg)?;
resolutions = self.packages.items_resolution();
let pair = if pkg
.resolution
.order(&resolutions[existing_id as usize], buf, buf)
== Ordering::Greater
{
[new_id, existing_id]
} else {
[existing_id, new_id]
};
let mut ids = PackageIDList::with_capacity(8);
ids.extend_from_slice(&pair);
*entry.value_ptr = PackageIndexEntry::Ids(ids);
Ok(new_id)
}
PackageIndexEntry::Ids(existing_ids) => {
for &existing_id in existing_ids.iter() {
if pkg
.resolution
.eql(&resolutions[existing_id as usize], buf, buf)
{
pkg.meta.id = existing_id;
return Ok(existing_id);
}
}
let new_id: PackageID = PackageID::try_from(self.packages.len()).expect("int cast");
pkg.meta.id = new_id;
self.packages.append(*pkg)?;
resolutions = self.packages.items_resolution();
for i in 0..existing_ids.len() {
let existing_id = existing_ids[i];
if pkg
.resolution
.order(&resolutions[existing_id as usize], buf, buf)
== Ordering::Greater
{
existing_ids.insert(i, new_id);
return Ok(new_id);
}
}
existing_ids.push(new_id);
Ok(new_id)
}
}
}
pub fn get_or_put_id(
&mut self,
id: PackageID,
name_hash: PackageNameHash,
) -> Result<(), AllocError> {
let gpe = self.package_index.get_or_put(name_hash)?;
if gpe.found_existing {
let index: &mut PackageIndexEntry = gpe.value_ptr;
match index {
PackageIndexEntry::Id(existing_id) => {
let existing_id = *existing_id;
let resolutions = self.packages.items_resolution();
let buf = self.buffers.string_bytes.as_slice();
let pair = if resolutions[id as usize].order(
&resolutions[existing_id as usize],
buf,
buf,
) == Ordering::Greater
{
[id, existing_id]
} else {
[existing_id, id]
};
let mut ids = PackageIDList::with_capacity(8);
ids.extend_from_slice(&pair);
*index = PackageIndexEntry::Ids(ids);
}
PackageIndexEntry::Ids(existing_ids) => {
let resolutions = self.packages.items_resolution();
let buf = self.buffers.string_bytes.as_slice();
for i in 0..existing_ids.len() {
let existing_id = existing_ids[i];
if resolutions[id as usize].order(
&resolutions[existing_id as usize],
buf,
buf,
) == Ordering::Greater
{
existing_ids.insert(i, id);
return Ok(());
}
}
existing_ids.push(id);
}
}
} else {
*gpe.value_ptr = PackageIndexEntry::Id(id);
}
Ok(())
}
pub fn append_package(&mut self, package_: &Package) -> Result<Package, AllocError> {
let id: PackageID = self.packages.len() as PackageID; self.append_package_with_id(package_, id)
}
pub fn append_package_with_id(
&mut self,
package_: &Package,
id: PackageID,
) -> Result<Package, AllocError> {
let name_hash = package_.name_hash;
let resolution = package_.resolution;
let mut package = *package_;
package.meta.id = id;
self.packages.append(package)?;
self.get_or_put_id(id, name_hash)?;
if cfg!(debug_assertions) {
debug_assert!(self.get_package_id(name_hash, None, &resolution).is_some());
}
Ok(package)
}
#[inline]
pub fn string_builder(&mut self) -> StringBuilder<'_> {
StringBuilder {
len: 0,
cap: 0,
off: 0,
ptr: None,
string_bytes: &mut self.buffers.string_bytes,
string_pool: &mut self.string_pool,
}
}
#[inline]
pub fn string_builder_split(&mut self) -> (StringBuilder<'_>, LockfileFields<'_>) {
let Buffers {
string_bytes,
dependencies,
resolutions,
extern_strings,
trees,
hoisted_dependencies,
} = &mut self.buffers;
(
StringBuilder {
len: 0,
cap: 0,
off: 0,
ptr: None,
string_bytes,
string_pool: &mut self.string_pool,
},
LockfileFields {
packages: &mut self.packages,
dependencies,
resolutions,
extern_strings,
trees,
hoisted_dependencies,
package_index: &mut self.package_index,
overrides: &mut self.overrides,
catalogs: &mut self.catalogs,
workspace_paths: &mut self.workspace_paths,
workspace_versions: &mut self.workspace_versions,
patched_dependencies: &mut self.patched_dependencies,
trusted_dependencies: &mut self.trusted_dependencies,
scripts: &mut self.scripts,
},
)
}
pub fn string_buf(&mut self) -> SemverStringBuf<'_> {
SemverStringBuf {
bytes: &mut self.buffers.string_bytes,
pool: &mut self.string_pool,
}
}
}
pub struct Scratch {
pub duplicate_checker_map: DuplicateCheckerMap,
pub dependency_list_queue: DependencyQueue,
}
pub(crate) type DuplicateCheckerMap =
BunHashMap<PackageNameHash, bun_ast::Loc, IdentityContext<PackageNameHash>>;
pub(crate) type DependencyQueue = LinearFifo<DependencySlice, DynamicBuffer<DependencySlice>>;
impl Scratch {
pub(crate) fn init() -> Scratch {
Scratch {
dependency_list_queue: DependencyQueue::init(),
duplicate_checker_map: DuplicateCheckerMap::default(),
}
}
}
impl Default for Scratch {
fn default() -> Self {
Self::init()
}
}
pub struct LockfileFields<'a> {
pub packages: &'a mut PackageList,
pub dependencies: &'a mut DependencyList,
pub resolutions: &'a mut PackageIDList,
pub extern_strings: &'a mut ExternalStringBuffer,
pub trees: &'a mut tree::List,
pub hoisted_dependencies: &'a mut DependencyIDList,
pub package_index: &'a mut PackageIndexMap,
pub overrides: &'a mut OverrideMap,
pub catalogs: &'a mut CatalogMap,
pub workspace_paths: &'a mut NameHashMap,
pub workspace_versions: &'a mut VersionHashMap,
pub patched_dependencies: &'a mut PatchedDependenciesMap,
pub trusted_dependencies: &'a mut Option<TrustedDependenciesSet>,
pub scripts: &'a mut Scripts,
}
pub struct StringBuilder<'a> {
pub len: usize,
pub cap: usize,
pub off: usize,
pub ptr: Option<*mut u8>,
pub string_bytes: &'a mut Vec<u8>,
pub string_pool: &'a mut StringPool,
}
#[macro_export]
macro_rules! string_builder {
($lockfile:expr) => {
$crate::lockfile_real::StringBuilder {
len: 0,
cap: 0,
off: 0,
ptr: None,
string_bytes: &mut $lockfile.buffers.string_bytes,
string_pool: &mut $lockfile.string_pool,
}
};
}
pub use bun_semver::semver_string::BuilderStringType as StringBuilderType;
impl<'a> StringBuilder<'a> {
#[inline]
pub fn count(&mut self, slice: &[u8]) {
self.assert_not_allocated();
if SemverString::can_inline(slice) {
return;
}
self._count_with_hash(slice, SemverStringBuilder::string_hash(slice));
}
#[inline]
pub fn count_with_hash(&mut self, slice: &[u8], hash: u64) {
self.assert_not_allocated();
if SemverString::can_inline(slice) {
return;
}
self._count_with_hash(slice, hash);
}
#[inline]
fn assert_not_allocated(&self) {
if cfg!(debug_assertions) {
if self.ptr.is_some() {
Output::panic(format_args!(
"StringBuilder.count called after StringBuilder.allocate. This is a bug in Bun. Please make sure to call StringBuilder.count before allocating."
));
}
}
}
#[inline]
fn _count_with_hash(&mut self, slice: &[u8], hash: u64) {
self.assert_not_allocated();
if !self.string_pool.contains(hash) {
self.cap += slice.len();
}
}
pub fn allocated_slice(&self) -> &[u8] {
if self.ptr.is_some() {
&self.string_bytes[self.off..self.off + self.cap]
} else {
b""
}
}
pub fn clamp(&mut self) {
if cfg!(debug_assertions) {
debug_assert!(self.cap >= self.len);
debug_assert!(self.string_bytes.len() == self.off + self.cap);
}
let excess = self.cap - self.len;
if excess > 0 {
let new_len = self.string_bytes.len() - excess;
self.string_bytes.truncate(new_len);
}
}
pub fn allocate(&mut self) -> Result<(), AllocError> {
let string_bytes = &mut *self.string_bytes;
let prev_len = string_bytes.len();
string_bytes.resize(prev_len + self.cap, 0);
self.off = prev_len;
self.ptr = Some(unsafe { string_bytes.as_mut_ptr().add(prev_len) });
self.len = 0;
Ok(())
}
#[inline]
pub fn append<T: StringBuilderType>(&mut self, slice: &[u8]) -> T {
self.append_with_hash::<T>(slice, SemverStringBuilder::string_hash(slice))
}
pub fn append_without_pool<T: StringBuilderType>(&mut self, slice: &[u8], hash: u64) -> T {
if SemverString::can_inline(slice) {
return T::from_init(self.string_bytes.as_slice(), slice, hash);
}
if cfg!(debug_assertions) {
debug_assert!(self.len <= self.cap); debug_assert!(self.ptr.is_some()); }
let start = self.off + self.len;
let end = start + slice.len();
self.string_bytes[start..end].copy_from_slice(slice);
let final_slice = &self.string_bytes[start..end];
self.len += slice.len();
if cfg!(debug_assertions) {
debug_assert!(self.len <= self.cap);
}
T::from_init(self.string_bytes.as_slice(), final_slice, hash)
}
pub fn append_with_hash<T: StringBuilderType>(&mut self, slice: &[u8], hash: u64) -> T {
if SemverString::can_inline(slice) {
return T::from_init(self.string_bytes.as_slice(), slice, hash);
}
if cfg!(debug_assertions) {
debug_assert!(self.len <= self.cap); debug_assert!(self.ptr.is_some()); }
let string_entry = self.string_pool.get_or_put(hash).expect("unreachable");
if !string_entry.found_existing {
let start = self.off + self.len;
let end = start + slice.len();
self.string_bytes[start..end].copy_from_slice(slice);
let final_slice = &self.string_bytes[start..end];
self.len += slice.len();
*string_entry.value_ptr = SemverString::init(self.string_bytes.as_slice(), final_slice);
}
if cfg!(debug_assertions) {
debug_assert!(self.len <= self.cap);
}
T::from_pooled(*string_entry.value_ptr, hash)
}
}
impl<'a> bun_semver::StringBuilder for StringBuilder<'a> {
#[inline]
fn count(&mut self, slice_: &[u8]) {
StringBuilder::count(self, slice_)
}
#[inline]
fn append<T: bun_semver::semver_string::BuilderStringType>(&mut self, slice_: &[u8]) -> T {
StringBuilder::append::<T>(self, slice_)
}
}
pub mod package_index {
use super::*;
pub type Map = BunHashMap<PackageNameHash, Entry, IdentityContext<PackageNameHash>>;
#[repr(u8)]
pub enum Tag {
Id = 0,
Ids = 1,
}
pub enum Entry {
Id(PackageID),
Ids(PackageIDList),
}
impl Default for Entry {
#[inline]
fn default() -> Self {
Entry::Id(0)
}
}
}
pub use package_index::Entry as PackageIndexEntry;
pub use package_index::Map as PackageIndexMap;
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct FormatVersion(pub u32);
impl FormatVersion {
pub const V0: Self = Self(0);
pub const V1: Self = Self(1);
pub const V2: Self = Self(2);
pub const V3: Self = Self(3);
pub const fn current() -> Self {
FormatVersion::V3
}
}
pub(crate) struct EqlSorter<'a> {
pub string_buf: &'a [u8],
pub pkg_names: &'a [SemverString],
}
#[derive(Clone, Copy)]
pub(crate) struct PathToId {
pub pkg_id: PackageID,
pub tree_path: bun_ptr::RawSlice<u8>,
}
impl<'a> EqlSorter<'a> {
pub(crate) fn order(&self, l: PathToId, r: PathToId) -> Ordering {
let l_path = l.tree_path.slice();
let r_path = r.tree_path.slice();
strings::order(l_path, r_path).then_with(|| {
let l_name = self.pkg_names[l.pkg_id as usize];
let r_name = self.pkg_names[r.pkg_id as usize];
l_name.order(r_name, self.string_buf, self.string_buf)
})
}
}
impl Lockfile {
pub fn eql(&self, r: &Lockfile, r_loaded_package_count: usize) -> Result<bool, AllocError> {
let l: &Lockfile = self;
let l_hoisted_deps = l.buffers.hoisted_dependencies.as_slice();
let r_hoisted_deps = r.buffers.hoisted_dependencies.as_slice();
let l_string_buf = l.buffers.string_bytes.as_slice();
let r_string_buf = r.buffers.string_bytes.as_slice();
let l_len = l_hoisted_deps.len();
let r_len = r_hoisted_deps.len();
if l_len != r_len {
return Ok(false);
}
let mut sort_buf: Vec<PathToId> = Vec::with_capacity(l_len + r_len);
let mut path_buf = PathBuffer::uninit();
let mut depth_buf: tree::DepthBuf = tree::depth_buf_uninit();
let mut tree_paths: Vec<Box<[u8]>> = Vec::new();
for l_tree in l.buffers.trees.iter() {
let (rel_path, _) = tree::relative_path_and_depth::<{ tree::IteratorPathStyle::PkgPath }>(
l.buffers.trees.as_slice(),
l.buffers.dependencies.as_slice(),
l.buffers.string_bytes.as_slice(),
l_tree.id,
&mut path_buf,
&mut depth_buf,
);
let tree_path: Box<[u8]> = Box::<[u8]>::from(rel_path.as_bytes());
let tree_path_ptr = bun_ptr::RawSlice::new(&tree_path[..]);
tree_paths.push(tree_path);
for &l_dep_id in l_tree.dependencies.get(l_hoisted_deps) {
if l_dep_id == invalid_dependency_id {
continue;
}
let l_pkg_id = l.buffers.resolutions[l_dep_id as usize];
if l_pkg_id == invalid_package_id {
continue;
}
sort_buf.push(PathToId {
pkg_id: l_pkg_id,
tree_path: tree_path_ptr,
});
}
}
let l_count = sort_buf.len();
for r_tree in r.buffers.trees.iter() {
let (rel_path, _) = tree::relative_path_and_depth::<{ tree::IteratorPathStyle::PkgPath }>(
r.buffers.trees.as_slice(),
r.buffers.dependencies.as_slice(),
r.buffers.string_bytes.as_slice(),
r_tree.id,
&mut path_buf,
&mut depth_buf,
);
let tree_path: Box<[u8]> = Box::<[u8]>::from(rel_path.as_bytes());
let tree_path_ptr = bun_ptr::RawSlice::new(&tree_path[..]);
tree_paths.push(tree_path);
for &r_dep_id in r_tree.dependencies.get(r_hoisted_deps) {
if r_dep_id == invalid_dependency_id {
continue;
}
let r_pkg_id = r.buffers.resolutions[r_dep_id as usize];
if r_pkg_id == invalid_package_id {
continue;
}
if r_pkg_id as usize >= r_loaded_package_count {
return Ok(false);
}
sort_buf.push(PathToId {
pkg_id: r_pkg_id,
tree_path: tree_path_ptr,
});
}
}
let (l_buf, r_buf) = sort_buf.split_at_mut(l_count);
if l_buf.len() != r_buf.len() {
return Ok(false);
}
let l_pkgs = l.packages.slice();
let r_pkgs = r.packages.slice();
let l_pkg_names = l_pkgs.items_name();
let r_pkg_names = r_pkgs.items_name();
{
let sorter = EqlSorter {
pkg_names: l_pkg_names,
string_buf: l_string_buf,
};
l_buf.sort_unstable_by(|a, b| sorter.order(*a, *b));
}
{
let sorter = EqlSorter {
pkg_names: r_pkg_names,
string_buf: r_string_buf,
};
r_buf.sort_unstable_by(|a, b| sorter.order(*a, *b));
}
let l_pkg_name_hashes = l_pkgs.items_name_hash();
let l_pkg_resolutions = l_pkgs.items_resolution();
let l_pkg_bins = l_pkgs.items_bin();
let l_pkg_scripts = l_pkgs.items_scripts();
let r_pkg_name_hashes = r_pkgs.items_name_hash();
let r_pkg_resolutions = r_pkgs.items_resolution();
let r_pkg_bins = r_pkgs.items_bin();
let r_pkg_scripts = r_pkgs.items_scripts();
let l_extern_strings = l.buffers.extern_strings.as_slice();
let r_extern_strings = r.buffers.extern_strings.as_slice();
debug_assert_eq!(l_buf.len(), r_buf.len());
for (l_ids, r_ids) in l_buf.iter().zip(r_buf.iter()) {
let l_pkg_id = l_ids.pkg_id as usize;
let r_pkg_id = r_ids.pkg_id as usize;
if l_pkg_name_hashes[l_pkg_id] != r_pkg_name_hashes[r_pkg_id] {
return Ok(false);
}
let l_res = l_pkg_resolutions[l_pkg_id];
let r_res = r_pkg_resolutions[r_pkg_id];
if l_res.tag == ResolutionTag::Uninitialized
|| r_res.tag == ResolutionTag::Uninitialized
{
if l_res.tag != r_res.tag {
return Ok(false);
}
} else if !l_res.eql(&r_res, l_string_buf, r_string_buf) {
return Ok(false);
}
if !crate::bin::Bin::eql(
&l_pkg_bins[l_pkg_id],
&r_pkg_bins[r_pkg_id],
l_string_buf,
l_extern_strings,
r_string_buf,
r_extern_strings,
) {
return Ok(false);
}
if !package::Scripts::eql(
&l_pkg_scripts[l_pkg_id],
&r_pkg_scripts[r_pkg_id],
l_string_buf,
r_string_buf,
) {
return Ok(false);
}
}
Ok(true)
}
pub fn has_meta_hash_changed(
&mut self,
print_name_version_string: bool,
packages_len: usize,
) -> Result<bool, BunError> {
let previous_meta_hash = self.meta_hash;
self.meta_hash = self.generate_meta_hash(print_name_version_string, packages_len)?;
Ok(!strings::eql_long(
&previous_meta_hash,
&self.meta_hash,
false,
))
}
pub fn generate_meta_hash(
&self,
print_name_version_string: bool,
packages_len: usize,
) -> Result<MetaHash, BunError> {
if packages_len <= 1 {
return Ok(ZERO_HASH);
}
let mut string_builder = bun_core::StringBuilder::default();
let names: &[SemverString] = &self.packages.items_name()[..packages_len];
let resolutions: &[Resolution] = &self.packages.items_resolution()[..packages_len];
let bytes = self.buffers.string_bytes.as_slice();
let mut alphabetized_names: Vec<PackageID> = vec![0; packages_len.saturating_sub(1)];
const HASH_PREFIX: &[u8] =
b"\n-- BEGIN SHA512/256(`${alphabetize(name)}@${order(version)}`) --\n";
const HASH_SUFFIX: &[u8] = b"-- END HASH--\n";
string_builder.cap += HASH_PREFIX.len() + HASH_SUFFIX.len();
{
let mut i: usize = 1;
while i + 16 < packages_len {
for j in 0..16usize {
alphabetized_names[(i + j) - 1] = (i + j) as PackageID; string_builder.fmt_count(format_args!(
"{}@{}\n",
bstr::BStr::new(names[i + j].slice(bytes)),
resolutions[i + j].fmt(bytes, PathSep::Posix)
));
}
i += 16;
}
while i < packages_len {
alphabetized_names[i - 1] = i as PackageID; string_builder.fmt_count(format_args!(
"{}@{}\n",
bstr::BStr::new(names[i].slice(bytes)),
resolutions[i].fmt(bytes, PathSep::Posix)
));
i += 1;
}
}
const SCRIPTS_BEGIN: &[u8] = b"\n-- BEGIN SCRIPTS --\n";
const SCRIPTS_END: &[u8] = b"\n-- END SCRIPTS --\n";
let mut has_scripts = false;
for (field_name, scripts) in self.scripts.fields() {
for script in scripts.iter() {
if !script.is_empty() {
string_builder.fmt_count(format_args!(
"{}: {}\n",
field_name,
bstr::BStr::new(script)
));
has_scripts = true;
}
}
}
if has_scripts {
string_builder.count(SCRIPTS_BEGIN);
string_builder.count(SCRIPTS_END);
}
{
let alphabetizer = package::Alphabetizer::<u64> {
names: names.into(),
buf: bytes.into(),
resolutions: resolutions.into(),
};
alphabetized_names.sort_unstable_by(|a, b| alphabetizer.order(*a, *b));
}
string_builder.allocate().expect("unreachable");
let _ = string_builder.append(HASH_PREFIX);
for &i in alphabetized_names.iter() {
let _ = string_builder.fmt(format_args!(
"{}@{}\n",
bstr::BStr::new(names[i as usize].slice(bytes)),
resolutions[i as usize].fmt(bytes, PathSep::Any)
));
}
if has_scripts {
let _ = string_builder.append(SCRIPTS_BEGIN);
for (field_name, scripts) in self.scripts.fields() {
for script in scripts.iter() {
if !script.is_empty() {
let _ = string_builder.fmt(format_args!(
"{}: {}\n",
field_name,
bstr::BStr::new(script)
));
}
}
}
let _ = string_builder.append(SCRIPTS_END);
}
let _ = string_builder.append(HASH_SUFFIX);
let len = string_builder.len;
let alphabetized_name_version_string = &string_builder.allocated_slice()[..len];
if print_name_version_string {
Output::flush();
Output::disable_buffering();
Output::writer()
.write_all(alphabetized_name_version_string)
.expect("unreachable");
Output::enable_buffering();
}
let mut digest = ZERO_HASH;
unsafe {
Crypto::SHA512_256::hash(
alphabetized_name_version_string,
&mut digest,
core::ptr::null_mut(),
)
};
Ok(digest)
}
pub fn resolve_package_from_name_and_version(
&self,
package_name: &[u8],
version: &DependencyVersion,
) -> Option<PackageID> {
let name_hash = SemverStringBuilder::string_hash(package_name);
let entry = self.package_index.get(&name_hash)?;
let buf = self.buffers.string_bytes.as_slice();
match version.tag {
dependency::Tag::Npm => {
let npm_group = &version.npm().version;
match entry {
PackageIndexEntry::Id(id) => {
let resolutions = self.packages.items_resolution();
if cfg!(debug_assertions) {
debug_assert!((*id as usize) < resolutions.len());
}
let res_ver = resolutions[*id as usize].npm().version;
if npm_group.satisfies(res_ver, buf, buf) {
return Some(*id);
}
}
PackageIndexEntry::Ids(ids) => {
let resolutions = self.packages.items_resolution();
for &id in ids.iter() {
if cfg!(debug_assertions) {
debug_assert!((id as usize) < resolutions.len());
}
let res_ver = resolutions[id as usize].npm().version;
if npm_group.satisfies(res_ver, buf, buf) {
return Some(id);
}
}
}
}
}
_ => {}
}
None
}
}
const MAX_DEFAULT_TRUSTED_DEPENDENCIES: usize = 512;
pub static DEFAULT_TRUSTED_DEPENDENCIES_LIST: std::sync::LazyLock<Vec<&'static [u8]>> =
std::sync::LazyLock::new(|| {
const DATA: &str = include_str!("default-trusted-dependencies.txt");
let mut names: Vec<&'static [u8]> = DATA
.split([' ', '\r', '\n', '\t'])
.filter(|s| !s.is_empty())
.map(str::as_bytes)
.collect();
names.sort_unstable();
debug_assert!(
names.len() <= MAX_DEFAULT_TRUSTED_DEPENDENCIES,
"default-trusted-dependencies.txt is too large, please increase \
'MAX_DEFAULT_TRUSTED_DEPENDENCIES' in lockfile.rs"
);
names
});
pub mod default_trusted_dependencies {
use super::{
DEFAULT_TRUSTED_DEPENDENCIES_LIST, MAX_DEFAULT_TRUSTED_DEPENDENCIES, SemverStringBuilder,
};
use bun_collections::static_hash_map::{
Entry, HashContext, HashMapMixin, StaticHashMap, static_slots,
};
use std::sync::LazyLock;
const SLOTS: usize = static_slots(MAX_DEFAULT_TRUSTED_DEPENDENCIES);
pub(crate) struct TrustedDepHashCtx;
impl HashContext<&'static [u8]> for TrustedDepHashCtx {
#[inline]
fn ctx_hash(s: &&'static [u8]) -> u64 {
(SemverStringBuilder::string_hash(s) as u32) as u64
}
#[inline]
fn ctx_eql(a: &&'static [u8], b: &&'static [u8]) -> bool {
*a == *b
}
}
type Map = StaticHashMap<
&'static [u8],
(),
TrustedDepHashCtx,
MAX_DEFAULT_TRUSTED_DEPENDENCIES,
SLOTS,
>;
static MAP: LazyLock<Box<Map>> = LazyLock::new(|| {
let mut map = Box::<Map>::default();
for &dep in DEFAULT_TRUSTED_DEPENDENCIES_LIST.iter() {
debug_assert!(map.len < MAX_DEFAULT_TRUSTED_DEPENDENCIES);
let entry = map.get_or_put_assume_capacity(dep);
debug_assert!(!entry.found_existing);
*entry.value_ptr = ();
}
map
});
pub(crate) fn entries() -> impl Iterator<Item = &'static Entry<&'static [u8], ()>> {
MAP.entries.iter().filter(|e| !e.is_empty())
}
#[inline]
pub(crate) fn has_with_hash(hash: u64) -> bool {
MAP.has_with_hash(hash)
}
pub(crate) fn has(name: &[u8]) -> bool {
let hash = (SemverStringBuilder::string_hash(name) as u32) as u64;
for entry in &MAP.entries[(hash >> MAP.shift) as usize..] {
if entry.hash >= hash {
return entry.hash == hash && entry.key == name;
}
}
unreachable!()
}
}
impl Lockfile {
pub fn has_trusted_dependency(
&self,
alias: &[u8],
pkg_name: &[u8],
resolution: &Resolution,
) -> bool {
if let Some(trusted_dependencies) = &self.trusted_dependencies {
let trusted_name = if resolution.tag == ResolutionTag::Npm {
pkg_name
} else {
alias
};
let hash = SemverStringBuilder::string_hash(trusted_name) as u32;
let name_is_trusted = match trusted_dependencies.get(&hash) {
Some(name) => !name.is_empty() && **name == *trusted_name,
None => false,
};
if !name_is_trusted {
return false;
}
if resolution.tag == ResolutionTag::Npm {
return true;
}
return self.declared_by_root_or_workspace(alias, resolution);
}
if resolution.tag != ResolutionTag::Npm || !default_trusted_dependencies::has(pkg_name) {
return false;
}
let buf = self.buffers.string_bytes.as_slice();
let npm = resolution.npm();
let url = npm.url.slice(buf);
if url.is_empty() {
return true;
}
let registry = PackageManager::get()
.scope_for_package_name(pkg_name)
.url
.href();
let Ok(canonical_url) = crate::extract_tarball::build_url_with_printer(
registry,
&strings::StringOrTinyString::init(pkg_name),
npm.version,
buf,
|args| -> Result<Vec<u8>, std::io::Error> {
let mut out: Vec<u8> = Vec::new();
out.write_fmt(args)?;
Ok(out)
},
) else {
return false;
};
url == canonical_url.as_slice()
}
fn declared_by_root_or_workspace(&self, alias: &[u8], resolution: &Resolution) -> bool {
let buf = self.buffers.string_bytes.as_slice();
let packages = self.packages.slice();
let resolutions = packages.items_resolution();
let dependencies_lists = packages.items_dependencies();
for (pkg_resolution, dependencies) in resolutions.iter().zip(dependencies_lists.iter()) {
if pkg_resolution.tag != ResolutionTag::Workspace
&& pkg_resolution.tag != ResolutionTag::Root
{
continue;
}
for dep_id in dependencies.begin()..dependencies.end() {
let dep = &self.buffers.dependencies[dep_id as usize];
if dep.name.slice(buf) != alias {
continue;
}
let package_id = self.buffers.resolutions[dep_id as usize];
if package_id == invalid_package_id || package_id as usize >= resolutions.len() {
continue;
}
if resolutions[package_id as usize].eql(resolution, buf, buf) {
return true;
}
}
}
false
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct PatchedDep {
pub path: SemverString,
_padding: [u8; 7],
pub patchfile_hash_is_null: bool,
__patchfile_hash: u64,
}
impl Default for PatchedDep {
fn default() -> Self {
PatchedDep {
path: SemverString::default(),
_padding: [0; 7],
patchfile_hash_is_null: true,
__patchfile_hash: 0,
}
}
}
impl PatchedDep {
pub fn with_path(path: SemverString) -> Self {
Self {
path,
..Default::default()
}
}
pub fn set_patchfile_hash(&mut self, val: Option<u64>) {
self.patchfile_hash_is_null = val.is_none();
self.__patchfile_hash = val.unwrap_or(0);
}
pub fn patchfile_hash(&self) -> Option<u64> {
if self.patchfile_hash_is_null {
None
} else {
Some(self.__patchfile_hash)
}
}
}