Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ jobs:
node-version: 24
command: |
vp test run
- name: bun-vite-template
node-version: 24
command: |
vp run build
vp run test
exclude:
# frm-stack uses Docker (testcontainers) which doesn't work the same way on Windows
- os: windows-latest
Expand Down
2 changes: 1 addition & 1 deletion crates/vite_global_cli/src/shim/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const RECURSION_ENV_VAR: &str = env_vars::VITE_PLUS_TOOL_RECURSION;

/// Package manager tools that should resolve Node.js version from the project context
/// rather than using the install-time version.
const PACKAGE_MANAGER_TOOLS: &[&str] = &["pnpm", "yarn"];
const PACKAGE_MANAGER_TOOLS: &[&str] = &["pnpm", "yarn", "bun"];

fn is_package_manager_tool(tool: &str) -> bool {
PACKAGE_MANAGER_TOOLS.contains(&tool)
Expand Down
42 changes: 42 additions & 0 deletions crates/vite_install/src/commands/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{collections::HashMap, process::ExitStatus};
use vite_command::run_command;
use vite_error::Error;
use vite_path::AbsolutePath;
use vite_shared::output;

use crate::package_manager::{
PackageManager, PackageManagerType, ResolveCommandResult, format_path_env,
Expand Down Expand Up @@ -195,6 +196,47 @@ impl PackageManager {
args.push("--save-exact".into());
}
}
PackageManagerType::Bun => {
bin_name = "bun".into();
args.push("add".into());

if let Some(save_dependency_type) = options.save_dependency_type {
match save_dependency_type {
SaveDependencyType::Production => {
// default, no flag needed
}
SaveDependencyType::Dev => {
args.push("--dev".into());
}
SaveDependencyType::Peer => {
args.push("--peer".into());
}
SaveDependencyType::Optional => {
args.push("--optional".into());
}
}
}
if options.save_exact {
args.push("--exact".into());
}
if let Some(filters) = options.filters {
if !filters.is_empty() {
output::warn("bun add does not support --filter");
}
}
if options.workspace_root {
output::warn("bun add does not support --workspace-root");
}
if options.workspace_only {
output::warn("bun add does not support --workspace-only");
}
if options.save_catalog_name.is_some() {
output::warn("bun add does not support --save-catalog-name");
}
if options.allow_build.is_some() {
output::warn("bun add does not support --allow-build");
}
}
}

if let Some(pass_through_args) = options.pass_through_args {
Expand Down
86 changes: 86 additions & 0 deletions crates/vite_install/src/commands/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,28 @@ impl PackageManager {
}
}
}
PackageManagerType::Bun => {
bin_name = "bun".into();
args.push("audit".into());

if options.fix {
output::warn("bun audit does not support --fix");
return None;
}

if let Some(level) = options.level {
args.push("--audit-level".into());
args.push(level.to_string());
}

if options.production {
output::warn("--production not supported by bun audit, ignoring flag");
}

if options.json {
args.push("--json".into());
}
}
}

// Add pass-through args
Expand Down Expand Up @@ -320,6 +342,70 @@ mod tests {
assert_eq!(result.args, vec!["audit", "--level", "high"]);
}

#[test]
fn test_bun_audit_basic() {
let pm = create_mock_package_manager(PackageManagerType::Bun, "1.3.11");
let result = pm.resolve_audit_command(&AuditCommandOptions {
fix: false,
json: false,
level: None,
production: false,
pass_through_args: None,
});
assert!(result.is_some());
let result = result.unwrap();
assert_eq!(result.bin_path, "bun");
assert!(result.args.contains(&"audit".to_string()), "should contain 'audit'");
assert!(!result.args.contains(&"pm".to_string()), "should NOT use 'bun pm audit'");
}

#[test]
fn test_bun_audit_level() {
let pm = create_mock_package_manager(PackageManagerType::Bun, "1.3.11");
let result = pm.resolve_audit_command(&AuditCommandOptions {
fix: false,
json: false,
level: Some("high"),
production: false,
pass_through_args: None,
});
assert!(result.is_some());
let result = result.unwrap();
assert!(
result.args.contains(&"--audit-level".to_string()),
"should use --audit-level not --level"
);
assert!(result.args.contains(&"high".to_string()));
}

#[test]
fn test_bun_audit_fix_not_supported() {
let pm = create_mock_package_manager(PackageManagerType::Bun, "1.3.11");
let result = pm.resolve_audit_command(&AuditCommandOptions {
fix: true,
json: false,
level: None,
production: false,
pass_through_args: None,
});
assert!(result.is_none());
}

#[test]
fn test_bun_audit_json() {
let pm = create_mock_package_manager(PackageManagerType::Bun, "1.3.11");
let result = pm.resolve_audit_command(&AuditCommandOptions {
fix: false,
json: true,
level: None,
production: false,
pass_through_args: None,
});
assert!(result.is_some());
let result = result.unwrap();
assert_eq!(result.args, vec!["audit", "--json"]);
}

#[test]
fn test_audit_with_level_yarn2() {
let pm = create_mock_package_manager(PackageManagerType::Yarn, "4.0.0");
Expand Down
22 changes: 22 additions & 0 deletions crates/vite_install/src/commands/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,28 @@ impl PackageManager {
}
}
}
PackageManagerType::Bun => {
bin_name = "bun".into();

match options.subcommand {
"dir" | "path" => {
args.push("pm".into());
args.push("cache".into());
}
"clean" => {
args.push("pm".into());
args.push("cache".into());
args.push("rm".into());
}
_ => {
output::warn(&format!(
"bun pm cache subcommand '{}' not supported",
options.subcommand
));
return None;
}
}
}
}

