Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/pm/cli/pm.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ To clear Bun's global module cache:
bun pm cache rm
```

Pass `--dry-run` to print the directories that would be deleted without deleting them.

## migrate

To migrate another package manager's lockfile without installing anything:
Expand All @@ -340,6 +342,8 @@ To migrate another package manager's lockfile without installing anything:
bun pm migrate
```

Pass `--dry-run` to run the migration without writing `bun.lock`.

## untrusted

To print current untrusted dependencies with scripts:
Expand All @@ -366,6 +370,7 @@ bun pm trust <names>
Options for the `trust` command:

- `--all`: Trust all untrusted dependencies.
- `--dry-run`: Print the scripts that would run and the packages that would be trusted, without running scripts or writing `package.json` and `bun.lock`.

## default-trusted

Expand Down Expand Up @@ -406,6 +411,7 @@ Options:
--message=<val>, -m Custom commit message, use %s for version substitution
--preid=<val> Prerelease identifier (i.e beta → 1.0.1-beta.0)
--force, -f Bypass dirty git history check
--dry-run Print the new version without writing, running scripts, or tagging

Examples:
bun pm version patch
Expand All @@ -423,7 +429,7 @@ bun pm version patch
v1.0.1
```

Supports `patch`, `minor`, `major`, `premajor`, `preminor`, `prepatch`, `prerelease`, `from-git`, or specific versions like `1.2.3`. By default it creates a git commit and tag; pass `--no-git-tag-version` to skip them.
Supports `patch`, `minor`, `major`, `premajor`, `preminor`, `prepatch`, `prerelease`, `from-git`, or specific versions like `1.2.3`. By default it creates a git commit and tag; pass `--no-git-tag-version` to skip them. Pass `--dry-run` to print the new version without writing `package.json`, running lifecycle scripts, or touching git.

## pkg

Expand Down Expand Up @@ -459,3 +465,5 @@ bun pm pkg delete scripts.test contributors[0] # multiple/nested
# fix
bun pm pkg fix # auto-fix common issues
```

`set`, `delete`, and `fix` accept `--dry-run`, which prints the resulting `package.json` instead of writing it.
4 changes: 2 additions & 2 deletions src/install/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ pub enum LockfileFormat {
}

