use core::fmt;
use std::io::Write as _;
use bun_alloc::AllocError;
use crate::bun_fs::FileSystem;
use crate::lockfile_real::package::PackageColumns;
use crate::repository::Repository;
use bun_core::ZStr;
use bun_core::{Error, Global, Output, ZBox, env_var, fmt as bun_fmt};
use bun_dotenv::Loader as DotEnvLoader;
use bun_install::lockfile::{Format as LockfileFormat, LoadResult, Lockfile};
use bun_install::resolution::Tag as ResolutionTag;
use bun_install::{PackageID, Resolution};
use bun_paths::{self as path, AbsPath, PathBuffer, SEP};
use bun_semver::{self as Semver, String as SemverString};
use bun_sys::{self as sys, Dir, Fd, FdDirExt, File};
use crate::bun_progress::Node as ProgressNode;
use super::options::{self, Enable, LogLevel};
use super::{Command, Options, PackageManager, ProgressStrings, Subcommand};
impl PackageManager {
#[inline]
pub fn get_cache_directory(&mut self) -> Fd {
get_cache_directory(self)
}
pub fn manifest_disk_cache_ctx(&mut self) -> crate::package_manifest_map::DiskCacheCtx {
let enable_manifest_cache = self.options.enable.manifest_cache();
crate::package_manifest_map::DiskCacheCtx {
enable_manifest_cache,
enable_manifest_cache_control: self.options.enable.manifest_cache_control(),
cache_directory: enable_manifest_cache.then(|| get_cache_directory(self)),
timestamp_for_manifest_cache_control: self.timestamp_for_manifest_cache_control,
}
}
#[inline]
pub fn get_cache_directory_and_abs_path(&mut self) -> (Fd, AbsPath) {
get_cache_directory_and_abs_path(self)
}
#[inline]
pub fn get_temporary_directory(&mut self) -> &'static TemporaryDirectory {
get_temporary_directory(self)
}
#[inline]
pub fn cached_git_folder_name(
&self,
repository: &Repository,
patch_hash: Option<u64>,
) -> &'static ZStr {
cached_git_folder_name(self, repository, patch_hash)
}
#[inline]
pub fn cached_github_folder_name(
&self,
repository: &Repository,
patch_hash: Option<u64>,
) -> &'static ZStr {
cached_github_folder_name(self, repository, patch_hash)
}
#[inline]
pub fn cached_npm_package_folder_name(
&self,
name: &[u8],
version: Semver::Version,
patch_hash: Option<u64>,
) -> &'static ZStr {
cached_npm_package_folder_name(self, name, version, patch_hash)
}
#[inline]
pub fn cached_tarball_folder_name(
&self,
url: SemverString,
patch_hash: Option<u64>,
) -> &'static ZStr {
cached_tarball_folder_name(self, url, patch_hash)
}
#[inline]
pub fn save_lockfile(
&mut self,
load_result: &LoadResult,
save_format: LockfileFormat,
had_any_diffs: bool,
lockfile_before_install: &Lockfile,
packages_len_before_install: usize,
log_level: LogLevel,
) -> Result<(), AllocError> {
save_lockfile(
self,
load_result,
save_format,
had_any_diffs,
lockfile_before_install,
packages_len_before_install,
log_level,
)
}
#[inline]
pub fn write_yarn_lock(&mut self) -> Result<(), Error> {
write_yarn_lock(self)
}
}
#[inline]
pub fn get_cache_directory(this: &mut PackageManager) -> Fd {
unsafe { get_cache_directory_raw(this) }
}
#[inline]
pub unsafe fn get_cache_directory_raw(this: *mut PackageManager) -> Fd {
if let Some(d) = unsafe { (*this).cache_directory_.as_ref() } {
return d.fd();
}
let d = unsafe { ensure_cache_directory(this) };
let fd = d.fd();
unsafe { (*this).cache_directory_ = Some(d) };
fd
}
#[inline]
pub fn get_cache_directory_and_abs_path(this: &mut PackageManager) -> (Fd, AbsPath) {
let cache_dir = get_cache_directory(this);
(
cache_dir,
AbsPath::from(this.cache_directory_path.as_bytes())
.expect("cache_directory_path is absolute"),
)
}
#[inline]
pub fn get_temporary_directory(this: &mut PackageManager) -> &'static TemporaryDirectory {
if let Some(td) = GET_TEMPORARY_DIRECTORY_ONCE.get() {
return td;
}
let td = get_temporary_directory_run(this);
let _ = GET_TEMPORARY_DIRECTORY_ONCE.set(td);
GET_TEMPORARY_DIRECTORY_ONCE.get().expect("just set")
}
pub struct TemporaryDirectory {
pub handle: Dir,
pub path: ZBox,
pub name: &'static [u8],
}
const _: fn() = || {
fn assert<T: Send + Sync>() {}
assert::<TemporaryDirectory>();
};
static GET_TEMPORARY_DIRECTORY_ONCE: std::sync::OnceLock<TemporaryDirectory> =
std::sync::OnceLock::new();
fn get_temporary_directory_run(manager: &mut PackageManager) -> TemporaryDirectory {
let cache_directory_fd = get_cache_directory(manager);
let cache_directory = Dir::borrow(&cache_directory_fd);
let temp_dir_name = FileSystem::get_default_temp_dir();
let mut tried_dot_tmp = false;
let mut tempdir: Dir =
match sys::make_path::make_open_path(&Dir::cwd(), temp_dir_name, Default::default()) {
Ok(d) => d,
Err(_) => {
tried_dot_tmp = true;
match sys::make_path::make_open_path(
cache_directory,
bun_paths::path_literal!(".tmp"),
Default::default(),
) {
Ok(d) => d,
Err(err) => {
Output::pretty_errorln(format_args!(
"<r><red>error<r>: bun is unable to access tempdir: {}",
err.name()
));
Global::crash();
}
}
}
};
let mut tmpbuf = PathBuffer::uninit();
let tmpname =
FileSystem::tmpname(b"hm", &mut tmpbuf, bun_core::fast_random()).expect("unreachable");
let mut timer = if manager.options.log_level != LogLevel::Silent {
Some(bun_core::time::Timer::start().expect("unreachable"))
} else {
None
};
'brk: loop {
let file = match tempdir.create_file_z(
tmpname,
sys::CreateFlags {
truncate: true,
..Default::default()
},
) {
Ok(f) => f,
Err(err2) => {
if !tried_dot_tmp {
tried_dot_tmp = true;
tempdir = match sys::make_path::make_open_path(
cache_directory,
bun_paths::path_literal!(".tmp"),
Default::default(),
) {
Ok(d) => d,
Err(err) => {
Output::pretty_errorln(format_args!(
"<r><red>error<r>: bun is unable to access tempdir: {}",
err.name()
));
Global::crash();
}
};
if verbose_install() {
Output::pretty_errorln(format_args!(
"<r><yellow>warn<r>: bun is unable to access tempdir: {}, using fallback",
err2.name()
));
}
continue 'brk;
}
Output::pretty_errorln(format_args!(
"<r><red>error<r>: {} accessing temporary directory. Please set <b>$BUN_TMPDIR<r> or <b>$BUN_INSTALL<r>",
err2.name()
));
Global::crash();
}
};
let _ = file.close();
match sys::renameat_z(tempdir.fd(), tmpname, cache_directory.fd(), tmpname) {
Ok(()) => {}
Err(err) => {
if !tried_dot_tmp {
tried_dot_tmp = true;
tempdir = match cache_directory.make_open_path(b".tmp", Default::default()) {
Ok(d) => d,
Err(err2) => {
Output::pretty_errorln(format_args!(
"<r><red>error<r>: bun is unable to write files to tempdir: {}",
err2.name()
));
Global::crash();
}
};
if verbose_install() {
Output::pretty_errorln(format_args!(
"<r><d>info<r>: cannot move files from tempdir: {}, using fallback",
bun_fmt::s(err.name())
));
}
continue 'brk;
}
Output::pretty_errorln(format_args!(
"<r><red>error<r>: {} accessing temporary directory. Please set <b>$BUN_TMPDIR<r> or <b>$BUN_INSTALL<r>",
bun_fmt::s(err.name())
));
Global::crash();
}
}
let _ = cache_directory.delete_file_z(tmpname);
break;
}
if tried_dot_tmp {
USING_FALLBACK_TEMP_DIR.store(true, core::sync::atomic::Ordering::Relaxed);
}
if manager.options.log_level != LogLevel::Silent {
let elapsed = timer.as_mut().unwrap().read();
if elapsed > bun_core::time::NS_PER_MS * 100 {
let mut path_buf = PathBuffer::uninit();
let cache_dir_path: &[u8] = match sys::get_fd_path(cache_directory_fd, &mut path_buf) {
Ok(p) => &p[..],
Err(_) => b"it",
};
Output::pretty_errorln(format_args!(
"<r><yellow>warn<r>: Slow filesystem detected. If {} is a network drive, consider setting $BUN_INSTALL_CACHE_DIR to a local folder.",
bun_fmt::s(cache_dir_path)
));
}
}
let mut buf = PathBuffer::uninit();
let temp_dir_path = match sys::get_fd_path_z(Fd::from_std_dir(&tempdir), &mut buf) {
Ok(p) => p,
Err(err) => {
Output::err(
err,
"Failed to read temporary directory path: '{}'",
(bun_fmt::s(temp_dir_name),),
);
Global::exit(1);
}
};
TemporaryDirectory {
handle: tempdir,
name: temp_dir_name,
path: ZBox::from_bytes(temp_dir_path.as_bytes()),
}
}
#[cold]
#[inline(never)]
unsafe fn ensure_cache_directory(this: *mut PackageManager) -> Dir {
loop {
if unsafe { (*this).options.enable.contains(Enable::CACHE) } {
let env = unsafe { &*this }.env_mut();
let cache_dir = fetch_cache_directory_path(env, Some(unsafe { &(*this).options }));
unsafe { (*this).cache_directory_path = ZBox::from_bytes(&cache_dir.path) };
match Dir::cwd().make_open_path(&cache_dir.path, Default::default()) {
Ok(d) => return d,
Err(_) => {
unsafe { (*this).options.enable.set(Enable::CACHE, false) };
unsafe { (*this).cache_directory_path = ZBox::from_bytes(b"") };
continue;
}
}
}
unsafe {
(*this).cache_directory_path =
ZBox::from_bytes(path::resolve_path::join_abs_string::<path::platform::Auto>(
FileSystem::instance().top_level_dir(),
&[b"node_modules", b".cache"],
))
};
match Dir::cwd().make_open_path(b"node_modules/.cache", Default::default()) {
Ok(d) => return d,
Err(err) => {
Output::pretty_errorln(format_args!(
"<r><red>error<r>: bun is unable to write files: {}",
err.name()
));
Global::crash();
}
}
}
}
pub struct CacheDir {
pub path: Vec<u8>,
pub is_node_modules: bool,
}
pub fn fetch_cache_directory_path(env: &mut DotEnvLoader, options: Option<&Options>) -> CacheDir {
if let Some(dir) = env.get(b"BUN_INSTALL_CACHE_DIR") {
return CacheDir {
path: FileSystem::instance().abs(&[dir]).to_vec(),
is_node_modules: false,
};
}
if let Some(opts) = options {
if !opts.cache_directory.is_empty() {
return CacheDir {
path: FileSystem::instance().abs(&[opts.cache_directory]).to_vec(),
is_node_modules: false,
};
}
}
if let Some(dir) = env.get(b"BUN_INSTALL") {
let parts: [&[u8]; 3] = [dir, b"install/", b"cache/"];
return CacheDir {
path: FileSystem::instance().abs(&parts).to_vec(),
is_node_modules: false,
};
}
if let Some(dir) = env_var::XDG_CACHE_HOME.get() {
let parts: [&[u8]; 4] = [dir, b".bun/", b"install/", b"cache/"];
return CacheDir {
path: FileSystem::instance().abs(&parts).to_vec(),
is_node_modules: false,
};
}
if let Some(dir) = env_var::HOME.get() {
let parts: [&[u8]; 4] = [dir, b".bun/", b"install/", b"cache/"];
return CacheDir {
path: FileSystem::instance().abs(&parts).to_vec(),
is_node_modules: false,
};
}
let fallback_parts: [&[u8]; 1] = [b"node_modules/.bun-cache"];
CacheDir {
is_node_modules: true,
path: FileSystem::instance().abs(&fallback_parts).to_vec(),
}
}
struct ByteCursor<'a> {
buf: &'a mut [u8],
at: usize,
}
impl<'a> ByteCursor<'a> {
#[inline(always)]
fn new(buf: &'a mut [u8]) -> Self {
Self { buf, at: 0 }
}
#[inline(always)]
fn put(&mut self, bytes: &[u8]) {
let end = self.at + bytes.len();
self.buf[self.at..end].copy_from_slice(bytes);
self.at = end;
}
#[inline(always)]
fn put_byte(&mut self, b: u8) {
self.buf[self.at] = b;
self.at += 1;
}
#[inline(always)]
fn put_u64_dec(&mut self, n: u64) {
let mut tmp = bun_fmt::ItoaBuf::new();
self.put(bun_fmt::itoa(&mut tmp, n));
}
#[inline(always)]
fn put_u64_hex16<const LOWER: bool>(&mut self, v: u64) {
self.put(&bun_fmt::u64_hex_fixed::<LOWER, 16>(v));
}
#[inline(always)]
fn put_u64_hex_var(&mut self, n: u64) {
let mut tmp = [0u8; 16];
self.put(bun_fmt::u64_hex_var_lower(&mut tmp, n));
}
#[inline(always)]
fn put_cache_version(&mut self, v: Option<usize>) {
if let Some(v) = v {
self.put(b"@@@");
self.put_u64_dec(v as u64);
}
}
#[inline(always)]
fn put_patch_hash(&mut self, hash: Option<u64>) {
if let Some(h) = hash {
self.put(b"_patch_hash=");
self.put_u64_hex_var(h);
}
}
#[inline(always)]
fn finish_z(self) -> &'a ZStr {
let at = self.at;
self.buf[at] = 0;
ZStr::from_buf(self.buf, at)
}
}
pub fn cached_git_folder_name_print<'a>(
buf: &'a mut [u8],
resolved: &[u8],
patch_hash: Option<u64>,
) -> &'a ZStr {
let mut w = ByteCursor::new(buf);
w.put(b"@G@");
w.put(resolved);
w.put_patch_hash(patch_hash);
w.finish_z()
}
pub fn cached_git_folder_name(
this: &PackageManager,
repository: &Repository,
patch_hash: Option<u64>,
) -> &'static ZStr {
cached_git_folder_name_print(
cached_package_folder_name_buf(),
this.lockfile.str(&repository.resolved),
patch_hash,
)
}
pub fn cached_git_folder_name_print_auto(
this: &PackageManager,
repository: &Repository,
patch_hash: Option<u64>,
) -> &'static ZStr {
if !repository.resolved.is_empty() {
return cached_git_folder_name(this, repository, patch_hash);
}
if !repository.repo.is_empty() && !repository.committish.is_empty() {
let string_buf = this.lockfile.buffers.string_bytes.as_slice();
let mut w = ByteCursor::new(cached_package_folder_name_buf());
w.put(b"@G@");
w.put(repository.committish.slice(string_buf));
w.put_cache_version(Some(CacheVersion::CURRENT));
w.put_patch_hash(patch_hash);
return w.finish_z();
}
ZStr::EMPTY
}
pub fn cached_github_folder_name_print<'a>(
buf: &'a mut [u8],
resolved: &[u8],
patch_hash: Option<u64>,
) -> &'a ZStr {
let mut w = ByteCursor::new(buf);
w.put(b"@GH@");
w.put(resolved);
w.put_cache_version(Some(CacheVersion::CURRENT));
w.put_patch_hash(patch_hash);
w.finish_z()
}
pub fn cached_github_folder_name(
this: &PackageManager,
repository: &Repository,
patch_hash: Option<u64>,
) -> &'static ZStr {
cached_github_folder_name_print(
cached_package_folder_name_buf(),
this.lockfile.str(&repository.resolved),
patch_hash,
)
}
pub fn cached_github_folder_name_print_auto(
this: &PackageManager,
repository: &Repository,
patch_hash: Option<u64>,
) -> &'static ZStr {
if !repository.resolved.is_empty() {
return cached_github_folder_name(this, repository, patch_hash);
}
if !repository.owner.is_empty()
&& !repository.repo.is_empty()
&& !repository.committish.is_empty()
{
return cached_github_folder_name_print_guess(
cached_package_folder_name_buf(),
this.lockfile.buffers.string_bytes.as_slice(),
repository,
patch_hash,
);
}
ZStr::EMPTY
}
pub fn cached_npm_package_folder_name_print<'a>(
this: &PackageManager,
buf: &'a mut [u8],
name: &[u8],
version: Semver::Version,
patch_hash: Option<u64>,
) -> &'a ZStr {
let scope = this.scope_for_package_name(name);
if scope.name.is_empty() && !this.options.did_override_default_scope {
let include_version_number = true;
return cached_npm_package_folder_print_basename(
buf,
name,
version,
patch_hash,
include_version_number,
);
}
let include_version_number = false;
let spanned_len =
cached_npm_package_folder_print_basename(buf, name, version, None, include_version_number)
.as_bytes()
.len();
let scope_url = scope.url.url();
let mut w = ByteCursor {
buf,
at: spanned_len,
};
let available = w.buf.len() - spanned_len;
if scope_url.hostname.len() > 32 || available < 64 {
let visible_hostname = &scope_url.hostname[..scope_url.hostname.len().min(12)];
w.put(b"@@");
w.put(visible_hostname);
w.put(b"__");
w.put_u64_hex16::<true>(Semver::semver_string::Builder::string_hash(scope_url.href));
} else {
w.put(b"@@");
w.put(scope_url.hostname);
}
w.put_cache_version(Some(CacheVersion::CURRENT));
w.put_patch_hash(patch_hash);
w.finish_z()
}
fn cached_github_folder_name_print_guess<'a>(
buf: &'a mut [u8],
string_buf: &[u8],
repository: &Repository,
patch_hash: Option<u64>,
) -> &'a ZStr {
let mut w = ByteCursor::new(buf);
w.put(b"@GH@");
w.put(repository.owner.slice(string_buf));
w.put_byte(b'-');
w.put(repository.repo.slice(string_buf));
w.put_byte(b'-');
w.put(repository.committish.slice(string_buf));
w.put_cache_version(Some(CacheVersion::CURRENT));
w.put_patch_hash(patch_hash);
w.finish_z()
}
pub fn cached_npm_package_folder_name(
this: &PackageManager,
name: &[u8],
version: Semver::Version,
patch_hash: Option<u64>,
) -> &'static ZStr {
cached_npm_package_folder_name_print(
this,
cached_package_folder_name_buf(),
name,
version,
patch_hash,
)
}
pub fn cached_npm_package_folder_print_basename<'a>(
buf: &'a mut [u8],
name: &[u8],
version: Semver::Version,
patch_hash: Option<u64>,
include_cache_version: bool,
) -> &'a ZStr {
let cache_ver = if include_cache_version {
Some(CacheVersion::CURRENT)
} else {
None
};
let mut w = ByteCursor::new(buf);
w.put(name);
w.put_byte(b'@');
w.put_u64_dec(version.major);
w.put_byte(b'.');
w.put_u64_dec(version.minor);
w.put_byte(b'.');
w.put_u64_dec(version.patch);
if version.tag.has_pre() {
w.put_byte(b'-');
w.put_u64_hex16::<true>(version.tag.pre.hash);
}
if version.tag.has_build() {
w.put_byte(b'+');
w.put_u64_hex16::<false>(version.tag.build.hash);
}
w.put_cache_version(cache_ver);
w.put_patch_hash(patch_hash);
w.finish_z()
}
pub fn cached_tarball_folder_name_print<'a>(
buf: &'a mut [u8],
url: &[u8],
patch_hash: Option<u64>,
) -> &'a ZStr {
let mut w = ByteCursor::new(buf);
w.put(b"@T@");
w.put_u64_hex16::<true>(Semver::semver_string::Builder::string_hash(url));
w.put_cache_version(Some(CacheVersion::CURRENT));
w.put_patch_hash(patch_hash);
w.finish_z()
}
pub fn cached_tarball_folder_name(
this: &PackageManager,
url: SemverString,
patch_hash: Option<u64>,
) -> &'static ZStr {
cached_tarball_folder_name_print(
cached_package_folder_name_buf(),
this.lockfile.str(&url),
patch_hash,
)
}
pub fn is_folder_in_cache(this: &mut PackageManager, folder_path: &ZStr) -> bool {
sys::directory_exists_at(get_cache_directory(this), folder_path).unwrap_or(false)
}
pub fn is_package_in_cache_at(cache_dir: Fd, folder_path: &ZStr, tag: ResolutionTag) -> bool {
let marker: &[u8] = match tag {
ResolutionTag::Npm => b"package.json",
ResolutionTag::Git => b".bun-tag",
_ => return sys::directory_exists_at(cache_dir, folder_path).unwrap_or(false),
};
let mut buf = PathBuffer::uninit();
let marker_path = path::resolve_path::join_z_buf::<path::platform::Auto>(
&mut buf.0,
&[folder_path.as_bytes(), marker],
);
sys::exists_at(cache_dir, marker_path)
}
pub fn is_package_in_cache(
this: &mut PackageManager,
folder_path: &ZStr,
tag: ResolutionTag,
) -> bool {
is_package_in_cache_at(get_cache_directory(this), folder_path, tag)
}
pub fn setup_global_dir(manager: &mut PackageManager, ctx: &Command::Context) -> Result<(), Error> {
manager.options.global_bin_dir = options::open_global_bin_dir(ctx.install.as_deref())?;
let mut out_buffer = PathBuffer::uninit();
let result = sys::get_fd_path_z(manager.options.global_bin_dir, &mut out_buffer)?;
let path = FileSystem::instance()
.dirname_store()
.append(result.as_bytes_with_nul())?;
manager.options.bin_path = ZStr::from_slice_with_nul(path);
Ok(())
}
pub fn global_link_dir(this: &mut PackageManager) -> Fd {
if let Some(d) = this.global_link_dir.as_ref() {
return d.fd();
}
let global_dir = match options::open_global_dir(this.options.explicit_global_directory) {
Ok(d) => Dir::from_fd(d),
Err(err) if err == bun_core::err!("No global directory found") => {
Output::err_generic(
"failed to find a global directory for package caching and global link directories",
(),
);
Global::exit(1);
}
Err(err) => {
Output::err(err, "failed to open the global directory", ());
Global::exit(1);
}
};
let link_dir = match global_dir.make_open_path(b"node_modules", Default::default()) {
Ok(d) => d,
Err(err) => {
Output::err(
err,
"failed to open global link dir node_modules at '{}'",
(global_dir.fd(),),
);
Global::exit(1);
}
};
let link_fd = link_dir.fd();
this.global_dir = Some(global_dir);
this.global_link_dir = Some(link_dir);
let mut buf = PathBuffer::uninit();
let path_ = match sys::get_fd_path(link_fd, &mut buf) {
Ok(p) => p,
Err(err) => {
Output::err(
err,
"failed to get the full path of the global directory",
(),
);
Global::exit(1);
}
};
this.global_link_dir_path = Box::<[u8]>::from(bun_core::handle_oom(
FileSystem::instance().dirname_store().append(path_),
));
link_fd
}
pub fn global_link_dir_path(this: &mut PackageManager) -> &[u8] {
let _ = global_link_dir(this);
&this.global_link_dir_path
}
pub fn global_link_dir_and_path(this: &mut PackageManager) -> (Fd, &[u8]) {
let dir = global_link_dir(this);
(dir, &this.global_link_dir_path)
}
pub fn path_for_cached_npm_path<'a>(
this: &mut PackageManager,
buf: &'a mut PathBuffer,
package_name: &[u8],
version: Semver::Version,
) -> Result<&'a mut [u8], Error> {
let mut cache_path_buf = PathBuffer::uninit();
let cache_path = cached_npm_package_folder_name_print(
this,
&mut cache_path_buf.0[..],
package_name,
version,
None,
);
let cache_path_len = cache_path.as_bytes().len();
if cfg!(debug_assertions) {
debug_assert!(cache_path_buf[package_name.len()] == b'@');
}
cache_path_buf[package_name.len()] = SEP;
let cache_dir: Fd = get_cache_directory(this);
#[cfg(windows)]
{
let _ = cache_dir;
let mut path_buf = PathBuffer::uninit();
let cache_path = ZStr::from_buf(&cache_path_buf, cache_path_len);
let joined = path::resolve_path::join_abs_string_buf_z::<path::platform::Windows>(
&this.cache_directory_path,
&mut path_buf,
&[cache_path.as_bytes()],
);
return match sys::readlink(joined, &mut buf.0[..]) {
Ok(n) => Ok(&mut buf.0[..n]),
Err(err) => {
let _ = sys::unlink(joined);
Err(err.into())
}
};
}
#[cfg(not(windows))]
{
let cache_path = ZStr::from_buf(&cache_path_buf, cache_path_len);
match sys::readlinkat(cache_dir, cache_path, &mut buf.0[..]) {
Ok(n) => Ok(&mut buf.0[..n]),
Err(err) => {
let _ = sys::unlinkat(cache_dir, cache_path);
Err(Error::from(err))
}
}
}
}
pub fn path_for_resolution<'a>(
this: &mut PackageManager,
package_id: PackageID,
resolution: &Resolution,
buf: &'a mut PathBuffer,
) -> Result<&'a mut [u8], Error> {
match resolution.tag {
ResolutionTag::Npm => {
let npm = *resolution.npm();
let package_name_ = this.lockfile.packages.items_name()[package_id as usize];
let package_name = this.lockfile.str(&package_name_).to_vec();
path_for_cached_npm_path(this, buf, &package_name, npm.version)
}
_ => Ok(&mut buf.0[..0]),
}
}
pub struct CacheDirAndSubpath<'a> {
pub cache_dir: Fd,
pub cache_dir_subpath: &'a ZStr,
}
pub fn compute_cache_dir_and_subpath<'a>(
manager: &mut PackageManager,
pkg_name: &[u8],
resolution: &Resolution,
folder_path_buf: &'a mut PathBuffer,
patch_hash: Option<u64>,
) -> CacheDirAndSubpath<'a> {
let name = pkg_name;
let mut cache_dir = Fd::cwd();
let mut cache_dir_subpath: &ZStr = ZStr::EMPTY;
match resolution.tag {
ResolutionTag::Npm => {
let version = resolution.npm().version;
cache_dir_subpath = cached_npm_package_folder_name(manager, name, version, patch_hash);
cache_dir = get_cache_directory(manager);
}
ResolutionTag::Git => {
let git = resolution.git();
cache_dir_subpath = cached_git_folder_name(manager, git, patch_hash);
cache_dir = get_cache_directory(manager);
}
ResolutionTag::Github => {
let github = resolution.github();
cache_dir_subpath = cached_github_folder_name(manager, github, patch_hash);
cache_dir = get_cache_directory(manager);
}
ResolutionTag::Folder => {
let buf = manager.lockfile.buffers.string_bytes.as_slice();
let folder = resolution.folder().slice(buf);
if folder.is_empty() || (folder.len() == 1 && folder[0] == b'.') {
cache_dir_subpath = z_static(b".\0");
} else {
folder_path_buf[..folder.len()].copy_from_slice(folder);
folder_path_buf[folder.len()] = 0;
cache_dir_subpath = ZStr::from_buf(folder_path_buf, folder.len());
}
cache_dir = Fd::cwd();
}
ResolutionTag::LocalTarball => {
let tarball = *resolution.local_tarball();
cache_dir_subpath = cached_tarball_folder_name(manager, tarball, patch_hash);
cache_dir = get_cache_directory(manager);
}
ResolutionTag::RemoteTarball => {
let tarball = *resolution.remote_tarball();
cache_dir_subpath = cached_tarball_folder_name(manager, tarball, patch_hash);
cache_dir = get_cache_directory(manager);
}
ResolutionTag::Workspace => {
let buf = manager.lockfile.buffers.string_bytes.as_slice();
let folder = resolution.workspace().slice(buf);
if folder.is_empty() || (folder.len() == 1 && folder[0] == b'.') {
cache_dir_subpath = z_static(b".\0");
} else {
folder_path_buf[..folder.len()].copy_from_slice(folder);
folder_path_buf[folder.len()] = 0;
cache_dir_subpath = ZStr::from_buf(folder_path_buf, folder.len());
}
cache_dir = Fd::cwd();
}
ResolutionTag::Symlink => {
let directory = global_link_dir(manager);
let folder = resolution
.symlink()
.slice(manager.lockfile.buffers.string_bytes.as_slice())
.to_vec();
if folder.is_empty() || (folder.len() == 1 && folder[0] == b'.') {
cache_dir_subpath = z_static(b".\0");
cache_dir = Fd::cwd();
} else {
let global_link_dir = global_link_dir_path(manager);
let ptr = &mut folder_path_buf.0[..];
let mut off = 0usize;
ptr[off..off + global_link_dir.len()].copy_from_slice(global_link_dir);
off += global_link_dir.len();
if global_link_dir[global_link_dir.len() - 1] != SEP {
ptr[off] = SEP;
off += 1;
}
ptr[off..off + folder.len()].copy_from_slice(&folder);
off += folder.len();
ptr[off] = 0;
let len = off;
cache_dir_subpath = ZStr::from_buf(folder_path_buf, len);
cache_dir = directory;
}
}
_ => {}
}
CacheDirAndSubpath {
cache_dir,
cache_dir_subpath,
}
}
pub fn attempt_to_create_package_json_and_open() -> Result<File, Error> {
let package_json_file = match Dir::cwd().create_file_z(
z_static(b"package.json\0"),
sys::CreateFlags {
read: true,
..Default::default()
},
) {
Ok(f) => f,
Err(err) => {
Output::pretty_errorln(format_args!(
"<r><red>error:<r> {} create package.json",
err.name()
));
Global::crash();
}
};
package_json_file.pwrite_all(b"{\"dependencies\": {}}", 0)?;
Ok(package_json_file)
}
pub fn attempt_to_create_package_json() -> Result<(), Error> {
let file = attempt_to_create_package_json_and_open()?;
let _ = file.close(); Ok(())
}
pub fn save_lockfile(
this: &mut PackageManager,
load_result: &LoadResult,
save_format: LockfileFormat,
had_any_diffs: bool,
lockfile_before_install: &Lockfile,
packages_len_before_install: usize,
log_level: LogLevel,
) -> Result<(), AllocError> {
if this.lockfile.is_empty() {
if !this.options.dry_run {
'delete: {
let delete_format = match load_result {
LoadResult::NotFound => break 'delete,
LoadResult::Err(err) => err.format,
LoadResult::Ok(ok) => ok.format,
};
match sys::unlinkat(
Fd::cwd(),
if delete_format == LockfileFormat::Text {
bun_paths::path_literal!("bun.lock")
} else {
bun_paths::path_literal!("bun.lockb")
},
) {
Ok(()) => {}
Err(err) => {
if err.get_errno() == sys::E::ENOENT {
if had_any_diffs {
return Ok(());
}
break 'delete;
}
if log_level != LogLevel::Silent {
Output::err(err, "failed to delete empty lockfile", ());
}
return Ok(());
}
}
}
}
if !this.options.global {
if log_level != LogLevel::Silent {
match this.subcommand {
Subcommand::Remove => Output::pretty_errorln(format_args!(
"\npackage.json has no dependencies! Deleted empty lockfile"
)),
_ => {
Output::pretty_errorln(format_args!("No packages! Deleted empty lockfile"))
}
}
}
}
return Ok(());
}
let mut save_node: *mut ProgressNode = core::ptr::null_mut();
if log_level.show_progress() {
this.progress.supports_ansi_escape_codes = Output::enable_ansi_colors_stderr();
save_node = this.progress.start(ProgressStrings::save(), 0);
unsafe { (*save_node).activate() };
this.progress.refresh();
}
this.lockfile.save_to_disk(load_result, &this.options);
if save_format == LockfileFormat::Text && load_result.loaded_from_binary_lockfile() {
let _ = sys::unlinkat(Fd::cwd(), bun_paths::path_literal!("bun.lockb"));
}
if cfg!(debug_assertions) {
if !matches!(load_result, LoadResult::NotFound) {
if load_result.loaded_from_text_lockfile() {
if !Lockfile::eql(
&this.lockfile,
lockfile_before_install,
packages_len_before_install,
)? {
Output::panic(format_args!("Lockfile non-deterministic after saving"));
}
} else {
if this
.lockfile
.has_meta_hash_changed(false, packages_len_before_install)
.unwrap_or(false)
{
Output::panic(format_args!(
"Lockfile metahash non-deterministic after saving"
));
}
}
}
}
if log_level.show_progress() {
unsafe { (*save_node).end() };
this.progress.refresh();
this.progress.root.end();
this.progress = Default::default();
} else if log_level != LogLevel::Silent {
Output::pretty_errorln(format_args!("Saved lockfile"));
Output::flush();
}
Ok(())
}
pub fn update_lockfile_if_needed(
manager: &mut PackageManager,
load_result: &LoadResult,
) -> Result<(), Error> {
if let LoadResult::Ok(ok) = load_result {
if ok.serializer_result.packages_need_update {
let mut slice = manager.lockfile.packages.slice();
for meta in slice.items_meta_mut() {
meta.set_has_install_script(false);
}
}
}
Ok(())
}
pub fn write_yarn_lock(this: &mut PackageManager) -> Result<(), Error> {
let mut tmpname_buf = [0u8; 512];
tmpname_buf[0..8].copy_from_slice(b"tmplock-");
let mut tmpfile = bun_resolver::fs::RealFsTmpfile::default();
let mut secret = [0u8; 32];
secret[0..8].copy_from_slice(
&u64::try_from(bun_core::time::milli_timestamp())
.expect("int cast")
.to_le_bytes(),
);
let mut base64_bytes = [0u8; 64];
bun_core::csprng(&mut base64_bytes);
let tmpname_len = {
let mut cursor = &mut tmpname_buf[8..];
let initial_len = cursor.len();
for b in &base64_bytes {
write!(cursor, "{:02x}", b).expect("unreachable");
}
initial_len - cursor.len()
};
tmpname_buf[tmpname_len + 8] = 0;
let tmpname = ZStr::from_buf(&tmpname_buf, tmpname_len + 8);
if let Err(err) = tmpfile.create(tmpname) {
Output::pretty_errorln(format_args!(
"<r><red>error:<r> failed to create tmpfile: {}",
err.name()
));
Global::crash();
}
let file = tmpfile.file();
{
let mut printer = crate::lockfile_real::Printer {
lockfile: &this.lockfile,
options: &this.options,
successfully_installed: None,
updates: &[],
};
let mut buf: Vec<u8> = Vec::with_capacity(4096);
crate::lockfile_real::printer::Yarn::print(&mut printer, &mut buf)?;
file.write_all(&buf).map_err(Error::from)?;
}
#[cfg(unix)]
{
let _ = sys::fchmod(
tmpfile.fd,
0o0000040 | 0o0000004 | 0o0000002 | 0o0000400 | 0o0000200 | 0o0000020,
);
}
tmpfile.promote_to_cwd(tmpname, z_static(b"yarn.lock\0"))?;
Ok(())
}
pub struct CacheVersion;
impl CacheVersion {
pub const CURRENT: usize = 1;
}
#[derive(Default)]
pub struct CacheVersionFormatter {
pub version_number: Option<usize>,
}
impl fmt::Display for CacheVersionFormatter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(version) = self.version_number {
write!(f, "@@@{}", version)?;
}
Ok(())
}
}
#[derive(Default)]
pub struct PatchHashFmt {
pub hash: Option<u64>,
}
impl fmt::Display for PatchHashFmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(h) = self.hash {
write!(f, "_patch_hash={:x}", h)?;
}
Ok(())
}
}
static USING_FALLBACK_TEMP_DIR: core::sync::atomic::AtomicBool =
core::sync::atomic::AtomicBool::new(false);
#[inline]
fn verbose_install() -> bool {
PackageManager::verbose_install()
}
#[inline]
fn cached_package_folder_name_buf() -> &'static mut [u8] {
unsafe { (*super::cached_package_folder_name_buf()).as_mut_slice() }
}
#[inline]
const fn z_static(bytes_with_nul: &'static [u8]) -> &'static ZStr {
ZStr::from_static(bytes_with_nul)
}