// Add pass-through args
Expand Down
26 changes: 26 additions & 0 deletions crates/vite_install/src/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,32 @@ impl PackageManager {
}
}
}
PackageManagerType::Bun => {
output::warn(
"bun uses bunfig.toml for configuration, not a config command. Falling back to npm config.",
);

// Fall back to npm config
args.push("config".into());
args.push(options.subcommand.to_string());

if let Some(key) = options.key {
args.push(key.to_string());
}

if let Some(value) = options.value {
args.push(value.to_string());
}

if options.json {
args.push("--json".into());
}

if let Some(location) = options.location {
args.push("--location".into());
args.push(location.to_string());
}
}
}

// Add pass-through args
Expand Down
6 changes: 6 additions & 0 deletions crates/vite_install/src/commands/dedupe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{collections::HashMap, process::ExitStatus};
use vite_command::run_command;
use vite_error::Error;
use vite_path::AbsolutePath;
use vite_shared::output;

use crate::package_manager::{
PackageManager, PackageManagerType, ResolveCommandResult, format_path_env,
Expand Down Expand Up @@ -63,6 +64,11 @@ impl PackageManager {
args.push("--dry-run".into());
}
}
PackageManagerType::Bun => {
bin_name = "bun".into();
output::warn("bun does not support dedupe, falling back to bun install");
args.push("install".into());
}
}

// Add pass-through args
Expand Down
12 changes: 11 additions & 1 deletion crates/vite_install/src/commands/deprecate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ use std::{collections::HashMap, process::ExitStatus};
use vite_command::run_command;
use vite_error::Error;
use vite_path::AbsolutePath;
use vite_shared::output;

use crate::package_manager::{PackageManager, ResolveCommandResult, format_path_env};
use crate::package_manager::{
PackageManager, PackageManagerType, ResolveCommandResult, format_path_env,
};

/// Options for the deprecate command.
#[derive(Debug, Default)]
Expand All @@ -31,6 +34,7 @@ impl PackageManager {

/// Resolve the deprecate command.
/// All package managers delegate to npm deprecate.
/// Bun does not support deprecate, falls back to npm.
#[must_use]
pub fn resolve_deprecate_command(
&self,
Expand All @@ -40,6 +44,12 @@ impl PackageManager {
let envs = HashMap::from([("PATH".to_string(), format_path_env(self.get_bin_prefix()))]);
let mut args: Vec<String> = Vec::new();

if self.client == PackageManagerType::Bun {
output::warn(
"bun does not support the deprecate command, falling back to npm deprecate",
);
}

args.push("deprecate".into());
args.push(options.package.to_string());
args.push(options.message.to_string());
Expand Down
6 changes: 6 additions & 0 deletions crates/vite_install/src/commands/dist_tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{collections::HashMap, process::ExitStatus};
use vite_command::run_command;
use vite_error::Error;
use vite_path::AbsolutePath;
use vite_shared::output;

use crate::package_manager::{
PackageManager, PackageManagerType, ResolveCommandResult, format_path_env,
Expand Down Expand Up @@ -64,6 +65,11 @@ impl PackageManager {
args.push("tag".into());
}
}
PackageManagerType::Bun => {
output::warn("bun does not support dist-tag, falling back to npm dist-tag");
bin_name = "npm".into();
args.push("dist-tag".into());
}
}

match &options.subcommand {
Expand Down
31 changes: 31 additions & 0 deletions crates/vite_install/src/commands/dlx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl PackageManager {
self.resolve_yarn_dlx(options, envs)
}
}
PackageManagerType::Bun => self.resolve_bun_dlx(options, envs),
}
}

Expand Down Expand Up @@ -187,6 +188,36 @@ impl PackageManager {
let args = build_npx_args(options);
ResolveCommandResult { bin_path: "npx".into(), args, envs }
}

fn resolve_bun_dlx(
&self,
options: &DlxCommandOptions,
envs: HashMap<String, String>,
) -> ResolveCommandResult {
let mut args = Vec::new();

// Use `bun x` instead of `bunx` for better cross-platform compatibility.
// Some installation methods (e.g. mise) don't add bunx to PATH on Windows.
args.push("x".into());

// --packages flags must come before the package spec
for pkg in options.packages {
args.push("--package".into());
args.push(pkg.clone());
}

// Add package spec
args.push(options.package_spec.into());

// Add command arguments
args.extend(options.args.iter().cloned());

if options.shell_mode {
output::warn("bun x does not support shell mode (-c)");
}

ResolveCommandResult { bin_path: "bun".into(), args, envs }
}
}

/// Build npx command-line arguments from dlx options.
Expand Down
10 changes: 9 additions & 1 deletion crates/vite_install/src/commands/fund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ use std::{collections::HashMap, process::ExitStatus};
use vite_command::run_command;
use vite_error::Error;
use vite_path::AbsolutePath;
use vite_shared::output;

use crate::package_manager::{PackageManager, ResolveCommandResult, format_path_env};
use crate::package_manager::{
PackageManager, PackageManagerType, ResolveCommandResult, format_path_env,
};

/// Options for the fund command.
#[derive(Debug, Default)]
Expand All @@ -28,12 +31,17 @@ impl PackageManager {

/// Resolve the fund command.
/// All package managers delegate to npm fund.
/// Bun does not support fund, falls back to npm.
#[must_use]
pub fn resolve_fund_command(&self, options: &FundCommandOptions) -> ResolveCommandResult {
let bin_name: String = "npm".to_string();
let envs = HashMap::from([("PATH".to_string(), format_path_env(self.get_bin_prefix()))]);
let mut args: Vec<String> = Vec::new();

if self.client == PackageManagerType::Bun {
output::warn("bun does not support the fund command, falling back to npm fund");
}

args.push("fund".into());

if options.json {
Expand Down
Loading
Loading