impl LockfileFormat {
pub(crate) fn filename(self) -> &'static ZStr {
pub fn filename(self) -> &'static ZStr {
match self {
LockfileFormat::Text => zstr!("bun.lock"),
LockfileFormat::Binary => zstr!("bun.lockb"),
Expand Down Expand Up @@ -396,7 +396,7 @@ impl<'a> LoadResult<'a> {
}
}

pub(crate) fn save_format(&self, options: &PackageManagerOptions) -> LockfileFormat {
pub fn save_format(&self, options: &PackageManagerOptions) -> LockfileFormat {
match self {
LoadResult::NotFound => {
// saving a lockfile for a new project. default to text lockfile
Expand Down
19 changes: 17 additions & 2 deletions src/runtime/cli/link_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use bstr::BStr;

use bun_core::strings;
use bun_core::{Global, Output};
use bun_paths::AbsPath;
use bun_paths::{AbsPath, platform, resolve_path};
use bun_resolver::fs::FileSystem;
use bun_sys::{Dir, Fd, FdDirExt};

Expand Down Expand Up @@ -112,6 +112,22 @@ fn link(ctx: command::Context) -> crate::Result<()> {
// from `package_json_source` (dropped above).
let name = lockfile.str(&package.name);

if manager.options.dry_run {
if manager.options.log_level != LogLevel::Silent {
let link_path = resolve_path::join_abs_string_z::<platform::Auto>(
pm::global_link_dir_path(manager),
&[name],
);
bun_core::prettyln!(
"<r><d>dry run:<r> would link \"{}\" <d>at<r> {}",
BStr::new(name),
BStr::new(link_path.as_bytes()),
);
}
Output::flush();
Global::exit(0);
}

// Step 2. Setup the global directory
let node_modules: Dir = 'brk: {
bin::Linker::ensure_umask();
Expand Down Expand Up @@ -169,7 +185,6 @@ fn link(ctx: command::Context) -> crate::Result<()> {
#[cfg(windows)]
{
use bun_core::ZStr;
use bun_paths::{platform, resolve_path};
// create the junction
let top_level = FileSystem::instance().top_level_dir_without_trailing_slash();
let mut link_path_buf = bun_paths::path_buffer_pool::get();
Expand Down
91 changes: 62 additions & 29 deletions src/runtime/cli/package_manager_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ impl PackageManagerCommand {
<b><green>bun pm<r> <blue>migrate<r> migrate another package manager's lockfile without installing anything\n\
<b><green>bun pm<r> <blue>untrusted<r> print current untrusted dependencies with scripts\n\
<b><green>bun pm<r> <blue>trust<r> <d>names ...<r> run scripts for untrusted dependencies and add to `trustedDependencies`\n\
<d>└<r> <cyan>--all<r> trust all untrusted dependencies\n\
<d>├<r> <cyan>--all<r> trust all untrusted dependencies\n\
<d>└<r> <cyan>--dry-run<r> print the scripts that would run, without running them or saving\n\
<b><green>bun pm<r> <blue>default-trusted<r> print the default trusted dependencies list\n\
\n\
Learn more about these at <magenta>https://bun.com/docs/cli/pm<r>.\n";
Expand Down Expand Up @@ -436,38 +437,47 @@ Learn more about these at <magenta>https://bun.com/docs/cli/pm<r>.\n";
&& strings::eql_comptime(pm.options.positionals[1], b"rm")
{
let mut had_err = false;
let dry_run = pm.options.dry_run;

let mut process_env = bun_dotenv::Loader::init();
process_env.load_process()?;
let cache_dir = fetch_cache_directory_path(&mut process_env, None);
let mut rm_buf = bun_paths::path_buffer_pool::get();
let rm_dir = match Dir::cwd().make_open_path(&cache_dir.path, Default::default()) {
Ok(d) => d,
Err(err) => {
bun_core::pretty_errorln!(
"{} getting cache directory",
crate::Error::from(err).name(),
);
Global::crash();
}
};
let rm_path = match rm_dir.get_fd_path(&mut rm_buf) {
Ok(p) => &p[..],
Err(err) => {
bun_core::pretty_errorln!(
"{} getting cache directory",
crate::Error::from(err).name(),
);
Global::crash();
}
};
rm_dir.close();
if dry_run {
bun_core::prettyln!(
"<r><d>dry run:<r> would delete 'bun install' cache at {}",
bstr::BStr::new(&cache_dir.path),
);
} else {
let mut rm_buf = bun_paths::path_buffer_pool::get();
let rm_dir =
match Dir::cwd().make_open_path(&cache_dir.path, Default::default()) {
Ok(d) => d,
Err(err) => {
bun_core::pretty_errorln!(
"{} getting cache directory",
crate::Error::from(err).name(),
);
Global::crash();
}
};
let rm_path = match rm_dir.get_fd_path(&mut rm_buf) {
Ok(p) => &p[..],
Err(err) => {
bun_core::pretty_errorln!(
"{} getting cache directory",
crate::Error::from(err).name(),
);
Global::crash();
}
};
rm_dir.close();

if let Err(err) = bun_sys::delete_tree_absolute(rm_path) {
Output::err(err, "Could not delete {s}", (bstr::BStr::new(rm_path),));
had_err = true;
if let Err(err) = bun_sys::delete_tree_absolute(rm_path) {
Output::err(err, "Could not delete {s}", (bstr::BStr::new(rm_path),));
had_err = true;
}
bun_core::prettyln!("Cleared 'bun install' cache");
}
bun_core::prettyln!("Cleared 'bun install' cache");

'bunx: {
let tmp = Fs::RealFS::platform_temp_dir();
Expand Down Expand Up @@ -516,7 +526,14 @@ Learn more about these at <magenta>https://bun.com/docs/cli/pm<r>.\n";
};
let name = entry.name.slice_u8();
if name.starts_with(prefix.as_slice()) {
if let Err(err) = tmp_dir.delete_tree(name) {
if dry_run {
bun_core::prettyln!(
"<r><d>dry run:<r> would delete {}{}{}",
bstr::BStr::new(strings::without_trailing_slash(tmp)),
std::path::MAIN_SEPARATOR,
bstr::BStr::new(name),
);
} else if let Err(err) = tmp_dir.delete_tree(name) {
Output::err(err, "Could not delete {s}", (bstr::BStr::new(name),));
had_err = true;
continue;
Expand All @@ -526,7 +543,11 @@ Learn more about these at <magenta>https://bun.com/docs/cli/pm<r>.\n";
}
}

bun_core::prettyln!("Cleared {} cached 'bunx' packages", deleted);
if dry_run {
bun_core::prettyln!("Would clear {} cached 'bunx' packages", deleted);
} else {
bun_core::prettyln!("Cleared {} cached 'bunx' packages", deleted);
}
}

Global::exit(if had_err { 1 } else { 0 });
Expand Down Expand Up @@ -742,6 +763,18 @@ Learn more about these at <magenta>https://bun.com/docs/cli/pm<r>.\n";
Global::exit(1);
}
Self::handle_load_lockfile_errors(&load_lockfile, log_level);
// SAFETY: `pm_raw` singleton; `options` is CLI config set at init.
let options = unsafe { &(*pm_raw).options };
if options.dry_run {
if log_level != LogLevel::Silent {
bun_core::prettyln!(
"<r><d>dry run:<r> would write {}",
bstr::BStr::new(load_lockfile.save_format(options).filename().as_bytes()),
);
}
Output::flush();
Global::exit(0);
}
// Reshaped for borrowck — `save_to_disk` needs
// `&mut Lockfile` (self) and `&LoadResult` simultaneously, but
// `LoadResultOk.lockfile` already holds the only `&mut` into the
Expand Down
25 changes: 19 additions & 6 deletions src/runtime/cli/pm_pkg_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,14 +326,14 @@ impl PmPkgCommand {
}

if modified {
Self::save_package_json(&path, root, &pkg)?;
Self::save_package_json(&path, root, &pkg, pm.options.dry_run)?;
}
Ok(())
}

fn exec_delete(
ctx: &Context,
_pm: &mut PackageManager,
pm: &mut PackageManager,
args: &[&[u8]],
cwd: &[u8],
) -> Result<(), Error> {
Expand Down Expand Up @@ -369,12 +369,12 @@ impl PmPkgCommand {
}

if modified {
Self::save_package_json(&path, root, &pkg)?;
Self::save_package_json(&path, root, &pkg, pm.options.dry_run)?;
}
Ok(())
}

fn exec_fix(ctx: &Context, _pm: &mut PackageManager, cwd: &[u8]) -> Result<(), Error> {
fn exec_fix(ctx: &Context, pm: &mut PackageManager, cwd: &[u8]) -> Result<(), Error> {
let path = Self::find_package_json(cwd)?;

let pkg = Self::load_package_json(ctx, &path)?;
Expand Down Expand Up @@ -426,7 +426,7 @@ impl PmPkgCommand {
}

if modified {
Self::save_package_json(&path, root, &pkg)?;
Self::save_package_json(&path, root, &pkg, pm.options.dry_run)?;
}
Ok(())
}
Expand Down Expand Up @@ -825,7 +825,12 @@ impl PmPkgCommand {
Ok(true)
}

fn save_package_json(path: &[u8], root: Expr, pkg: &PackageJson) -> Result<(), Error> {
fn save_package_json(
path: &[u8],
root: Expr,
pkg: &PackageJson,
dry_run: bool,
) -> Result<(), Error> {
let preserve_newline =
!pkg.contents.is_empty() && pkg.contents[pkg.contents.len() - 1] == b'\n';

Expand Down Expand Up @@ -853,6 +858,14 @@ impl PmPkgCommand {
}

let content = writer.ctx.written_without_trailing_zero();
if dry_run {
let _ = Output::writer().write_all(content);
if !preserve_newline {
let _ = Output::writer().write_all(b"\n");
}
Output::flush();
return Ok(());
}
let path_z = bun_core::ZBox::from_bytes(path);
if let Err(e) = bun_sys::File::write_file(bun_sys::Fd::cwd(), path_z.as_zstr(), content) {
Output::err_generic(
Expand Down
47 changes: 47 additions & 0 deletions src/runtime/cli/pm_trusted_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,47 @@ impl TrustCommand {
}
}

fn print_dry_run(
lockfile: &Lockfile,
scripts_at_depth: &ArrayHashMap<usize, Vec<ScriptInfo>>,
package_names_to_add: &StringArrayHashMap<()>,
) {
let buf = lockfile.buffers.string_bytes.as_slice();
let resolutions = lockfile.packages.items_resolution();
let mut total_scripts: usize = 0;
let mut total_packages: usize = 0;

Output::print(format_args!("\n"));
for entry in scripts_at_depth.values().iter().rev() {
for info in entry.iter() {
if info.skip {
continue;
}
total_packages += 1;
total_scripts += info.scripts_list.total as usize;
info.scripts_list.print_scripts(
&resolutions[info.package_id as usize],
buf,
PrintFormat::Untrusted,
);
Output::print(format_args!("\n"));
}
}

bun_core::prettyln!(
"<r><d>dry run:<r> would run <b>{}<r> script{} across <b>{}<r> package{}",
total_scripts,
if total_scripts != 1 { "s" } else { "" },
total_packages,
if total_packages != 1 { "s" } else { "" },
);
bun_core::prettyln!("<r><d>dry run:<r> would add to trustedDependencies:");
for name in package_names_to_add.keys() {
bun_core::pretty!(" <d>-<r> {}\n", bstr::BStr::new(name));
}
Output::flush();
}

pub(crate) fn exec(
ctx: Command::Context,
pm: &mut PackageManager,
Expand Down Expand Up @@ -439,6 +480,12 @@ impl TrustCommand {
Global::crash();
}

// SAFETY: `pm_raw` singleton; `options` is CLI config set at init.
if unsafe { (*pm_raw).options.dry_run } {
Self::print_dry_run(lockfile, &scripts_at_depth, &package_names_to_add);
return Ok(());
}

let mut scripts_node: Progress::Node;
// SAFETY: `pm_raw` singleton; `progress` is owned inline.
let show_progress = unsafe { (*pm_raw).options.log_level.show_progress() };
Expand Down
Loading
Loading