diff --git a/completions/bun-cli.json b/completions/bun-cli.json index 6dfdac75a8f2..5bcf1c989fbd 100644 --- a/completions/bun-cli.json +++ b/completions/bun-cli.json @@ -503,6 +503,7 @@ }, { "name": "filter", + "shortName": "F", "description": "Install packages for the matching workspaces", "hasValue": true, "valueType": "val", @@ -523,6 +524,14 @@ "hasValue": false, "required": false, "multiple": false + }, + { + "name": "catalog", + "description": "Add the resolved version to the root package.json catalog and depend on it as \"catalog:\" (use --catalog=NAME for a named catalog)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false } ], "positionalArgs": [ @@ -813,6 +822,15 @@ "required": false, "multiple": false }, + { + "name": "filter", + "shortName": "F", + "description": "Add the package(s) to the matching workspaces instead of the current package", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, { "name": "analyze", "shortName": "a", @@ -827,6 +845,14 @@ "hasValue": false, "required": false, "multiple": false + }, + { + "name": "catalog", + "description": "Add the resolved version to the root package.json catalog and depend on it as \"catalog:\" (use --catalog=NAME for a named catalog)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false } ], "positionalArgs": [ @@ -1096,6 +1122,15 @@ "hasValue": false, "required": false, "multiple": false + }, + { + "name": "filter", + "shortName": "F", + "description": "Remove the package(s) from the matching workspaces instead of the current package", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false } ], "positionalArgs": [ @@ -1145,7 +1180,7 @@ { "name": "production", "shortName": "p", - "description": "Don't install devDependencies", + "description": "Only update dependencies and optionalDependencies (alias: --prod)", "hasValue": false, "required": false, "multiple": false @@ -1182,7 +1217,7 @@ }, { "name": "dry-run", - "description": "Don't install anything", + "description": "Perform a dry run without making changes", "hasValue": false, "required": false, "multiple": false @@ -1307,9 +1342,10 @@ }, { "name": "concurrent-scripts", - "description": "Maximum number of concurrent jobs for lifecycle scripts (default 5)", + "description": "Maximum number of concurrent jobs for lifecycle scripts (default: 2x CPU cores)", "hasValue": true, "valueType": "val", + "defaultValue": "2x", "required": false, "multiple": false }, @@ -1351,6 +1387,30 @@ "required": false, "multiple": false }, + { + "name": "minimum-release-age", + "description": "Only install packages published at least N seconds ago (security feature)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "cpu", + "description": "Override CPU architecture for optional dependencies (e.g., x64, arm64, * for all)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "os", + "description": "Override operating system for optional dependencies (e.g., linux, darwin, * for all)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, { "name": "help", "shortName": "h", @@ -1361,7 +1421,8 @@ }, { "name": "latest", - "description": "Update packages to their latest versions", + "shortName": "L", + "description": "Update packages to their latest versions, ignoring the ranges in package.json", "hasValue": false, "required": false, "multiple": false @@ -1376,6 +1437,7 @@ }, { "name": "filter", + "shortName": "F", "description": "Update packages for the matching workspaces", "hasValue": true, "valueType": "val", @@ -1389,6 +1451,29 @@ "hasValue": false, "required": false, "multiple": false + }, + { + "name": "dev", + "shortName": "d", + "description": "Only update devDependencies", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "no-optional", + "description": "Don't update optionalDependencies", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "exact", + "shortName": "E", + "description": "Write exact versions to package.json instead of ^ or ~ ranges", + "hasValue": false, + "required": false, + "multiple": false } ], "positionalArgs": [ @@ -1405,7 +1490,16 @@ "type": "string" } ], - "examples": ["bun update", "bun update --latest", "bun update -i", "bun update zod jquery@3"], + "examples": [ + "bun update", + "bun update --latest", + "bun update -i", + "bun update zod jquery@3", + "bun update '@types/*'", + "bun update '!webpack'", + "bun update --dev", + "bun update --prod" + ], "usage": "Usage: bun update [flags] @", "documentationUrl": "https://bun.com/docs/cli/update.", "dynamicCompletions": {} @@ -1420,6 +1514,22 @@ "hasValue": false, "required": false, "multiple": false + }, + { + "name": "audit-level", + "description": "Only print advisories at or above the given severity (low, moderate, high, critical)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "ignore", + "description": "Ignore advisories by GHSA or numeric advisory ID (repeatable)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false } ], "positionalArgs": [ @@ -1430,9 +1540,419 @@ "type": "string" } ], - "examples": ["bun audit", "bun audit --json"], + "examples": [ + "bun audit", + "bun audit --json", + "bun audit fix", + "bun audit fix --dry-run", + "bun audit fix --latest" + ], "usage": "Usage: bun audit [flags]", "documentationUrl": "https://bun.com/docs/install/audit.", + "dynamicCompletions": {}, + "subcommands": { + "fix": { + "name": "fix", + "description": "Upgrade vulnerable packages to the lowest safe version that still satisfies every dependent's range", + "flags": [ + { + "name": "dry-run", + "description": "Show what bun audit fix would change without changing anything", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "latest", + "shortName": "L", + "description": "Also update past the ranges declared in package.json and catalogs, rewriting them to accept the fix", + "hasValue": false, + "required": false, + "multiple": false + } + ] + } + } + }, + "dedupe": { + "name": "dedupe", + "description": "Remove duplicate versions from bun.lock by re-resolving dependency ranges onto versions that are already in the lockfile, then install.", + "flags": [ + { + "name": "config", + "shortName": "c", + "description": "Specify path to config file (bunfig.toml)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "yarn", + "shortName": "y", + "description": "Write a yarn.lock file (yarn v1)", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "production", + "shortName": "p", + "description": "Don't install devDependencies", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "no-save", + "description": "Don't update package.json or save a lockfile", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "save", + "description": "Save to package.json (true by default)", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "ca", + "description": "Provide a Certificate Authority signing certificate", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "cafile", + "description": "The same as `--ca`, but is a file path to the certificate", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "dry-run", + "description": "Perform a dry run without making changes", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "frozen-lockfile", + "description": "Disallow changes to lockfile", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "force", + "shortName": "f", + "description": "Always request the latest versions from the registry & reinstall all dependencies", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "cache-dir", + "description": "Store & load cached data from a specific directory path", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "no-cache", + "description": "Ignore manifest cache entirely", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "silent", + "description": "Don't log anything", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "quiet", + "description": "Only show tarball name when packing", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "verbose", + "description": "Excessively verbose logging", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "no-progress", + "description": "Disable the progress bar", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "no-summary", + "description": "Don't print a summary", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "no-verify", + "description": "Skip verifying integrity of newly downloaded packages", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "ignore-scripts", + "description": "Skip lifecycle scripts in the project's package.json (dependency scripts are never run)", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "trust", + "description": "Add to trustedDependencies in the project's package.json and install the package(s)", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "global", + "shortName": "g", + "description": "Install globally", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "cwd", + "description": "Set a specific cwd", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "backend", + "description": "Platform-specific optimizations for installing dependencies. Possible values: \"clonefile\" (default), \"hardlink\", \"symlink\", \"copyfile\"", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "registry", + "description": "Use a specific registry by default, overriding .npmrc, bunfig.toml and environment variables", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "concurrent-scripts", + "description": "Maximum number of concurrent jobs for lifecycle scripts (default: 2x CPU cores)", + "hasValue": true, + "valueType": "val", + "defaultValue": "2x", + "required": false, + "multiple": false + }, + { + "name": "network-concurrency", + "description": "Maximum number of concurrent network requests (default 48)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "save-text-lockfile", + "description": "Save a text-based lockfile", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "omit", + "description": "Exclude 'dev', 'optional', or 'peer' dependencies from install", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "lockfile-only", + "description": "Generate a lockfile without installing dependencies", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "linker", + "description": "Linker strategy (one of \"isolated\" or \"hoisted\")", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "minimum-release-age", + "description": "Only install packages published at least N seconds ago (security feature)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "cpu", + "description": "Override CPU architecture for optional dependencies (e.g., x64, arm64, * for all)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "os", + "description": "Override operating system for optional dependencies (e.g., linux, darwin, * for all)", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "help", + "shortName": "h", + "description": "Print this help menu", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "check", + "description": "Exit with code 1 if the lockfile has duplicate versions that can be removed, without changing anything", + "hasValue": false, + "required": false, + "multiple": false + } + ], + "positionalArgs": [ + { + "name": "flags", + "required": false, + "multiple": false, + "type": "string" + } + ], + "examples": ["bun dedupe", "bun dedupe --check", "bun dedupe --lockfile-only"], + "usage": "Usage: bun dedupe [flags]", + "documentationUrl": "https://bun.com/docs/pm/cli/dedupe.", + "dynamicCompletions": {} + }, + "prune": { + "name": "prune", + "description": "Remove packages from node_modules that are not in bun.lock. With --production, also remove packages that are only needed by devDependencies.", + "flags": [ + { + "name": "production", + "shortName": "p", + "description": "Also remove packages that are only needed by devDependencies (alias: --prod)", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "omit", + "description": "Also remove packages that are only needed by the given dependency types", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "dry-run", + "description": "Print what would be removed without deleting anything", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "os", + "description": "Prune for a different operating system than the current one", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "cpu", + "description": "Prune for a different CPU architecture than the current one", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "linker", + "description": "Prune a node_modules installed with the given linker (one of \"isolated\" or \"hoisted\")", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "filter", + "shortName": "F", + "description": "Only prune the node_modules folders of the matching workspaces; the shared store / hoisted root folder keeps everything other workspaces use", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": true + }, + { + "name": "silent", + "description": "Don't log anything", + "hasValue": false, + "required": false, + "multiple": false + }, + { + "name": "cwd", + "description": "Set a specific cwd", + "hasValue": true, + "valueType": "val", + "required": false, + "multiple": false + }, + { + "name": "help", + "shortName": "h", + "description": "Print this help menu", + "hasValue": false, + "required": false, + "multiple": false + } + ], + "positionalArgs": [ + { + "name": "flags", + "required": false, + "multiple": false, + "type": "string" + } + ], + "examples": ["bun prune", "bun prune --production", "bun prune --dry-run"], + "usage": "Usage: bun prune [flags]", + "documentationUrl": "https://bun.com/docs/pm/cli/prune.", "dynamicCompletions": {} }, "outdated": { @@ -2858,6 +3378,12 @@ "flags": [], "positionalArgs": [] }, + "licenses": { + "name": "licenses", + "description": "list installed packages grouped by license", + "flags": [], + "positionalArgs": [] + }, "whoami": { "name": "whoami", "description": "print the current npm username", diff --git a/completions/bun.bash b/completions/bun.bash index 7040f0ca51b2..29304d7e69ca 100644 --- a/completions/bun.bash +++ b/completions/bun.bash @@ -89,20 +89,28 @@ _bun_completions() { declare -A PACKAGE_OPTIONS; declare -A PM_OPTIONS; - local SUBCOMMANDS="dev bun create run install add remove upgrade completions discord help init pm x test repl update outdated link unlink build"; + local SUBCOMMANDS="dev bun create run install add remove upgrade completions discord help init pm x test repl update audit dedupe prune outdated link unlink build"; GLOBAL_OPTIONS[LONG_OPTIONS]="--use --cwd --bunfile --server-bunfile --config --disable-react-fast-refresh --disable-hmr --env-file --extension-order --jsx-factory --jsx-fragment --extension-order --jsx-factory --jsx-fragment --jsx-import-source --jsx-production --jsx-runtime --main-fields --no-summary --version --platform --public-dir --tsconfig-override --define --external --help --inject --loader --origin --port --dump-environment-variables --dump-limits --disable-bun-js"; GLOBAL_OPTIONS[SHORT_OPTIONS]="-c -v -d -e -h -i -l -u -p"; - PACKAGE_OPTIONS[ADD_OPTIONS_LONG]="--development --optional --peer"; - PACKAGE_OPTIONS[ADD_OPTIONS_SHORT]="-d"; - PACKAGE_OPTIONS[REMOVE_OPTIONS_LONG]=""; - PACKAGE_OPTIONS[REMOVE_OPTIONS_SHORT]=""; + PACKAGE_OPTIONS[ADD_OPTIONS_LONG]="--development --optional --peer --catalog --filter"; + PACKAGE_OPTIONS[ADD_OPTIONS_SHORT]="-d -F"; + PACKAGE_OPTIONS[REMOVE_OPTIONS_LONG]="--filter"; + PACKAGE_OPTIONS[REMOVE_OPTIONS_SHORT]="-F"; + PACKAGE_OPTIONS[UPDATE_OPTIONS_LONG]="--latest --interactive --recursive --filter --dev --development --prod --no-optional --exact"; + PACKAGE_OPTIONS[UPDATE_OPTIONS_SHORT]="-L -i -r -F -d -D -P -E"; PACKAGE_OPTIONS[SHARED_OPTIONS_LONG]="--config --yarn --production --frozen-lockfile --no-save --dry-run --force --cache-dir --no-cache --silent --verbose --global --cwd --backend --link-native-bins --help"; PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]="-c -y -p -f -g"; - PM_OPTIONS[LONG_OPTIONS]="--config --yarn --production --frozen-lockfile --no-save --dry-run --force --cache-dir --no-cache --silent --verbose --no-progress --no-summary --no-verify --ignore-scripts --global --cwd --backend --link-native-bins --help" + PACKAGE_OPTIONS[DEDUPE_OPTIONS_LONG]="--check"; + PACKAGE_OPTIONS[PRUNE_OPTIONS_LONG]="--production --prod --omit --filter --dry-run --os --cpu --linker --silent --cwd --help"; + PACKAGE_OPTIONS[PRUNE_OPTIONS_SHORT]="-p -P -F -h"; + PACKAGE_OPTIONS[AUDIT_OPTIONS_LONG]="--json --audit-level --ignore --prod --production --omit --dry-run --latest --cwd --help"; + PACKAGE_OPTIONS[AUDIT_OPTIONS_SHORT]="-L"; + + PM_OPTIONS[LONG_OPTIONS]="--config --yarn --production --frozen-lockfile --no-save --dry-run --force --cache-dir --no-cache --silent --verbose --no-progress --no-summary --no-verify --ignore-scripts --global --cwd --backend --link-native-bins --json --help" PM_OPTIONS[SHORT_OPTIONS]="-c -y -p -f -g" local cur_word="${COMP_WORDS[${COMP_CWORD}]}"; @@ -115,11 +123,22 @@ _bun_completions() { --server-bunfile) _file_arguments "!*.server.bun" && return;; --backend) case "${COMP_WORDS[1]}" in - a|add|remove|rm|install|i) + a|add|remove|rm|install|i|dedupe|update|up) COMPREPLY=( $(compgen -W "clonefile copyfile hardlink clonefile_each_dir symlink" -- "${cur_word}") ); ;; esac return ;; + --omit) + COMPREPLY=( $(compgen -W "dev optional peer" -- "${cur_word}") ); + return;; + -F|--filter) + case "${COMP_WORDS[1]}" in + a|add|remove|rm|i|install|prune|update|up) return;; + esac + ;; + --linker) + COMPREPLY=( $(compgen -W "isolated hoisted" -- "${cur_word}") ); + return;; --cwd|--public-dir) COMPREPLY=( $(compgen -d -- "${cur_word}" )); return;; @@ -144,11 +163,34 @@ _bun_completions() { "${PACKAGE_OPTIONS[ADD_OPTIONS_LONG]} ${PACKAGE_OPTIONS[ADD_OPTIONS_SHORT]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_LONG]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]}" \ "${PACKAGE_OPTIONS[ADD_OPTIONS_SHORT]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]}" return;; - remove|rm|i|install|link|unlink) + remove|rm|i|install) _long_short_completion \ "${PACKAGE_OPTIONS[REMOVE_OPTIONS_LONG]} ${PACKAGE_OPTIONS[REMOVE_OPTIONS_SHORT]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_LONG]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]}" \ "${PACKAGE_OPTIONS[REMOVE_OPTIONS_SHORT]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]}"; return;; + update|up) + _long_short_completion \ + "${PACKAGE_OPTIONS[UPDATE_OPTIONS_LONG]} ${PACKAGE_OPTIONS[UPDATE_OPTIONS_SHORT]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_LONG]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]}" \ + "${PACKAGE_OPTIONS[UPDATE_OPTIONS_SHORT]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]}"; + return;; + link|unlink) + _long_short_completion \ + "${PACKAGE_OPTIONS[SHARED_OPTIONS_LONG]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]}" \ + "${PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]}"; + return;; + dedupe) + _long_short_completion \ + "${PACKAGE_OPTIONS[DEDUPE_OPTIONS_LONG]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_LONG]} ${PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]}" \ + "${PACKAGE_OPTIONS[SHARED_OPTIONS_SHORT]}"; + return;; + prune) + _long_short_completion \ + "${PACKAGE_OPTIONS[PRUNE_OPTIONS_LONG]} ${PACKAGE_OPTIONS[PRUNE_OPTIONS_SHORT]}" \ + "${PACKAGE_OPTIONS[PRUNE_OPTIONS_SHORT]}"; + return;; + audit) + COMPREPLY=( $(compgen -W "fix ${PACKAGE_OPTIONS[AUDIT_OPTIONS_LONG]} ${PACKAGE_OPTIONS[AUDIT_OPTIONS_SHORT]}" -- "${cur_word}") ); + return;; create|c) COMPREPLY=( $(compgen -W "--force --no-install --help --no-git --verbose --no-package-json --open next react" -- "${cur_word}") ); return;; @@ -166,7 +208,7 @@ _bun_completions() { pm) _long_short_completion \ "${PM_OPTIONS[LONG_OPTIONS]} ${PM_OPTIONS[SHORT_OPTIONS]}"; - COMPREPLY+=( $(compgen -W "bin ls cache hash hash-print hash-string" -- "${cur_word}") ); + COMPREPLY+=( $(compgen -W "bin ls licenses cache hash hash-print hash-string" -- "${cur_word}") ); return;; *) local replaced_script; diff --git a/completions/bun.fish b/completions/bun.fish index 6b2dc5bc9bcd..5e94166f5ecb 100644 --- a/completions/bun.fish +++ b/completions/bun.fish @@ -35,7 +35,7 @@ end set -l bun_install_boolean_flags yarn production optional development no-save dry-run force no-cache silent verbose global set -l bun_install_boolean_flags_descriptions "Write a yarn.lock file (yarn v1)" "Don't install devDependencies" "Add dependency to optionalDependencies" "Add dependency to devDependencies" "Don't update package.json or save a lockfile" "Don't install anything" "Always request the latest versions from the registry & reinstall all dependencies" "Ignore manifest cache entirely" "Don't output anything" "Excessively verbose logging" "Use global folder" -set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add update init pm x repl +set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add update audit dedupe prune init pm x repl set -l bun_builtin_cmds_accepting_flags create help bun upgrade discord run init link unlink pm x update function __bun_complete_bins_scripts --inherit-variable bun_builtin_cmds_without_run -d "Emit bun completions for bins and scripts" @@ -148,14 +148,23 @@ complete -c bun \ for i in (seq (count $bun_install_boolean_flags)) complete -c bun \ - -n "__fish_seen_subcommand_from install add remove update" -l "$bun_install_boolean_flags[$i]" -d "$bun_install_boolean_flags_descriptions[$i]" + -n "__fish_seen_subcommand_from install add remove dedupe" -l "$bun_install_boolean_flags[$i]" -d "$bun_install_boolean_flags_descriptions[$i]" end complete -c bun \ - -n "__fish_seen_subcommand_from install add remove update" -l 'cwd' -d 'Change working directory' + -n "__fish_seen_subcommand_from install add remove update dedupe" -l 'cwd' -d 'Change working directory' complete -c bun \ - -n "__fish_seen_subcommand_from install add remove update" -l 'cache-dir' -d 'Choose a cache directory (default: $HOME/.bun/install/cache)' + -n "__fish_seen_subcommand_from install add remove update dedupe" -l 'cache-dir' -d 'Choose a cache directory (default: $HOME/.bun/install/cache)' + +complete -c bun \ + -n "__fish_seen_subcommand_from install add remove" -s 'F' -l 'filter' -r -d 'Apply to the matching workspaces instead of the current package' + +complete -c bun \ + -n "__fish_seen_subcommand_from install add" -l 'catalog' -d 'Add the resolved version to the root package.json catalog and depend on it as "catalog:" (--catalog=NAME for a named catalog)' + +complete -c bun \ + -n "__fish_seen_subcommand_from dedupe" -l 'check' -d 'Exit with code 1 if the lockfile has duplicate versions that can be removed, without changing anything' complete -c bun \ -n "__fish_seen_subcommand_from add" -d 'Popular' -a '(__fish__get_bun_packages)' @@ -164,11 +173,29 @@ complete -c bun \ -n "__fish_seen_subcommand_from add" -d 'History' -a '(__history_completions)' complete -c bun \ - -n "__fish_seen_subcommand_from pm; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts) cache;" -a 'bin ls cache hash hash-print hash-string' -f + -n "__fish_seen_subcommand_from pm; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts) cache;" -a 'bin ls licenses cache hash hash-print hash-string' -f complete -c bun \ -n "__fish_seen_subcommand_from pm; and __fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts);" -a 'rm' -f +complete -c bun \ + -n "__fish_seen_subcommand_from pm; and __fish_seen_subcommand_from licenses" -l 'json' -d 'Output as JSON' -f + +complete -c bun \ + -n "__fish_seen_subcommand_from pm; and __fish_seen_subcommand_from licenses" -l 'prod' -d 'Omit devDependencies' -f + +complete -c bun \ + -n "__fish_seen_subcommand_from pm; and __fish_seen_subcommand_from licenses" -l 'production' -d 'Omit devDependencies' -f + +complete -c bun \ + -n "__fish_seen_subcommand_from pm; and __fish_seen_subcommand_from licenses" -l 'dev' -s 'D' -d 'List only what devDependencies pull in' -f + +complete -c bun \ + -n "__fish_seen_subcommand_from pm; and __fish_seen_subcommand_from licenses" -l 'long' -d 'Also print author, description and homepage' -f + +complete -c bun \ + -n "__fish_seen_subcommand_from pm; and __fish_seen_subcommand_from licenses" -l 'filter' -s 'F' -d 'List only the matching workspaces' -r + # Add built-in subcommands with descriptions. complete -c bun -n "__fish_use_subcommand" -a "create" -f -d "Create a new project from a template" complete -c bun -n "__fish_use_subcommand" -a "build bun" --require-parameter -F -d "Transpile and bundle one or more files" @@ -183,7 +210,44 @@ complete -c bun -n "__fish_use_subcommand" -a "unlink" -d "Unregister a local np complete -c bun -n "__fish_use_subcommand" -a "pm" -d "Additional package management utilities" -f complete -c bun -n "__fish_use_subcommand" -a "x" -d "Execute a package binary, installing if needed" -f complete -c bun -n "__fish_use_subcommand" -a "outdated" -d "Display the latest versions of outdated dependencies" -f +complete -c bun -n "__fish_use_subcommand" -a "audit" -d "Check installed packages for vulnerabilities" -f +complete -c bun -n "__fish_use_subcommand" -a "dedupe" -d "Remove duplicate versions from the lockfile" -f +complete -c bun -n "__fish_use_subcommand" -a "prune" -d "Remove packages that are not in the lockfile from node_modules" -f +complete -c bun -n "__fish_seen_subcommand_from audit; and not __fish_seen_subcommand_from fix" -a "fix" -d "Upgrade vulnerable packages to the lowest safe version" -f +complete -c bun -n "__fish_seen_subcommand_from audit" -l "json" -d "Output in JSON format" -f +complete -c bun -n "__fish_seen_subcommand_from audit" -l "audit-level" -r -a "low moderate high critical" -d "Only print advisories at or above this severity" -f +complete -c bun -n "__fish_seen_subcommand_from audit" -l "ignore" -r -d "Ignore advisories by GHSA or numeric advisory ID" -f +complete -c bun -n "__fish_seen_subcommand_from audit" -l "prod" -d "Omit devDependencies" -f +complete -c bun -n "__fish_seen_subcommand_from audit prune" -l "omit" -r -a "dev optional peer" -d "Omit the given dependency type" -f +complete -c bun -n "__fish_seen_subcommand_from audit prune" -l "dry-run" -d "Print what would change without changing anything" -f +complete -c bun -n "__fish_seen_subcommand_from audit; and __fish_seen_subcommand_from fix" -s "L" -l "latest" -d "Also apply fixes that fall outside the ranges declared in package.json or catalogs" -f +complete -c bun -n "__fish_seen_subcommand_from prune" -s "p" -l "production" -d "Also remove packages that are only needed by devDependencies" -f +complete -c bun -n "__fish_seen_subcommand_from prune" -s "P" -l "prod" -d "Also remove packages that are only needed by devDependencies" -f +complete -c bun -n "__fish_seen_subcommand_from prune" -l "os" -r -d "Prune for a different operating system than the current one" -f +complete -c bun -n "__fish_seen_subcommand_from prune" -l "cpu" -r -d "Prune for a different CPU architecture than the current one" -f +complete -c bun -n "__fish_seen_subcommand_from prune" -l "linker" -r -a "isolated hoisted" -d "Prune a node_modules installed with the given linker" -f +complete -c bun -n "__fish_seen_subcommand_from prune" -s "F" -l "filter" -r -d "Prune only the matching workspaces" -f +complete -c bun -n "__fish_seen_subcommand_from prune" -l "silent" -d "Don't log anything" -f +complete -c bun -n "__fish_seen_subcommand_from audit prune" -l "cwd" -r -d "Set a specific cwd" complete -c bun -n "__fish_use_subcommand" -a "update" -d "Update dependencies to their latest versions" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "p" -l "production" -d "Only update dependencies and optionalDependencies" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "P" -l "prod" -d "Only update dependencies and optionalDependencies" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "d" -l "dev" -d "Only update devDependencies" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "D" -l "development" -d "Only update devDependencies" -f +complete -c bun -n "__fish_seen_subcommand_from update" -l "no-optional" -d "Don't update optionalDependencies" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "E" -l "exact" -d "Write exact versions to package.json instead of ^ or ~ ranges" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "L" -l "latest" -d "Update packages to their latest versions, ignoring the ranges in package.json" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "i" -l "interactive" -d "Show an interactive list of outdated packages to select for update" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "r" -l "recursive" -d "Update packages in all workspaces" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "F" -l "filter" -r -d "Update packages for the matching workspaces" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "g" -l "global" -d "Update the packages installed globally" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "y" -l "yarn" -d "Write a yarn.lock file (yarn v1)" -f +complete -c bun -n "__fish_seen_subcommand_from update" -l "no-save" -d "Don't update package.json or save a lockfile" -f +complete -c bun -n "__fish_seen_subcommand_from update" -l "dry-run" -d "Perform a dry run without making changes" -f +complete -c bun -n "__fish_seen_subcommand_from update" -s "f" -l "force" -d "Always request the latest versions from the registry & reinstall all dependencies" -f +complete -c bun -n "__fish_seen_subcommand_from update" -l "no-cache" -d "Ignore manifest cache entirely" -f +complete -c bun -n "__fish_seen_subcommand_from update" -l "silent" -d "Don't log anything" -f +complete -c bun -n "__fish_seen_subcommand_from update" -l "verbose" -d "Excessively verbose logging" -f complete -c bun -n "__fish_use_subcommand" -a "publish" -d "Publish your package from local to npm" -f complete -c bun -n "__fish_use_subcommand" -a "repl" -d "Start a REPL session with Bun" -f complete -c bun -n "__fish_seen_subcommand_from repl" -s "e" -l "eval" -r -d "Evaluate argument as a script, then exit" -f diff --git a/completions/bun.zsh b/completions/bun.zsh index 9ab15deedf66..3a02d58b200c 100644 --- a/completions/bun.zsh +++ b/completions/bun.zsh @@ -36,7 +36,10 @@ _bun_add_completion() { '--development[]' \ '--optional[Add dependency to "optionalDependencies]' \ '--peer[Add dependency to "peerDependencies]' \ - '--exact[Add the exact version instead of the ^range]' && + '--exact[Add the exact version instead of the ^range]' \ + '--catalog=-[Add the resolved version to the root package.json catalog and depend on it as "catalog:"]:catalog' \ + '*--filter[Add the package(s) to the matching workspaces instead of the current package]:workspace pattern' \ + '*-F[Add the package(s) to the matching workspaces instead of the current package]:workspace pattern' && ret=0 case $state in @@ -257,6 +260,7 @@ _bun_pm_completion() { sub_commands=( 'bin\:"print the path to bin folder" ' 'ls\:"list the dependency tree according to the current lockfile" ' + 'licenses\:"list installed packages grouped by license" ' 'hash\:"generate & print the hash of the current lockfile" ' 'hash-string\:"print the string used to hash the lockfile" ' 'hash-print\:"print the hash stored in the current lockfile" ' @@ -295,6 +299,25 @@ _bun_pm_completion() { "--trusted[list only trusted dependencies]" ) + _arguments -s -C \ + '1: :->cmd' \ + '2: :->cmd2' \ + $pmargs && + ret=0 + + ;; + licenses) + pmargs=( + "--json[output as JSON]" + "--prod[omit devDependencies]" + "--production[omit devDependencies]" + "--dev[list only what devDependencies pull in]" + "-D[list only what devDependencies pull in]" + "--long[also print author, description and homepage]" + "*--filter[list only the matching workspaces' dependencies]:workspace pattern" + "*-F[list only the matching workspaces' dependencies]:workspace pattern" + ) + _arguments -s -C \ '1: :->cmd' \ '2: :->cmd2' \ @@ -377,7 +400,10 @@ _bun_install_completion() { '-D[]' \ '--optional[Add dependency to "optionalDependencies]' \ '--peer[Add dependency to "peerDependencies]' \ - '--exact[Add the exact version instead of the ^range]' && + '--exact[Add the exact version instead of the ^range]' \ + '--catalog=-[Add the resolved version to the root package.json catalog and depend on it as "catalog:"]:catalog' \ + '*--filter[Install packages for the matching workspaces]:workspace pattern' \ + '*-F[Install packages for the matching workspaces]:workspace pattern' && ret=0 case $state in @@ -417,7 +443,9 @@ _bun_remove_completion() { '--cwd[Set a specific cwd]:cwd' \ '--backend[Platform-specific optimizations for installing dependencies]:backend:("copyfile" "hardlink" "symlink")' \ '--link-native-bins[Link "bin" from a matching platform-specific dependency instead. Default: esbuild, turbo]:link-native-bins' \ - '--help[Print this help menu]' && + '--help[Print this help menu]' \ + '*--filter[Remove the package(s) from the matching workspaces instead of the current package]:workspace pattern' \ + '*-F[Remove the package(s) from the matching workspaces instead of the current package]:workspace pattern' && ret=0 case $state in @@ -596,13 +624,29 @@ _bun_update_completion() { '--config[Load config(bunfig.toml)]: :->config' \ '-y[Write a yarn.lock file (yarn v1)]' \ '--yarn[Write a yarn.lock file (yarn v1)]' \ - '-p[Don'"'"'t install devDependencies]' \ - '--production[Don'"'"'t install devDependencies]' \ + '-p[Only update dependencies and optionalDependencies]' \ + '--production[Only update dependencies and optionalDependencies]' \ + '-P[Only update dependencies and optionalDependencies]' \ + '--prod[Only update dependencies and optionalDependencies]' \ + '-d[Only update devDependencies]' \ + '--dev[Only update devDependencies]' \ + '-D[Only update devDependencies]' \ + '--development[Only update devDependencies]' \ + '--no-optional[Don'"'"'t update optionalDependencies]' \ + '-E[Write exact versions to package.json instead of ^ or ~ ranges]' \ + '--exact[Write exact versions to package.json instead of ^ or ~ ranges]' \ '--no-save[Don'"'"'t save a lockfile]' \ '--save[Save to package.json]' \ '--dry-run[Don'"'"'t install anything]' \ '--frozen-lockfile[Disallow changes to lockfile]' \ - '--latest[Updates dependencies to latest version, regardless of compatibility]' \ + '-L[Update packages to their latest versions, ignoring the ranges in package.json]' \ + '--latest[Update packages to their latest versions, ignoring the ranges in package.json]' \ + '-i[Show an interactive list of outdated packages to select for update]' \ + '--interactive[Show an interactive list of outdated packages to select for update]' \ + '-r[Update packages in all workspaces]' \ + '--recursive[Update packages in all workspaces]' \ + '*--filter[Update packages for the matching workspaces]:workspace pattern' \ + '*-F[Update packages for the matching workspaces]:workspace pattern' \ '-f[Always request the latest versions from the registry & reinstall all dependencies]' \ '--force[Always request the latest versions from the registry & reinstall all dependencies]' \ '--cache-dir[Store & load cached data from a specific directory path]:cache-dir' \ @@ -645,6 +689,91 @@ _bun_outdated_completion() { esac } +_bun_dedupe_completion() { + _arguments -s -C \ + '1: :->cmd1' \ + '--check[Exit with code 1 if the lockfile has duplicate versions that can be removed, without changing anything]' \ + '-c[Load config(bunfig.toml)]: :->config' \ + '--config[Load config(bunfig.toml)]: :->config' \ + '-y[Write a yarn.lock file (yarn v1)]' \ + '--yarn[Write a yarn.lock file (yarn v1)]' \ + '-p[Don'"'"'t install devDependencies]' \ + '--production[Don'"'"'t install devDependencies]' \ + '--no-save[Don'"'"'t save a lockfile]' \ + '--save[Save to package.json]' \ + '--dry-run[Don'"'"'t install anything]' \ + '--frozen-lockfile[Disallow changes to lockfile]' \ + '--lockfile-only[Generate a lockfile without installing dependencies]' \ + '-f[Always request the latest versions from the registry & reinstall all dependencies]' \ + '--force[Always request the latest versions from the registry & reinstall all dependencies]' \ + '--cache-dir[Store & load cached data from a specific directory path]:cache-dir' \ + '--no-cache[Ignore manifest cache entirely]' \ + '--silent[Don'"'"'t log anything]' \ + '--verbose[Excessively verbose logging]' \ + '--no-progress[Disable the progress bar]' \ + '--no-summary[Don'"'"'t print a summary]' \ + '--no-verify[Skip verifying integrity of newly downloaded packages]' \ + '--ignore-scripts[Skip lifecycle scripts in the package.json (dependency scripts are never run)]' \ + '--cwd[Set a specific cwd]:cwd' \ + '--backend[Platform-specific optimizations for installing dependencies]:backend:("copyfile" "hardlink" "symlink")' \ + '--linker[Linker strategy]:linker:(isolated hoisted)' \ + '--help[Print this help menu]' && + ret=0 + + case $state in + config) + _bun_list_bunfig_toml + + ;; + esac +} + +_bun_prune_completion() { + _arguments -s -C \ + '1: :->cmd1' \ + '-p[Also remove packages that are only needed by devDependencies]' \ + '--production[Also remove packages that are only needed by devDependencies]' \ + '-P[Also remove packages that are only needed by devDependencies]' \ + '--prod[Also remove packages that are only needed by devDependencies]' \ + '*--omit[Also remove packages that are only needed by the given dependency types]:type:(dev optional peer)' \ + '--dry-run[Print what would be removed without deleting anything]' \ + '*--os[Prune for a different operating system than the current one]:os' \ + '*--cpu[Prune for a different CPU architecture than the current one]:cpu' \ + '--linker[Prune a node_modules installed with the given linker]:linker:(isolated hoisted)' \ + '*--filter[Only prune the node_modules folders of the matching workspaces]:workspace pattern' \ + '*-F[Only prune the node_modules folders of the matching workspaces]:workspace pattern' \ + '--silent[Don'"'"'t log anything]' \ + '--cwd[Set a specific cwd]:cwd' \ + '-h[Print this help menu]' \ + '--help[Print this help menu]' && + ret=0 +} + +_bun_audit_completion() { + _arguments -s -C \ + '1: :->cmd1' \ + '2: :->subcommand' \ + '--json[Output in JSON format]' \ + '--audit-level[Only print advisories with severity greater than or equal to the given level]:level:(low moderate high critical)' \ + '*--ignore[Ignore advisories by GHSA or numeric advisory ID]:advisory' \ + '--prod[Omit devDependencies]' \ + '--production[Omit devDependencies]' \ + '*--omit[Exclude dependency types from the audit]:type:(dev optional peer)' \ + '--dry-run[Show what bun audit fix would change without changing anything]' \ + '--latest[Let bun audit fix update direct dependencies past their declared ranges]' \ + '-L[Let bun audit fix update direct dependencies past their declared ranges]' \ + '--cwd[Set a specific cwd]:cwd' \ + '--help[Print this help menu]' && + ret=0 + + case $state in + subcommand) + _alternative 'args:subcommand:((fix\:"Upgrade vulnerable packages to the lowest safe version"))' + + ;; + esac +} + _bun_test_completion() { _arguments -s -C \ '1: :->cmd1' \ @@ -753,6 +882,9 @@ _bun() { 'add\:"Add a dependency to package.json (bun a)" ' 'remove\:"Remove a dependency from package.json (bun rm)" ' 'update\:"Update outdated dependencies & save to package.json" ' + 'audit\:"Check installed packages for vulnerabilities" ' + 'dedupe\:"Remove duplicate versions from the lockfile" ' + 'prune\:"Remove packages that are not in the lockfile from node_modules" ' 'outdated\:"Display the latest versions of outdated dependencies" ' 'link\:"Link an npm package globally" ' 'unlink\:"Globally unlink an npm package" ' @@ -833,6 +965,18 @@ _bun() { outdated) _bun_outdated_completion + ;; + audit) + _bun_audit_completion + + ;; + dedupe) + _bun_dedupe_completion + + ;; + prune) + _bun_prune_completion + ;; 'test') _bun_test_completion @@ -920,6 +1064,18 @@ _bun() { outdated) _bun_outdated_completion + ;; + audit) + _bun_audit_completion + + ;; + dedupe) + _bun_dedupe_completion + + ;; + prune) + _bun_prune_completion + ;; 'test') _bun_test_completion diff --git a/docs/docs.json b/docs/docs.json index 54f3545a1c1c..2dca006313a1 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -198,7 +198,15 @@ { "group": "Core Commands", "icon": "terminal", - "pages": ["/pm/cli/install", "/pm/cli/add", "/pm/cli/remove", "/pm/cli/update", "/pm/bunx"] + "pages": [ + "/pm/cli/install", + "/pm/cli/add", + "/pm/cli/remove", + "/pm/cli/update", + "/pm/cli/dedupe", + "/pm/cli/prune", + "/pm/bunx" + ] }, { "group": "Publishing & Analysis", diff --git a/docs/pm/catalogs.mdx b/docs/pm/catalogs.mdx index 2fe7f4a5f0eb..c313ccbae459 100644 --- a/docs/pm/catalogs.mdx +++ b/docs/pm/catalogs.mdx @@ -84,10 +84,15 @@ In your workspace packages, use the `catalog:` protocol to reference versions: "devDependencies": { "jest": "catalog:testing", "testing-library": "catalog:testing" + }, + "peerDependencies": { + "react": "catalog:" } } ``` +`catalog:` and `catalog:` are accepted in `dependencies`, `devDependencies`, `optionalDependencies` and `peerDependencies`, and as the value of a root [`overrides` / `resolutions`](/pm/overrides) rule. A catalog peer is resolved and hoisted exactly as if the catalog's range were written inline: if an ancestor already provides a version that satisfies the catalog range, the workspace package reuses that copy instead of getting its own. If no ancestor provides a satisfying version, the workspace package gets its own copy of whatever the catalog range resolves to, just as it would with the range written inline. `bun.lock` stores the catalog ranges themselves, not a resolved version per reference, so a catalog reference binds to whichever version in the lockfile satisfies the range, again like an inline range. (pnpm additionally pins each catalog entry to the version it first resolved to; Bun does not.) + ### 3. Run Bun Install Run `bun install` to install all dependencies according to the catalog versions. @@ -233,6 +238,18 @@ To update versions across all packages, change the version in the root package.j Then run `bun install` to update all packages. +To add a dependency to the catalog, run `bun add` with `--catalog` (or `--catalog=`) from the workspace package or from the root; an entry that already exists is reused unless you give a version: + +```bash terminal icon="terminal" +bun add react --catalog +``` + +Bun writes the entry to the catalog in the root `package.json` (the range the current `package.json` already declares, otherwise the resolved version) and `"catalog:"` to the `package.json` you ran the command in. See [`bun add --catalog`](/pm/cli/add#--catalog). + +When the entry already existed and differs from the range the package declared, `bun add` says so on stderr, e.g. `note: react in app now follows the catalog entry "^19.0.0" instead of "^18.2.0"`. With `--filter`, the first selected package that declares a range seeds a missing entry; a later package whose declared range does not fit that entry keeps its range and gets a `keeps` note instead. + +`bun add react` without the flag also uses the default catalog when it lists `react`: the package gets `"catalog:"`, and an existing `"catalog:"` reference is kept. `bun add react@latest` (or any explicit version) writes a concrete range instead. Named catalogs are only used through `--catalog=` or an existing reference, and a version you type is never checked against the catalog (there is no equivalent of pnpm's `catalogMode` setting). + ## Lockfile Integration Bun's lockfile tracks catalog versions, so installs are consistent across environments. The lockfile includes: @@ -279,8 +296,9 @@ Bun's lockfile tracks catalog versions, so installs are consistent across enviro - Catalog references must match a dependency defined in either `catalog` or one of the named `catalogs` - Empty strings and whitespace in catalog names are ignored (treated as default catalog) +- `catalog:default` is the same as `catalog:`; both read the `catalog` field, or `catalogs.default` if that is where the entry is defined (as pnpm names it). A package may be defined in only one of the two: if both `catalog` and `catalogs.default` list it, `bun install` stops with `error: "react" is defined in both "catalog" and "catalogs.default"` (`bun add --catalog` always edits the one that already defines the package) - Invalid dependency versions in catalogs fail to resolve during `bun install` -- Catalogs are only available within workspaces; they cannot be used outside the monorepo +- `catalog:` is only understood in the root `package.json` and in workspace packages. A `catalog:` specifier inside an installed package (from the registry, git, a tarball or a `file:` folder) never reads your catalogs: as a regular dependency it fails to resolve (`x@catalog: failed to resolve`), and as a peer dependency it behaves like an optional `*` peer: it binds to whichever version the package that depends on it already provides, and no copy is installed for it otherwise. Publish with `bun publish` or `bun pm pack`, which substitute the ranges (see [Publishing](#publishing)) ## Publishing diff --git a/docs/pm/cli/add.mdx b/docs/pm/cli/add.mdx index 97cd6031a1c5..63e1434c24ef 100644 --- a/docs/pm/cli/add.mdx +++ b/docs/pm/cli/add.mdx @@ -19,6 +19,8 @@ bun add zod@^3.0.0 bun add zod@latest ``` +The package is written to `dependencies` unless `--dev`, `--optional` or `--peer` is given; if the current `package.json` already lists it in another group, that entry is updated in place instead. + ## `--dev` **Alias** — `--development`, `-d`, `-D` @@ -46,6 +48,8 @@ To add a package as a peer dependency (`"peerDependencies"`): bun add --peer @types/bun ``` +Only `peerDependencies` is written; because Bun installs peer dependencies by default, no `devDependencies` entry is needed for the package to be installed locally (pnpm's `--save-peer` adds one). + ## `--exact` **Alias** — `-E` @@ -77,6 +81,75 @@ To view a complete list of options for this command: bun add --help ``` +## `--catalog` + +In a workspace, `bun add react --catalog` resolves the version as usual, but writes it to the root `package.json` [catalog](/pm/catalogs) (`workspaces.catalog`, or `workspaces.catalogs.` with `--catalog=`) and adds `"react": "catalog:"` (or `"catalog:"`) to the current package. An entry that is already in the catalog is reused unless you give a version. Combine with `--exact`, `--dev`, etc. as usual. + +```bash terminal icon="terminal" +bun add react --catalog +bun add vitest --catalog=testing +``` + +```json package.json icon="file-json" +// root package.json +{ + "workspaces": { + "packages": ["packages/*"], + "catalog": { + "react": "^18.2.0" // [!code ++] + } + } +} +``` + +```json packages/app/package.json icon="file-json" +{ + "dependencies": { + "react": "catalog:" // [!code ++] + } +} +``` + +A few things to know, some of which differ from pnpm's `--save-catalog`: + +- If the catalog already has an entry for the package, `bun add react --catalog` leaves it alone and only writes `"react": "catalog:"` to the current package. `bun add react@19.1.0 --catalog` keeps an entry such as `^19.0.0` that the version satisfies and only changes which version is installed; any other version or range replaces the entry, which moves every package that depends on `"catalog:"` along with it. +- Without a version, a range the current `package.json` already has (`"react": "^18.2.0"`) is what gets cataloged. The package is only resolved to its latest version when neither the catalog nor the `package.json` mentions it. +- A package that already depends on `"react": "catalog:legacy"` stays in `legacy`, whatever name the flag carries: the `legacy` entry is reused, or replaced if you gave a version. +- With `--filter`, the entry is decided per selected package. An entry the root already has is used by all of them. When there is none, the first selected package that declares a range seeds it; a later package that declares the same range, or an exact version inside it, is switched to `"catalog:"`, and one that declares anything else keeps its range. Bun prints a `note:` for every package whose range was changed or kept. +- `--catalog` and `--catalog=default` are the same catalog: the entry is written to whichever of `catalog` or `catalogs.default` the root `package.json` already defines (`catalog` is created when there is neither), so a repository migrated from pnpm never ends up with the package in both. +- The recorded version is the one that was installed, so an entry in [`overrides`](/pm/overrides) for the package ends up in the catalog too. +- The name must be attached with `=` (`--catalog=testing`); `--catalog testing` adds a package called `testing`. +- pnpm's `--save-catalog` is spelled `--catalog` and `--save-catalog-name testing` is `--catalog=testing`; Bun silently skips flags it does not know, so the pnpm spellings add the package as an ordinary dependency (and `--save-catalog-name testing` also tries to add a package called `testing`) instead of failing. +- A positional without a name (`bun add https://example.com/react.tgz --catalog`, `bun add github:facebook/react --catalog`) is installed first and then cataloged under the package's real name, with the same text a plain `bun add` would have written. If the catalog already has a different entry for that name, the package gets the tarball directly and Bun prints a note; `bun add --catalog` converts it. If the current package already declares that name itself, the command fails without writing anything and tells you to spell it `bun add @ --catalog`, which moves that declaration into the catalog. Relative paths (`./vendor/foo`, `foo@file:../foo`, `link:`) and workspace packages (by name or via `workspace:`) are still refused before anything is written. +- The root `package.json` must have a `workspaces` field. + +Catalogs are also used without the flag: in a workspace whose default catalog lists `react`, `bun add react` with no version writes `"react": "catalog:"`, and a package that already depends on `"catalog:"` keeps that reference. Pass a version (`bun add react@latest`) to write a concrete range instead. + +## `--filter` + +**Alias** — `-F` + +In a monorepo, `--filter` adds the package to the matching workspace package(s) instead of the package in the current directory. Patterns match workspace names or paths (`./packages/*`), `*` matches every workspace package (not the root), and `!pattern` excludes; the `{dir}` and `...` relation selectors from the filtering page work here too (`--filter '{packages}'`, `--filter 'api...'`). Repeat the flag to combine patterns. Every matching package is edited; if nothing matches, the command fails without touching anything, and a pattern that matches nothing while others do prints a warning. `bun remove` and `bun install ` accept the same flag. See [filtering](/pm/filter). + +```bash terminal icon="terminal" +bun add zod --filter api +bun add -d typescript --filter './packages/*' +bun add ./vendor/logger --filter '*' +bun remove zod --filter '*' --filter '!api' +``` + +A few things to know, some of which differ from pnpm: + +- The workspace root is only edited when a pattern names it: its name, `--filter .` from the root directory, or `{.}`. `*`, filter sets made only of `!` patterns, and relations that merely reach it through its own dependencies (`...api` when the root depends on `api`) leave it alone (`--filter '*' --filter ''` edits everything). A `package.json` without workspaces is still selected by `*`. +- With a `...` relation, `bun add` and `bun remove` read the workspace links from the workspace `package.json` files as they are on disk, so no `bun.lock` is needed and a dependency you just added by hand counts. The closure includes the named package itself unless you write `^` (`api^...`, `...^api`). +- A `!` pattern always wins, whatever order the flags are given in: `--filter '!./packages/*' --filter api` selects nothing. +- Name patterns are globs, and `*` does not cross `/`: `*-utils` does not match `@acme/date-utils`; use `*/*-utils` or `@acme/*`. +- Path patterns must match a workspace directory exactly, relative to the current directory: `--filter ./packages` selects nothing, `--filter ./packages/*` or `--filter '{packages}'` selects every package in it, and `--filter .` only works from a workspace's own directory. +- Local paths (`./vendor/logger`, `file:../logger`, `./logger.tgz`) are relative to the current directory and are re-spelled relative to each selected package (`"logger": "../../vendor/logger"` in `packages/api/package.json`). +- The flag chooses both which `package.json` files are edited and which workspaces are installed. Everything is still resolved and `bun.lock` is updated for the whole repository, but, as with `bun install --filter`, only the selected workspaces' dependencies are linked into `node_modules`; the other workspaces' `node_modules` (and the root's own dependencies and lifecycle scripts, unless a pattern selected the root) are left exactly as they were, and a plain `bun install` brings them up to date. The `package.json` files are written as soon as the lockfile is saved, so they agree with `bun.lock` even if a root `postinstall` script then fails. +- The install summary is printed from the point of view of one selected workspace (the first one that received every requested package); the other selected `package.json` files get the same edits even though they are not listed in the output. +- `--dry-run` skips writing; `--filter` cannot be combined with `--global`. + ## `--global` **Alias** — `bun add --global`, `bun add -g`, `bun install --global` and `bun install -g` diff --git a/docs/pm/cli/audit.mdx b/docs/pm/cli/audit.mdx index 0038c4997aa9..7b484846a5c0 100644 --- a/docs/pm/cli/audit.mdx +++ b/docs/pm/cli/audit.mdx @@ -9,7 +9,9 @@ Run the command in a project with a `bun.lock` file: bun audit ``` -Bun sends the list of installed packages and versions to npm, and prints a report of any vulnerabilities found. Packages installed from registries other than the default registry are skipped. +Bun sends the list of installed packages and versions to npm, and prints a report of any vulnerabilities found. Packages that come from a registry configured for their scope are sent to that registry's advisory endpoint instead, and its answer is merged into the same report; a registry that does not answer is listed after the report (`Skipped @acme/a, @acme/b because https://npm.acme.dev could not be audited`) and does not affect the exit code. + +The package list comes from `bun.lock` alone, so `node_modules` does not need to exist, and `bun audit` writes nothing: `package.json`, `bun.lock` and `node_modules` are left as they are (only `bun audit fix`, below, changes `bun.lock` and `node_modules` — with the hoisted linker that includes deleting a nested copy of the vulnerable version whose dependent now uses the fixed copy above it, so nothing keeps loading the old one — and `package.json` only when it rewrites an exact pin or is run with `--latest`). If no vulnerabilities are found, the command prints: @@ -17,36 +19,43 @@ If no vulnerabilities are found, the command prints: No vulnerabilities found ``` -When vulnerabilities are detected, Bun lists each affected package with the severity, a short description, and a link to the advisory. At the end of the report it prints a summary and hints for updating: +When vulnerabilities are detected, Bun lists each affected package with the severity, a short description, and a link to the advisory. The report ends with a summary line (`1 vulnerability (1 high)` when there is only one) followed by the commands that fix what it lists: ``` 3 vulnerabilities (1 high, 2 moderate) -To update all dependencies to the latest compatible versions: - bun update -To update all dependencies to the latest versions (including breaking changes): - bun update --latest + + bun audit fix upgrade the vulnerable packages within their ranges + bun audit fix --latest also cross major versions ``` ### Filtering options -**`--audit-level=`** - Only show vulnerabilities at this severity level or higher: +**`--audit-level=`** - Only show vulnerabilities at this severity level or higher. Without the flag nothing is filtered out; an advisory whose severity is not one of these four values is counted and filtered as `moderate`: ```bash terminal icon="terminal" bun audit --audit-level=high ``` -**`--prod`** - Audit only production dependencies (excludes devDependencies): +**`--prod`** (also `-P`, `-p` or `--production`) - Audit only production dependencies. A package is production when it is reachable from the root through `dependencies`, `optionalDependencies` or `peerDependencies` edges; packages that are only reached through `devDependencies` or an optional peer dependency are excluded, even when another version of the same package is a production dependency. There is no flag for auditing only `devDependencies`: ```bash terminal icon="terminal" bun audit --prod ``` -**`--ignore `** - Ignore specific CVEs (repeat the flag to ignore several): +**`--omit=`** - Leave out packages that are only reached through edges of the given kind, using the same reachability rule as `--prod` (`--omit=dev` is the same as `--prod`). Repeat the flag to leave out several kinds: + +```bash terminal icon="terminal" +bun audit --omit=optional --omit=peer +``` + +**`--ignore `** - Ignore specific advisories by GHSA ID or numeric advisory ID (repeat the flag to ignore several). The registry's advisory data does not include CVE IDs, so a CVE ID does not match anything: ```bash terminal icon="terminal" -bun audit --ignore CVE-2022-25883 --ignore CVE-2023-26136 +bun audit --ignore GHSA-c2qf-rxjj-qqgw --ignore 1112918 ``` +These filters cannot be set in `bunfig.toml`; to ignore an advisory or raise the level for every run, put the flags in a `package.json` script. + ### `--json` Use the `--json` flag to print the raw JSON response from the registry instead of the formatted report: @@ -55,6 +64,62 @@ Use the `--json` flag to print the raw JSON response from the registry instead o bun audit --json ``` +The response is printed as received: `--prod` and `--omit` still limit which packages are sent, and `--audit-level` and `--ignore` do not remove anything from the printed JSON. They do decide the exit code, which is `1` only if an advisory is left after both filters are applied (and `0` otherwise), so `bun audit --json --audit-level=high` prints every advisory but only fails the run for high or critical ones. If the response cannot be parsed, it is still printed and the command exits with `1`. + +### `bun audit fix` + +`bun audit fix` runs the audit and then moves each vulnerable version installed in `bun.lock` to the lowest release that no advisory covers and that the ranges of its dependents accept (`overrides` and catalog entries count as ranges), then installs. A dependency that the root `package.json`, a workspace `package.json` or a catalog entry pins to an exact version is planned as if it were written `^version`; when that finds a fix, that `package.json` entry (or the catalog entry in the root; workspace files that say `catalog:` are left as they are) is rewritten to the new version. Apart from that and `--latest` (below), the command does not edit `package.json`: `overrides` are never added or changed, and unrelated packages are not touched. `fix` is the only subcommand and it is not a flag: `bun audit --fix` has no effect (the unknown flag is ignored and a plain `bun audit` runs), and any other word (`bun audit fixx`) exits with `1` and `error: unknown subcommand "fixx"` before anything is sent to the registry. + +```bash terminal icon="terminal" +bun audit fix +``` + +``` +fixing: + ms@0.7.0 → 0.7.1 + lodash@4.17.20 → 4.17.21 + package.json: 4.17.20 → 4.17.21 + +blocked by a dependent's range: + minimatch@0.3.0 → 3.0.2 + express@3.21.2 depends on minimatch@0.3.0 + semver@5.7.1 → 6.3.1 + my-app depends on semver@^5.0.0 + bun audit fix --latest + +no published version fixes: + left-pad@1.3.0 GHSA-xxxx-xxxx-xxxx + qs@6.2.0 GHSA-yyyy-yyyy-yyyy, GHSA-zzzz-zzzz-zzzz + bun audit fix --ignore GHSA-xxxx-xxxx-xxxx --ignore GHSA-yyyy-yyyy-yyyy --ignore GHSA-zzzz-zzzz-zzzz + +Fixed 2 vulnerabilities in 2 packages +5 vulnerabilities remaining +``` + +- `blocked by a dependent's range:` lists the dependents whose range accepts no safe release; they stay on the version they have. Other dependents of the same version whose ranges do accept the fix are still moved, in which case the package appears under both `fixing:` and this heading. Update the dependent, or add an `overrides` entry, and run the command again. A dependency bundled inside another package (`express@3.21.2 bundles minimatch@0.3.0`) can only be fixed by updating the package that bundles it. +- When any of the blocking ranges is one your own `package.json` (root or workspace) or a catalog entry declares, the section ends with the `bun audit fix --latest` command that gets past it. Without `--latest`, a range such as `^5.0.0` is never widened. +- Only stable releases are candidates. When no newer release is safe, the highest safe older release the ranges accept is installed instead and marked `(downgrade)`. `no published version fixes:` lists versions for which no safe release exists in either direction, each with the advisories that cover it; the section ends with one `bun audit fix --ignore ...` command carrying every one of those advisories, which silences them all (the other way out is replacing the package). A version whose registry request failed is reported on stderr as `warn: @ was not checked for updates: ` (the same line `bun update` prints), and nothing is installed for it. +- A safe release published more recently than `--minimum-release-age` is still installed, marked `(newer than --minimum-release-age)`: the age gate is lifted for that package during this run only. +- Advisories that match none of the installed versions are listed under `not matched to an installed version:` and count as remaining. +- Counts are advisories, as in `bun audit`. After installing, Bun sends the packages of the `bun.lock` it wrote to the registry a second time, and the `remaining` line, the exit code and the `vulnerable after install:` list come from that second answer, so it also reports advisories that only cover the version a fix moved to, and packages the new versions pulled in, and agrees with a `bun audit` run afterwards. The `Fixed` line still counts the advisories from the first answer that no installed version matches any more. `--dry-run` prints the plan without changing anything or asking the registry again, and its counts are the plan's. +- A fix that only moved `bun.lock` can be undone by a later dependency that pins the vulnerable version or by regenerating the lockfile; a rewritten `package.json` pin stays. Re-run `bun audit` after such changes. +- A version listed in `patchedDependencies` is moved like any other when an advisory covers it (unlike `bun update`, which holds patched versions), so the patch entry no longer applies to anything installed afterwards; re-create it against the new version with `bun patch` or remove it. +- `--json` prints one JSON object instead of the text report, with the fields `fixes`, `blocked`, `unfixable`, `manifestUnavailable`, `unmatched`, `unaudited`, `vulnerableAfterInstall`, `fixed`, `remaining` and `dryRun`. Each entry of `fixes` and `blocked` has `name`, `from`, `to` and `downgrade`; a fix also has `newerThanMinimumReleaseAge` and `packageJson` (the rewrites printed under `fixing:`, each with `file`, `catalog` — `"default"` for the default catalog, the catalog's name, or `null` when the entry is not a catalog entry — `key`, `from` and `to`); a blocked entry also has `blockers` (`dependent`, `range`, `bundled`) and `latestFixes`, `true` when the text report would print the `bun audit fix --latest` line. `unfixable` entries have `name`, `from` and `advisories`, the ids the `--ignore` line is built from; `manifestUnavailable` entries have `name`, `from` and `error`; `unmatched` entries have `name` and `range`; `unaudited` groups the skipped `packages` by `registry`, with a `reason` (`null` when the request was never made); `vulnerableAfterInstall` entries have `name`, `version` and `advisories`. `fixes`, `blocked`, `unfixable`, `manifestUnavailable` and `unmatched` describe the plan; `fixed` counts advisories from the first request; `remaining`, `vulnerableAfterInstall` and `unaudited` come from the request made after the install (with `--dry-run`, from the plan). It is printed once the install has finished (with `--dry-run`, as soon as the plan is made); if a root lifecycle script writes to stdout, pass `--ignore-scripts` to keep the output parseable. +- `--registry` applies to both the advisory request and the install of the fixed versions; there is no way to query one registry for advisories and install from another. +- A [security scanner](/pm/security-scanner-api) configured in `bunfig.toml` (`install.security.scanner`) runs on the packages `bun audit fix` is about to install, exactly as it does for a bare `bun update`: it is sent every package in the new lockfile (the fixed versions included), a fatal advisory aborts the run before `package.json`, `bun.lock` or `node_modules` are touched, and `--dry-run` does not run it. +- `--latest` (or `-L`) also rewrites the ranges your own `package.json` files and catalogs declare when they exclude the fix; see below. It is only accepted together with `fix`: `bun audit --latest` exits with `--latest only applies to bun audit fix`. +- `--audit-level`, `--ignore` and `--prod` behave as for `bun audit`, except that `--prod` (like `--frozen-lockfile` and `--no-save`) stops `bun.lock` from being written, so `bun audit fix` rejects it before contacting the registry. `--omit` does not narrow what `bun audit fix` audits: every package in `bun.lock` is checked and fixed. + +### `bun audit fix --latest` + +```bash terminal icon="terminal" +bun audit fix --latest +``` + +Same as `bun audit fix`, except that a range declared in the root `package.json`, a workspace `package.json` or a catalog entry no longer blocks a fix: the fix version is chosen as before, and the declared entry is rewritten to accept it. The rewrite keeps the style of the old entry: `^5.0.0` becomes `^6.3.1`, `~5.7.1` becomes `~6.3.1`, an exact version or `=` prefix stays exact, and an `npm:` alias keeps its alias (with `--exact` or `install.exact`, the bare version is written). Each rewrite is printed under `fixing:` in the same `package.json: old → new` form as an exact-pin rewrite. Ranges declared by other packages, and dependencies bundled inside another package, still block exactly as without the flag; the way past those is an `overrides` entry, or updating the dependent. Ranges that already accept the fix are left as they are, and an entry covered by `overrides` is never rewritten (`overrides` itself is not edited either). + ### Exit code -`bun audit` exits with code `0` if no vulnerabilities are found and `1` if the report lists any, including when `--json` is passed. +`bun audit` exits with code `0` if no vulnerabilities are found and `1` if any are left after `--audit-level` and `--ignore` are applied; `--json` uses the same rule even though it prints the unfiltered response. `bun audit fix` exits with `0` when the audit of the written `bun.lock` reports nothing after `--audit-level` and `--ignore` are applied (or, with `--dry-run`, when the plan leaves nothing remaining) and `1` otherwise. + +When the default registry cannot be reached, or answers with a 4xx or 5xx status, both commands print `audit request failed` to stderr and also exit with `1`; there is no flag to turn a registry failure into a passing run, so a CI step that wants to distinguish an outage from a vulnerable tree has to look at stderr. This also applies to the second request `bun audit fix` makes after installing: the fixed versions stay in `bun.lock` and `node_modules`, `audit request failed` is printed and the command exits with `1`. A scoped registry that fails is only reported as skipped, as described above. diff --git a/docs/pm/cli/dedupe.mdx b/docs/pm/cli/dedupe.mdx new file mode 100644 index 000000000000..9f4ceb7744a9 --- /dev/null +++ b/docs/pm/cli/dedupe.mdx @@ -0,0 +1,66 @@ +--- +title: "bun dedupe" +description: "Remove duplicate versions of packages from bun.lock" +--- + +After bumping or adding a dependency, `bun.lock` can end up with several versions of the same package even though one of them satisfies every range that requests it — for example `esbuild@0.15.10` and `esbuild@0.15.11` when the ranges are `^0.15.7` and `^0.15.8`. `bun dedupe` keeps the smallest set of already-locked versions that still satisfies every range (preferring newer versions), moves only the ranges whose version is being dropped, removes those versions, saves `bun.lock`, and installs. + +It only uses versions that are already in the lockfile. It never moves a dependency outside its range and never contacts the registry to resolve anything; use `bun update` for that. + +```bash terminal icon="terminal" +bun dedupe +``` + +``` +bun dedupe v1.4.0 (abc12345) + +↳ esbuild 0.15.10 → 0.15.11 +↳ react 18.2.0 → 18.3.1 + +2 duplicate versions removed, 3 packages installed (checked 5 packages) [12.00ms] +``` + +Each `↳` row is a version that was removed and the version its dependents now resolve to (when the output is not a terminal the row is written as `~ esbuild 0.15.10 -> 0.15.11`). Any `kept` lines (see the notes below) follow the rows. The rows and the summary are printed together once the install has finished, after any lifecycle script output. In the summary, `checked` counts every package in `bun.lock`; installed packages are only mentioned when the install changed `node_modules`. + +If there is nothing to remove, the command prints only a summary and exits without installing or re-saving `bun.lock` (also with `--lockfile-only`): + +``` +bun dedupe v1.4.0 (abc12345) + +🎉 No duplicates — checked 5 packages, every one already resolves to a single version [9.00ms] +``` + +### `--check` and `--dry-run` + +`bun dedupe --check` prints the versions that can be removed without installing packages or modifying `bun.lock`, and exits with code `1` when there is something to remove. It exits with code `0` when the lockfile is already deduplicated, which makes it usable in CI. Like `bun dedupe`, it exits with code `1` and `error: bun.lock does not match package.json, nothing to dedupe` when `package.json` changed since the last install. + +```bash terminal icon="terminal" +bun dedupe --check +``` + +``` +bun dedupe v1.4.0 (abc12345) + +↳ esbuild 0.15.10 → 0.15.11 +↳ react 18.2.0 → 18.3.1 + +2 duplicate versions can be removed (checked 5 packages) [9.00ms] + bun dedupe +``` + +`bun dedupe --dry-run` prints the same output but always exits with code `0`, like `bun prune --dry-run`; use it to look before running `bun dedupe`, and `--check` to fail CI. When there is nothing to remove, both print the 🎉 line above and exit with code `0`. + +### Notes + +- Overrides and catalogs are honoured: a dependency is re-pointed using its effective range. +- A dependency listed directly in the root or a workspace `package.json` is only moved to an older version when that is the only way to remove a version: if a transitive dependency pins an older version exactly, the direct range is collapsed onto it (pnpm keeps both versions in that case). Use `bun update` or an [override](/pm/overrides) if you want the newer version to win. +- A range whose version survives is never moved. +- A version listed in `patchedDependencies` is never moved away from and never removed. If removing some other version would leave a patched package unreachable, that version is kept as well, and every mode (`bun dedupe`, `--check`, and when there is nothing else to remove) says so with a line directly under the `↳` rows, before the summary, e.g. ` kept one-fixed-dep@1.0.0 (needed to reach patched no-deps@1.0.0)`. Delete the patch entry (or `bun update` the dependent) if you want the version removed. +- Bundled dependencies stay on the copy inside their tarball, and dependencies specified as a dist-tag (such as `latest`), a git URL, or a tarball keep the version they resolved to. A dist-tag keeps its version because only the registry knows what the tag points at now; other ranges are still collapsed onto that version. +- Only the ranges of packages that remain installed are counted; a version that the same run removes does not influence where its own dependencies end up, so running the command twice never changes anything the second time. +- It works from the ranges recorded in `bun.lock`. If the dependencies, overrides, or catalogs in any `package.json` changed since the last install, it stops with `error: bun.lock does not match package.json, nothing to dedupe` followed by `note: run 'bun install' first`, and exits with code `1` (also with `--check` and `--dry-run`). Edits that do not affect resolution (`scripts`, `trustedDependencies`) do not block it. +- The command needs an existing `bun.lock` to work from: when there is none, `bun dedupe` (and `bun dedupe --check`) reports `missing lockfile, nothing to dedupe` and exits with code `1`, so run `bun install` first. A `package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml` counts: it is migrated to `bun.lock` before deduplicating, and the error is only reported when there is no lockfile of any format. +- `bun.lock` and `node_modules` change (with the hoisted linker the nested copy of a removed version — `node_modules/a/node_modules/b` — is deleted so `a` loads the version that survived; with the isolated linker the store links are re-pointed by the install; `--check`, `--dry-run` and `--lockfile-only` leave `node_modules` alone); `package.json` never does. The result only lives in `bun.lock`: deleting the lockfile and reinstalling can bring the duplicates back. Add an [override](/pm/overrides) to pin a version permanently. +- `--lockfile-only` rewrites `bun.lock` without installing. +- `--frozen-lockfile`, `--production`, and `--no-save` cannot be combined with removing duplicates. The command prints the same rows and `… can be removed` summary as `--check`, then exits with code `1` with the reason and the fix together on stderr: `error: 1 duplicate version can be removed, but --frozen-lockfile was passed` (or `but --production implies --frozen-lockfile`, `but --no-save was passed`) followed by `note: run 'bun dedupe' to remove it, or 'bun dedupe --check' in CI`. Use `--check` in CI. +- Works with both the hoisted and isolated linker. It only removes distinct versions of a package; the isolated linker may still keep several copies of one version that differ in their peer dependencies, and those are not reported by `--check`. Leftover store entries of removed versions are cleaned by [`bun prune`](/pm/cli/prune). diff --git a/docs/pm/cli/install.mdx b/docs/pm/cli/install.mdx index ae03186ce5ac..afff5728f01d 100644 --- a/docs/pm/cli/install.mdx +++ b/docs/pm/cli/install.mdx @@ -164,12 +164,20 @@ To install in production mode (without `devDependencies`): bun install --production ``` +`--production` (`--prod`, `-p`, `-P`) also implies `--frozen-lockfile`: it never writes `bun.lock`, and it exits with an error if `package.json` and `bun.lock` disagree. It only controls which packages Bun links; anything already in `node_modules` that it would not install (for example `devDependencies` from an earlier `bun install`) is left there — run [`bun prune --production`](/pm/cli/prune) to delete it. + For reproducible installs, use `--frozen-lockfile`. Bun installs the exact versions specified in the lockfile and does not update it. If your `package.json` disagrees with `bun.lock`, Bun exits with an error. ```bash terminal icon="terminal" bun install --frozen-lockfile ``` +Bun does not enable `--frozen-lockfile` on its own when it detects a CI environment (`CI=1` only turns off the progress bar); pass the flag or run `bun ci` if a stale `bun.lock` should fail the build. When there is no `bun.lock` at all, `bun install --frozen-lockfile` (and `bun ci`) resolves and installs from `package.json` without writing a lockfile; the error is only raised when a lockfile exists and does not match `package.json`. + +`--frozen-lockfile` also works on a pruned copy of a monorepo (for example the output of `turbo prune`, or a Docker context that copies `bun.lock` with only some workspace folders): workspaces listed in `bun.lock` whose `package.json` is not on disk are skipped rather than treated as a lockfile change, and packages the pruned lockfile still lists are installed as written. This works whether the root `workspaces` field uses globs or lists each folder explicitly, and Bun prints a `note:` with the number of skipped workspaces (`--verbose` names them), so a `bun.lock` that is stale because a workspace was deleted without re-running `bun install` is still visible in CI logs. Only workspaces that `bun.lock` knows about are skipped: a `workspaces` entry whose folder is missing and which `bun.lock` does not list (for example a typo) still fails with `Workspace not found`, exactly as it does without `--frozen-lockfile`. If a remaining workspace (or the root `package.json`) depends on a skipped workspace, the install fails with `workspace "app" depends on workspace "other" (packages/other), which is listed in bun.lock but not on disk`: whatever prunes the checkout has to keep the workspaces the survivors depend on (`turbo prune` does). A plain `bun install` reports the same error in that situation; it looks at what the remaining `package.json` files declare, so deleting a workspace together with the entries that pointed at it installs normally and drops it from `bun.lock`. This is stricter than pnpm, whose `--frozen-lockfile` also ignores a workspace that is still on disk but was removed from the `workspaces` list — Bun only skips a workspace whose `package.json` is gone; un-listing one that is still present is a lockfile change. Changing any `package.json` that is on disk or trimming `overrides` from `bun.lock` still fails the install; `catalog`/`catalogs` entries, on the other hand, may be missing from a pruned `bun.lock` as long as only the skipped workspaces used them — every entry `bun.lock` does keep must still match `package.json`, and an entry that a remaining workspace or an `overrides` rule refers to must be present. When entries were left out this way, the install prints `note: skipped 1 catalog entry missing from bun.lock that no remaining workspace uses` alongside the skipped-workspace note. The skipped workspaces' exclusive dependencies are not downloaded or installed, but they remain in `bun.lock`, so `bun pm ls` and `bun audit` still report them. A package that a surviving workspace lists as an optional peer dependency is installed if the pruned `bun.lock` still contains it, even when only a skipped workspace depended on it. This also holds when only `trustedDependencies` (which `turbo prune` leaves out of the pruned `bun.lock`) or `patchedDependencies` differ between `package.json` and `bun.lock`; a package is only dropped from the lockfile when a dependency, `overrides` or `catalog` entry actually changed, or when `bun audit fix` upgrades it. [`bun prune`](/pm/cli/prune) accepts such a checkout as well and prunes it against the workspaces that are on disk. + +`--frozen-lockfile` never writes `bun.lock`, including with `--lockfile-only`. The one exception is a lockfile format migration: converting `bun.lockb` with `--save-text-lockfile`, or migrating from `package-lock.json`, `yarn.lock` or `pnpm-lock.yaml`, still writes `bun.lock`. To check a lockfile without installing anything, use `bun install --frozen-lockfile --dry-run`. + See [lockfile](/pm/lockfile) for more on `bun.lock`. --- @@ -296,7 +304,7 @@ On `bun install`, `bun remove`, and `bun add`, Bun looks for `bunfig.toml` in: 1. `$XDG_CONFIG_HOME/.bunfig.toml` or `$HOME/.bunfig.toml` 2. `./bunfig.toml` -If both are found, the results are merged together. +If both are found, both are loaded, and keys set in the project's `bunfig.toml` override the same keys in the global file. Configuring with `bunfig.toml` is optional. These are the default values: @@ -512,10 +520,17 @@ The migration process handles: ### Lockfile Migration -- Converts `pnpm-lock.yaml` to `bun.lock` format -- Preserves package versions and resolution information -- Maintains dependency relationships and peer dependencies -- Handles patched dependencies with integrity hashes +- Converts `pnpm-lock.yaml` versions 7 through 9 to `bun.lock`; a newer `lockfileVersion` is read with the 9.0 rules and produces a warning +- When the file holds several YAML documents (pnpm 11 writes one for its own tooling dependencies ahead of the project's), the last document is the one migrated +- Keeps every package's resolved version and integrity; registry packages are downloaded from the registry Bun is configured to use for that package, and a tarball URL recorded by pnpm is only kept when it lives under that registry +- `name@registry:version` entries written for pnpm's named registries use the URL from `namedRegistries` in `pnpm-workspace.yaml` (`gh` and `npmjs` are built in); an unknown name falls back to Bun's registry with a warning +- Peer dependencies keep the ranges each package declares, together with `peerDependenciesMeta`, and the `peerDependencies` of the root and of every workspace are read from their `package.json`, so the migrated `bun.lock` is the file `bun install` would have written and the next install leaves it unchanged +- A peer dependency that pnpm did not record is reported and left for the next `bun install` to resolve +- A package that pnpm recorded under several peer suffixes becomes a single `bun.lock` entry: which peers each importer gets is decided again by `bun install` from the declared ranges (the isolated linker creates one `node_modules/.bun` entry per peer set, as pnpm's store does), and a peer that pnpm resolved to a local folder or workspace package in some importers keeps that resolution even though other importers left it unmet +- A `file:` directory whose `packages:` entry was removed by a pruning tool such as `turbo prune` is rebuilt from its snapshot, and injected workspace packages (`dependenciesMeta.*.injected`) become ordinary workspace dependencies +- Migrates git (`git+ssh://`, `git+https://`), GitHub, tarball URL and `file:` dependencies, including transitive ones and npm aliases (`npm:`) recorded in the lockfile; a local tarball is recognised by a `.tgz`, `.tar.gz` or `.tar` extension in any case +- Handles patched dependencies with integrity hashes; pnpm's current hash-only `patchedDependencies` entries are matched to the patch files listed in `package.json` or `pnpm-workspace.yaml` +- `runtime:` entries (Node.js versions managed by pnpm) are skipped with a warning, and `packages:` entries without a snapshot are ignored ### Workspace Configuration @@ -577,11 +592,15 @@ Bun migrates the following pnpm configuration from both `pnpm-lock.yaml` and `pn - **Patched Dependencies**: Moved from `pnpm.patchedDependencies` to root-level `patchedDependencies` in `package.json` - **Workspace Overrides**: Applied from `pnpm-workspace.yaml` to root `package.json` -### Requirements +### Requirements and limitations - Requires pnpm lockfile version 7 or higher - Workspace packages must have a `name` field in their `package.json` - All catalog entries referenced by dependencies must exist in the catalogs definition +- Every workspace listed in `pnpm-lock.yaml` must have its `package.json` on disk (when building in Docker, copy each workspace's `package.json` before running `bun install`); otherwise migration reports the missing importer and `bun install` falls back to a fresh resolution +- `link:` dependencies that point at a relative path are not migrated, because Bun's `link:` protocol refers to packages registered with `bun link`: when pnpm recorded them the migration stops with a message, and when pnpm left them out through `excludeLinksFromLockfile` each omitted dependency is reported and the rest of the lockfile is migrated +- Git dependencies that select a sub-directory of the repository (`resolution.path`) stop the migration with a message naming the package +- When the migration stops, `bun install` resolves everything from scratch and prints why After migration, you can safely remove `pnpm-lock.yaml` and `pnpm-workspace.yaml` files. diff --git a/docs/pm/cli/pm.mdx b/docs/pm/cli/pm.mdx index 2fd29fc7a2e1..71201780736c 100644 --- a/docs/pm/cli/pm.mdx +++ b/docs/pm/cli/pm.mdx @@ -163,6 +163,74 @@ bun list --trusted └── esbuild@0.21.5 ``` +## licenses + +To list every installed package grouped by its license (read from the `license` or legacy `licenses` field of each package's installed `package.json`; packages declaring neither are listed under `Unknown`, and workspace and `link:` packages are not listed): + +```bash terminal icon="terminal" +bun pm licenses +# or +bun pm licenses list +# or +bun pm licenses ls +``` + +```txt +MIT (2) +├── path-parse@1.0.6 +└── resolve@1.9.0 + +Unknown (4) +├── a-dep@1.0.1 (dev) +├── no-deps@1.0.0 +├── no-deps@1.0.1 +└── one-dep@1.0.0 +``` + +License groups are printed in name order with `Unknown` always last, and packages within a group are sorted by name and then version. Packages that are only reached through `devDependencies` are marked `(dev)`. When nothing is listed (for example `--filter` selecting a workspace with no dependencies), the text output is a single line showing how many packages were checked and how long it took, and `--json` prints `{}`: + +```txt +No packages to list (checked 0 packages in bun.lock) [1.20ms] +``` + +Pass `--prod` (or `-P`, `-p`, `--production`) to skip `devDependencies`, and `--json` to get a machine-readable object keyed by license, where each entry has `name`, `versions` (in semver order), `paths` (the directory each of those versions was read from, in the same order — under `node_modules` for hoisted installs, inside `node_modules/.bun` for isolated installs), `license` (the same string as the key, `Unknown` included) and, when the newest listed version declares them, its `homepage`, `author` and `description`: + +```bash terminal icon="terminal" +bun pm licenses --json --prod +``` + +```json +{ + "MIT": [ + { + "name": "path-parse", + "versions": ["1.0.6"], + "paths": ["/home/me/app/node_modules/path-parse"], + "license": "MIT", + "homepage": "https://github.com/jbgutierrez/path-parse#readme", + "author": "Javier Blanco ", + "description": "Node.js path.parse() ponyfill" + } + ] +} +``` + +`--long` prints each package's `author`, `description` and `homepage` (whichever it declares) on indented lines under its entry in the text output; it does not change `--json`, and `paths` are only part of the `--json` output. + +`--prod` drops every `devDependencies` edge — the same packages `bun install --production` leaves out, including the `devDependencies` of `file:` dependencies; `optionalDependencies`, `peerDependencies` and everything a production dependency pulls in are still listed, and from a workspace root every workspace's production dependencies are included. Run it inside a workspace package to list only that package's dependencies, and use [`bun why`](/pm/cli/why) to see which dependency pulls in an unexpected package. + +`--dev` (or `-D`) lists only the packages that the `devDependencies` of the root (or of every workspace, from a workspace root) pull in, including their transitive dependencies. It is an error to combine it with `--prod`, `--omit=dev` or `install.production`. + +`--filter ` (or `-F`, repeatable) lists the dependencies of the matching workspaces only, from any directory of the monorepo. It takes the same patterns as [`bun outdated --filter`](/pm/cli/outdated): workspace names, `./relative/paths` resolved from the current directory, `!` to exclude, and `*` for every workspace. With a filter each selected workspace contributes only its own dependencies, so selecting the root does not pull in the workspace members, and a filter that matches nothing is an error. Without a filter, running from the workspace root lists every workspace's dependencies and running inside a workspace package lists only that package's. + +`--omit=dev` is the same as `--prod` (as is [`install.production`](/runtime/bunfig#install-production) in `bunfig.toml`), and `--omit=optional` / `--omit=peer` skip `optionalDependencies` / `peerDependencies` as well, so the listing follows the same dependency types `bun install` would with those flags. + +`bun pm licenses` only reads `bun.lock` and `node_modules`; it never writes to either of them or to `package.json`, and it prints an error and exits with code `1` when the project has no lockfile or no `node_modules` directory. + +Packages that are in the lockfile but not in `node_modules` (for example after `bun install --production`) are omitted, and a `warn:` line with the number of omitted packages is printed to stderr. Packages that don't apply to the current platform (`os`/`cpu`) are omitted silently. + +This is the equivalent of `pnpm licenses list`, including its `--json`, `--prod`, `--dev`, `--long` and `--filter` flags. + ## whoami Print your npm username. Requires you to be logged in (`bunx npm login`) with credentials in either `bunfig.toml` or `.npmrc`: diff --git a/docs/pm/cli/prune.mdx b/docs/pm/cli/prune.mdx new file mode 100644 index 000000000000..312e88402f37 --- /dev/null +++ b/docs/pm/cli/prune.mdx @@ -0,0 +1,91 @@ +--- +title: "bun prune" +description: "Remove packages that are not in bun.lock from node_modules" +--- + +`bun prune` deletes everything in `node_modules` that the current `bun.lock` does not install there — packages left behind after switching branches, editing `bun.lock`, or installing with another package manager. With the isolated linker, that includes every entry in `node_modules/.bun` a fresh install would not create, such as the extra `name@version+` variants left behind after one of a package's peer dependencies was updated. It reads `bun.lock` and first checks that it still matches your `package.json` files; if you changed dependencies, `overrides` or a catalog since `bun.lock` was written it stops with `bun.lock does not match package.json` — run `bun install` first. It never contacts the registry and never changes `bun.lock` or `package.json`. It takes no package names: passing one is an error, because it always removes every package `bun.lock` does not account for. + +```bash terminal icon="terminal" +bun prune +``` + +``` +bun prune v1.4.0 (abc12345) + +- @types/node@20.11.5 +- left-pad@1.3.0 +2 packages removed (checked 948) [22.00ms] +``` + +Each row is a package that was removed, with the version read from its `package.json` when there is one; a package removed from a workspace's `node_modules` folder (or a nested `node_modules` folder) names that folder in parentheses, for example `- typescript@5.4.0 (packages/app/node_modules)`. `checked` is the number of entries it examined across every `node_modules` folder (and `node_modules/.bun` with the isolated linker). If there is nothing to remove, the command prints how much it checked instead: + +``` +bun prune v1.4.0 (abc12345) + +Done! Checked 948 packages across 12 folders (nothing to prune) [14.00ms] +``` + +If there is no `node_modules` folder at all, it prints `Done! No node_modules folder (nothing to prune)` and exits with code 0. + +### `--production` + +`bun prune --production` (alias `--prod`) additionally removes every package that `bun install --production` would not link, and with the isolated linker the `node_modules/.bun` entries that only those packages used. A package that stays keeps its `node_modules/.bun` entry whether `node_modules` was populated by a full `bun install` or by a `bun install` with the same `--production`/`--omit` flags, even though those two installs can resolve its peer dependencies differently and give the entry a different `+`; only entries that neither install would create are removed. `--omit=dev`, `--omit=optional` and `--omit=peer` work the same way as they do for `bun install`; there is no `--no-optional` flag, use `--omit=optional`. + +This makes it possible to build with `devDependencies` installed and ship without them: + +```dockerfile +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile +COPY . . +RUN bun run build +RUN bun prune --production +``` + +### `--dry-run` + +`bun prune --dry-run` prints the same list followed by `N packages can be removed (checked C)` and deletes nothing. The last line is the same invocation without `--dry-run`, ready to copy and paste. + +```bash terminal icon="terminal" +bun prune --production --dry-run +``` + +``` +bun prune v1.4.0 (abc12345) + +- typescript@5.4.0 +1 package can be removed (checked 948) [9.00ms] + bun prune --production +``` + +### `--filter` + +`bun prune --filter ` (alias `-F`, repeatable; the same workspace name and `./path` globs as [`bun install --filter`](/pm/filter)) prunes only the `node_modules` folders of the selected workspaces. The part of the install that every workspace shares — the root `node_modules` with the hoisted linker, `node_modules/.bun` with the isolated linker — is cleaned in the same run, but anything a workspace you did not select depends on stays there, so `bun prune --production --filter app` removes only what `app` alone needed. A filter that matches no workspace is an error. + +```bash terminal icon="terminal" +bun prune --production --filter app +``` + +``` +bun prune v1.4.0 (abc12345) + +- build-scripts@0.0.1 (packages/app/node_modules) +- typescript@5.4.0 +2 packages removed (checked 1103) [18.00ms] +``` + +Here `typescript` is a `node_modules/.bun` entry (isolated linker) that only `app`'s `devDependencies` needed, and `build-scripts` is `app`'s link to a workspace it lists there. + +### Notes + +- Works with both linkers. With the hoisted linker it checks every `node_modules` folder `bun install` would install into — including workspace folders and the nested `node_modules` folders of installed packages, so copies left behind by an earlier install are removed too (subject to the version check below); with the isolated linker it keeps the `node_modules/.bun` entries the current `bun.lock` installs (so stale `name@version+` peer variants left by earlier installs go away), minus those belonging to packages `--production`/`--omit` leave out, and removes the rest plus the symlinks that pointed at them; a kept package's entry is never removed because omitting dependencies would have given it a different peer set. With `--production`, the symlink of a dev-only dependency is removed and listed even when its `node_modules/.bun` entry stays because a production dependency also uses it. +- The linker is chosen the same way `bun install` chooses it. If `node_modules` was installed with the other linker (for example `bun install --linker hoisted` in a project that defaults to isolated), `bun prune` refuses to run instead of removing packages the other layout needs; pass the same `--linker` you installed with, or run `bun install` to switch layouts. +- Always runs against the workspace root, even when invoked inside a workspace package, and prunes the `node_modules` folder of every workspace package in that one run; with `--production`, a package that only a workspace's `devDependencies` need is removed too. +- Workspace folders themselves are never touched. With the isolated linker, `--production`/`--omit` also remove a workspace's link to another workspace when only its `devDependencies` listed it (a fresh `bun install --production` would not create that link); with the hoisted linker the workspace links live in the root folder and are always kept. `.bin` entries that are still in use, dot-entries such as `.cache`, plain files, and dependencies bundled inside a package's tarball are never removed. `.bin` entries whose package was removed are cleaned up on every platform. +- Nothing outside `node_modules` is ever deleted. A package folder that you replaced with a symlink is left alone, and the `node_modules` folder inside it is not pruned. With `install.globalStore`, only the project's links into the store are removed. +- `--global` (`-g`) is an error: the global folder is also where `bun link` registers packages, and those registrations are not in its `bun.lock`, so pruning it would unlink them. Like `pnpm prune`, `bun prune` only works on a project. +- Packages disabled for the current `os`/`cpu` are removed, just as `bun install` would skip them. Pass `--os` / `--cpu` to prune for another platform. +- What stays is decided by `bun.lock` alone, and packages are matched by name: a package that is installed under the right name at the wrong version is left for `bun install` to replace, and a dependency you removed from `package.json` is pruned after the `bun install` that updates `bun.lock` (until then `bun prune` refuses to run). A copy nested inside another package (`node_modules/a/node_modules/b`) is only removed once the copy that replaces it higher up is installed as `bun.lock` expects — verified from its `package.json` (name and version for registry packages, name only for tarball and `file:` packages), from `.bun-tag` for git dependencies, and by being a symlink for `link:` dependencies; until then it is kept and `bun prune` prints a `warn:` line naming both folders — typically after `bun prune --production` when a `devDependency` pinned a different version of `b` at the root. Run `bun install` (with the same flags), then `bun prune` again. `bun dedupe`, `bun audit fix` and `bun update` remove the nested copies they collapse themselves (hoisted linker), so `bun prune` is only needed for copies left behind by other tools or branch switches. +- A `bun.lock` that still lists workspaces whose folders were removed (a pruned checkout that `bun install --frozen-lockfile` accepts) is pruned the way that install would install it: the missing workspaces' links and the packages only they needed are removed, `--filter` does not keep them, and the same `note:` lines `bun install --frozen-lockfile` prints for such a checkout (the skipped workspaces, catalog entries left out of `bun.lock`) are printed. A remaining workspace that still depends on a missing one is an error, exactly as it is for `bun install`. +- If an entry cannot be deleted, the others are still removed, `error: failed to remove ...` is printed for each failure, the summary reads `N packages removed, F failed (checked C)` and the command exits with code 1 (`pnpm prune` only warns) — in a Dockerfile, a package that could not be removed should fail the build. +- Lifecycle scripts are never run. +- Equivalent to `pnpm prune` and `npm prune`. Unlike `pnpm prune`, it works in monorepos, and `--filter` narrows it to some workspaces. It does not touch the global cache; the counterpart of `pnpm store prune` is [`bun pm cache rm`](/pm/cli/pm#cache). Not related to `turbo prune`, which copies a subset of a monorepo's workspaces, `package.json` files and `bun.lock` into an output directory: `bun prune` only deletes from `node_modules`, so a Dockerfile can use `turbo prune`, then `bun install`, then `bun prune --production` after building. diff --git a/docs/pm/cli/remove.mdx b/docs/pm/cli/remove.mdx index 9df4d760cd7d..b3f8ac0f155e 100644 --- a/docs/pm/cli/remove.mdx +++ b/docs/pm/cli/remove.mdx @@ -7,10 +7,25 @@ import Remove from "/snippets/cli/remove.mdx"; ## Basic Usage +**Alias** — `bun rm`, `bun uninstall`, `bun r` + ```bash terminal icon="terminal" bun remove ts-node ``` +The package is deleted from every one of `dependencies`, `devDependencies`, `optionalDependencies` and `peerDependencies` it appears in (there is no flag to limit the removal to one of them), `bun.lock` is updated, and `node_modules/` is deleted once no package in the lockfile still depends on it. + +## `--filter` + +**Alias** — `-F` + +In a monorepo, `--filter` removes the package from the matching workspace package(s) instead of the package in the current directory, using the same patterns as [`bun add --filter`](/pm/cli/add#--filter). To remove a package from every workspace, use `--filter '*'` (the root `package.json` is only included when a pattern names it, e.g. `--filter '*' --filter ''`); there is no `--recursive`/`-r` flag. Selected workspaces that don't list the package are left untouched, and if none of them list it no `package.json` is edited and the command exits 0. + +```bash terminal icon="terminal" +bun remove zod --filter api +bun remove zod --filter '*' +``` + --- diff --git a/docs/pm/cli/update.mdx b/docs/pm/cli/update.mdx index 52afe28c03aa..5aee1c1686e6 100644 --- a/docs/pm/cli/update.mdx +++ b/docs/pm/cli/update.mdx @@ -1,24 +1,46 @@ --- title: "bun update" -description: "Update dependencies to latest versions" +description: "Update dependencies to the newest versions their ranges allow" --- import Update from "/snippets/cli/update.mdx"; To upgrade your Bun CLI version, see [`bun upgrade`](/installation#upgrading). -To update all dependencies to the latest version: +`bun update` (also spelled `bun up`) updates dependencies to the newest versions allowed by the ranges in `package.json`; [`bun update --latest`](#--latest) ignores those ranges. ```sh terminal icon="terminal" bun update ``` -To update a specific dependency to the latest version: +To update specific packages, pass their names. A `@range` suffix applies to the entry in your `package.json`: `bun update jquery@3` moves it to the newest `3.x` even if its current range does not allow that. A suffix cannot be combined with `--latest`; `bun update jquery@3 --latest` is an error. + +A name can also be a pattern: `bun update '@types/*'` updates every package whose name matches, `bun update '!webpack'` updates everything except the excluded names, and the two combine (`bun update '@babel/*' '!@babel/core'`). Quote patterns so your shell does not expand them. Patterns are matched against the packages `bun.lock` records for the workspace you are in, so they reach nested packages too, and they need an existing `bun.lock`. A pattern that matches nothing is an error (patterns that exclude everything are a no-op, [below](#--dev---prod---no-optional)), and a pattern cannot carry a `@range`. ```sh terminal icon="terminal" bun update [package] +bun update zod jquery@3 +bun update '@types/*' ``` +A plain `bun update` updates the whole tree, like `pnpm update` and `npm update`: the dependencies declared in `package.json` move within their declared ranges and have their `package.json` entries rewritten, and every package that other packages in `bun.lock` depend on is re-resolved exactly as a fresh `bun install` would resolve its dependent's range today (so `minimumReleaseAge` and a `latest` tag that lags behind the newest publish apply the same way), and moves only if that is newer than what is locked. Each dependent is resolved against its own range, so two packages declaring different ranges on the same dependency can end up on different versions. This also applies when one of the dependents is your own `package.json`: an exact pin there stays put, and a package depending on a range of the same name moves to the newest version in that range, so `bun.lock` ends up with two copies. Ranges are never widened: a package depending on `^1.0.0` never picks up `2.x`; update the package that declares the range instead. A package that depends on a dist-tag (`"foo": "next"`) is re-resolved to whatever that tag points at now. The [`overrides`](/pm/overrides) and catalogs in effect are the ones in your `package.json` now, not the ones recorded in `bun.lock`, so a package held back by an override you have since removed or loosened moves on the next `bun update`. Transitive updates only change `bun.lock` and `node_modules` (with the hoisted linker, a nested copy — `node_modules/a/node_modules/b` — whose version the update merged into the copy above it is deleted, so `a` loads the updated version), never `package.json`, and show up in the install summary as `↑ name old → new` rows, the same rows a direct dependency that moved gets, each package once (with `(v3.0.0 available)` after a row when a newer version is out of range). `--dry-run` prints the same rows followed by `N packages would be updated [12.00ms]` in place of the install summary; when nothing would move it prints `Checked N packages, nothing to update [12.00ms]`, where `N` counts the packages in `bun.lock`. A real run that moves nothing ends with install's usual `Checked N installs across M packages (no changes)` line. Run from the root of a monorepo, this covers everything any workspace reaches; run inside a workspace, or with `--filter`, it only re-resolves the packages the selected workspaces reach ([below](#--recursive-and---filter)). + +`bun update ` re-resolves `` wherever the workspace you run it in depends on it, directly or through other packages (at the root of a monorepo, that includes everything its workspaces pull in), and nothing else: dependencies you did not name keep their locked versions, and so do the dependencies of `` itself as long as its new version still allows them. `bun update --latest` additionally moves the dependencies of `` to the newest versions its ranges allow, the same as a plain `bun update` would for them. `` is matched by the name of the package that gets installed, so a dependency declared as `"foo": "npm:@^1"` is updated as well, and it is also matched by the alias, so `bun update foo` updates that same entry. If `` is declared in your `package.json`, that entry is rewritten too; if it is only a dependency of your dependencies, only `bun.lock` changes, which is how to pick up a fix in a nested package (`bun update caniuse-lite`) without adding it to your own dependencies or using `overrides`. A name that is not in `bun.lock` at all is an error, not a no-op; use `bun add` to add a dependency. In a monorepo, another workspace's own entry for `` is not re-resolved and its `package.json` is not touched; it only moves along when the version picked here also satisfies its range, so `bun.lock` does not gain a second copy. Run `bun update ` inside that workspace, or use `-r`/`--filter` ([below](#--recursive-and---filter)), to update it there; if only other workspaces depend on ``, `bun update ` is an error. + +## What is held back + +A package that is only in `bun.lock` because it satisfies another package's `peerDependencies`, with nothing depending on it outright, is re-resolved like any other package by `bun update`, `bun update ` and `bun update --latest`. A peer entry whose package something else does depend on keeps following that package rather than getting a second copy. The entries in your own `peerDependencies` are only rewritten or moved when their package moves for another reason. + +A version listed in `patchedDependencies` is not moved by `bun update` or `bun update ` as long as the ranges pointing at it still allow it, whether it is declared in `package.json` or nested. Every patched version an update leaves in place when its ranges would have allowed something newer is listed as `kept name@version (patched, v1.2.3 available)`. `--latest`, a range you have edited so that it no longer allows the patched version, and [`bun audit fix`](/pm/cli/audit#bun-audit-fix) do move it; to move on deliberately, update with `--latest` and re-create the patch on the new version with [`bun patch`](/pm/cli/patch). + +To find out what can move, a plain `bun update` requests the abbreviated manifest of every npm package name in `bun.lock` that a re-resolvable entry points at: one request per name, at most `--network-concurrency` of them at a time (see the flags below). A name whose request fails keeps its locked version and gets one `warn: name@version was not checked for updates: ` line on stderr, the same line [`bun audit fix`](/pm/cli/audit#bun-audit-fix) prints (`--silent` suppresses it); the command still exits 0. A failed request for a dependency declared in `package.json` is an error, as it is for `bun install`. + +## What an update writes + +`bun update` rewrites a `package.json` entry only when it is written as `^x.y.z`, `~x.y.z` or an exact version, and keeps the operator you declared: `^1.1.0` becomes `^1.2.0`, `~` stays `~`, and an exact pin is left alone unless you pass `--latest`. With [`install.exact`](/runtime/bunfig#install-exact) (or `--exact`) the rewritten entry is an exact version even if it was declared with `^` or `~`. Any other range (`*`, `1`, `1.x`, `>=1.0.0`, `1.0.0 - 1.5.0`, or an alias without a version such as `npm:foo`) is left exactly as written and only `bun.lock` and `node_modules` move. A short range is rewritten only when filling it out keeps the same upper bound: `^1` and `~1.2` are rewritten (`^1.1.0`, `~1.2.3`), while `~1` and `^0` are kept as written, since `~1.5.0` or `^0.3.0` would allow less than they do. `--latest` does rewrite such a range, to a range of the same width on the latest version: `1` becomes `^2.0.0`, `1.0` becomes `~2.0.0`, and any other form (`*`, `1.x`, `>=1.0.0`, `1.0.0 - 1.5.0`, `npm:foo`) becomes a `^` range. A `catalog:` reference is never rewritten; a plain `bun update` updates the catalog entry in the root `package.json` instead. A dist-tag such as `"foo": "latest"` or `"foo": "next"` (also inside a catalog, or behind `npm:foo@next`) is kept as written by `bun update` and `bun update foo`, and `bun.lock` follows whatever the tag points at now; `--latest` replaces it with a `^` range (or an exact version with `install.exact`) on the version `latest` points at, so `"foo": "next"` becomes `"foo": "^2.0.0"` and `npm:foo@next` becomes `npm:foo@^2.0.0`. `--dry-run` prints what would change without writing anything, and `--no-save` updates `node_modules` but leaves both `package.json` and `bun.lock` untouched, so it never changes what the next `bun install` produces. + +After any `bun update` or `bun add`, `bun.lock` records the same ranges that were written to `package.json`, so a following `bun install --frozen-lockfile` passes. + ## `--interactive` Use the `--interactive` flag to choose which packages to update: @@ -28,7 +50,7 @@ bun update --interactive bun update -i ``` -The flag opens a terminal interface that lists every outdated package with its current and target versions. +The flag opens a terminal interface that lists every outdated direct dependency with its current and target versions (`--dev`, `--prod` and `--no-optional` narrow that list, see [below](#--dev---prod---no-optional)). Once you confirm, the selection is applied like `bun update ` with the selected names: those entries are rewritten in `package.json` and updated everywhere they occur in `bun.lock`, while the packages you left unselected, and every other transitive package, keep their locked versions. Confirming with nothing selected changes nothing. ### Interactive Interface @@ -99,25 +121,49 @@ Packages are organized in sections by dependency type: Within each section, individual packages may have a suffix (` dev`, ` peer`, ` optional`). -## `--recursive` +## `--recursive` and `--filter` -Use the `--recursive` flag with `--interactive` to update dependencies across all workspaces in a monorepo: +In a monorepo, a plain `bun update` rewrites only the `package.json` of the workspace you run it in, and which packages move in `bun.lock` depends on where you run it: from the root, every package any workspace reaches is re-resolved; from inside a workspace, only the packages that workspace reaches, so a package only other workspaces depend on keeps its locked version. `bun update --recursive` (`-r`) updates the direct dependencies of every workspace, rewrites each workspace's `package.json` and re-resolves every package in `bun.lock`; `bun update --filter ` (`-F`) limits both to the matching workspaces and the packages they reach; the pattern syntax, including `{dir}` and `...` relations, is described on the [filtering](/pm/filter) page. Both need an existing `bun.lock`. They also work with `--interactive`, which then adds a "Workspace" column showing which workspace each dependency belongs to. + +`bun update -r` and `bun update --filter ` rewrite the entry for `` in every selected workspace whose `package.json` declares it, each keeping its own `^`/`~`/exact style; the `package.json` of a workspace that was not selected is left alone; in `bun.lock`, `` is re-resolved wherever a selected workspace reaches it, directly or through other packages, and a copy that only unselected workspaces reach keeps its locked version. A name that none of the selected workspaces reaches, directly or through their dependencies, is an error, even if another workspace uses it. `--latest` and `--dry-run` apply the same way; in particular, `--latest` keeps a workspace's entry that is already locked ahead of the `latest` tag where it is, exactly as it does when run inside that workspace ([below](#--latest)). With `--filter`, with or without names, the install that follows links only the selected workspaces, exactly like [`bun install --filter`](/pm/filter), and a pattern that matches nothing prints a warning; `-r` installs every workspace: ```sh terminal icon="terminal" -bun update --interactive --recursive +bun update --recursive +bun update --filter './packages/*' bun update -i -r +bun update zod -r +bun update zod --filter '...^ui' +``` + +## `--dev`, `--prod`, `--no-optional` + +`bun update --dev` (`-D`) only updates the entries in `devDependencies`; `--prod` (`-P`, also `-p`/`--production`) only those in `dependencies` and `optionalDependencies`; `--no-optional` skips `optionalDependencies`. These select which entries of the current workspace's `package.json` (or of the workspaces chosen with `-r`/`--filter`) are updated, and combine with names, patterns and `--latest`. They do not change what gets installed: `bun update --production` still installs `devDependencies`, unlike `bun install --production`. The bunfig setting [`install.production`](/runtime/bunfig#install-production) is unrelated to these flags: it puts `bun update` (like `bun add`/`bun remove`) into install's production mode, so with it set `bun update` fails with `lockfile had changes, but lockfile is frozen` as soon as anything would be updated. The flags need an existing `bun.lock`; when no entry qualifies they change nothing and print how many entries were checked, for example `Checked 3 dependencies, none selected by --dev (no changes) [12.00ms]` (a set of patterns that excludes everything prints `none match "!webpack"` the same way). With `--interactive` the same flags decide which entries the picker lists (`bun update -i --dev`): + +```sh terminal icon="terminal" +bun update --dev +bun update --prod --latest +bun update -D '@types/*' +bun update -i --prod ``` -With `--recursive`, the interface adds a "Workspace" column showing which workspace each dependency belongs to. +## `--global` + +`bun update -g` (`--global`) updates the packages installed with `bun add -g` instead of the current project, following the same rules; `bun update -g ` and `--latest` work there too: + +```sh terminal icon="terminal" +bun update -g +bun update -g typescript +``` ## `--latest` By default, `bun update` updates each dependency to the latest version that satisfies the version range in your `package.json`. -To update to the latest version regardless of whether it satisfies that range, use the `--latest` flag: +To update the dependencies declared in `package.json` to the latest version regardless of whether it satisfies that range, use the `--latest` flag (`-L`). Transitive packages still move within the ranges their dependents declare, and an entry of the workspace you run in that is already ahead of the `latest` tag (for example a prerelease) is left where it is rather than downgraded. An entry you declared as a dist-tag (`"foo": "next"`) is replaced with a range on the version `latest` points at, even when its own tag was ahead of `latest`; a `"foo": "next"`-style entry declared by another package or workspace still resolves through its own tag: ```sh terminal icon="terminal" bun update --latest +bun update -L ``` In interactive mode, press **l** to toggle a package between its target version (respecting semver) and the latest version. diff --git a/docs/pm/filter.mdx b/docs/pm/filter.mdx index 802554702fd1..1cd356e82c19 100644 --- a/docs/pm/filter.mdx +++ b/docs/pm/filter.mdx @@ -3,9 +3,9 @@ title: "bun --filter" description: "Select packages by pattern in a monorepo using the --filter flag" --- -The `--filter` (or `-F`) flag selects packages in a monorepo by pattern. Patterns match package names or package paths, with full glob syntax. +The `--filter` (or `-F`) flag selects packages in a monorepo by pattern. A pattern is a package name glob, a `./path` glob, a `{dir}` directory selector, or a `...` dependency relation. -`bun install` and `bun outdated` support `--filter`, and you can use it to run scripts in multiple packages at once. +The same patterns are understood by `bun install`, `bun add`, `bun remove`, `bun update` and `bun outdated`, and by `bun run --filter` (including `--parallel` and `--sequential`), which runs a script in every selected package. For the package-manager commands, pass the flag after the subcommand or write it as `--filter=` before it, because `bun --filter ` always runs the script ``. For `bun add`, `bun remove` and `bun update` the filter selects which workspace `package.json` files are edited and, like `bun install --filter`, which workspaces get installed; the `node_modules` of an unselected workspace is left as it was. --- @@ -19,12 +19,41 @@ Name patterns select packages by the `name` field in `package.json`. For example Path patterns start with `./` and select all packages in directories matching the pattern. For example, to match all packages in subdirectories of `packages`, use `--filter './packages/**'`. To match the package in `packages/foo`, use `--filter ./packages/foo`. +Patterns are matched against the full package name -- `--filter core` does not select `@acme/core` (use `@acme/core` or `@acme/*`), and `*` does not cross `/`. Path patterns must match a workspace's directory itself: `--filter ./packages` selects nothing, `--filter './packages/*'` selects every workspace directly inside it, and `--filter '!./lib'` only excludes a workspace located at `./lib`. + +### Directory `--filter '{}'` + +A directory in braces selects every workspace located in that directory or anywhere below it. The directory is resolved from the current directory: `--filter '{packages}'` and `--filter '{./packages}'` select every workspace under `packages`, and `--filter '{.}'` selects the workspace in the current directory and everything under it. Unlike a `./path` pattern, which has to match a workspace's own directory, the directory in braces can be any parent of the workspaces you want. A pattern that starts with `{` is always a directory selector. + +### Dependency relations `--filter 'foo...'` + +Adding `...` to a pattern also selects the workspaces related to it through workspace dependencies: + +| Pattern | Selects | +| --------- | -------------------------------------------------------------------- | +| `foo...` | `foo` and the workspaces it depends on, directly or transitively | +| `foo^...` | only the workspaces `foo` depends on, not `foo` itself | +| `...foo` | `foo` and the workspaces that depend on it, directly or transitively | +| `...^foo` | only the workspaces that depend on `foo`, not `foo` itself | + +The middle part is a name glob or a directory selector (`...{./packages/api}`), and `!` still goes first: `--filter '!...foo'` drops `foo` and everything that depends on it. + +For `bun add`, `bun remove` and `bun update `, relations are read from the workspace `package.json` files as they are now: in any dependency group, a `workspace:` entry counts as a link, and so does a plain range that the workspace's current `version` satisfies (see [`linkWorkspacePackages`](/runtime/bunfig#install-linkworkspacepackages)). They work before the first install and pick up a `package.json` you have just edited. `bun install --filter` selects from the tree it has just resolved, and `bun outdated`, `bun prune` and `bun pm licenses` with `--filter` read the links from `bun.lock`. `bun run --filter` instead follows the `dependencies`, `devDependencies` and `optionalDependencies` entries of the workspace `package.json` files themselves (the same links Bun uses to order the scripts), so it needs no `bun.lock` and works before the first install. + +```bash terminal icon="terminal" +bun install --filter 'web...' +bun add zod --filter '...^ui' +bun outdated --filter '{./packages/apps}' +``` + --- ## `bun install` and `bun outdated` By default, `bun install` installs dependencies for every package in the monorepo. To install dependencies for specific packages, use `--filter`. +A set of patterns selects everything matched by a positive pattern, minus everything matched by a `!` pattern, so `bun install --filter api --filter '!web'` installs only `api`. A positive pattern that matches nothing prints `No workspace packages matched the filter ""` (`the filters "a", "b"` when several patterns matched nothing); `!` patterns never do. For `bun install` and `bun outdated` this is a warning: if nothing is selected, `bun install` installs nothing and exits 0. For `bun add`, `bun remove` and `bun update` (with `--filter` or `-r`), as well as `bun prune` and `bun pm licenses`, the same sentence is an error and the command exits 1 without editing or installing anything when the filters select no workspace at all; if at least one workspace is selected, the unmatched patterns only warn and the command continues. + Given a monorepo with workspaces `pkg-a`, `pkg-b`, and `pkg-c` under `./packages`: ```bash terminal icon="terminal" @@ -81,6 +110,24 @@ Both scripts run in parallel, and a terminal UI shows their respective outputs: ![Terminal Output](https://github.com/oven-sh/bun/assets/48869301/2a103e42-9921-4c33-948f-a1ad6e6bac71) +The other pattern forms work the same way: + +```bash terminal icon="terminal" +# build web and everything it depends on, in dependency order +bun --filter 'web...' build + +# test the packages that depend on ui +bun --filter '...^ui' test + +# run dev in every package under packages/apps +bun --filter '{./packages/apps}' dev + +# lint every package except docs +bun --filter '*' --filter '!docs' lint +``` + +A `!` pattern removes packages matched by the other patterns, and a `package.json` without a `name` is only selected by a `./path` pattern. If the patterns select no package with the script, `bun run` prints `error: No workspace packages matched the filter ""` and exits 1 (pass `--if-present` to exit 0 instead). + ### Running scripts in workspaces Filters respect your [workspace configuration](/pm/workspaces): if your `package.json` specifies which packages are part of the workspace, diff --git a/docs/pm/isolated-installs.mdx b/docs/pm/isolated-installs.mdx index 41a95f2799fb..0a1b080d3cc8 100644 --- a/docs/pm/isolated-installs.mdx +++ b/docs/pm/isolated-installs.mdx @@ -91,6 +91,7 @@ node_modules/ 2. **Symlinks** — Top-level `node_modules` contains symlinks pointing to the central store 3. **Peer resolution** — Complex peer dependencies create specialized directory names 4. **Deduplication** — Packages with identical package IDs and peer dependency sets are shared +5. **Re-linking** — On later installs, existing store entries are kept but their `node_modules/` symlinks are checked against the lockfile and re-pointed when a dependency was re-resolved (for example after [`bun dedupe`](/pm/cli/dedupe) or adding an override); store entries nothing references any more — including the previous `name@version+` variant of a package whose peer dependency was just updated — are left in place until you run [`bun prune`](/pm/cli/prune), which removes exactly the entries a fresh install would not create ### Workspace handling diff --git a/docs/pm/lockfile.mdx b/docs/pm/lockfile.mdx index c0a7ce23fd82..ef4357f0ff07 100644 --- a/docs/pm/lockfile.mdx +++ b/docs/pm/lockfile.mdx @@ -11,12 +11,14 @@ Yes #### Generate a lockfile without installing? -To generate a lockfile without installing to `node_modules`, use the `--lockfile-only` flag. The lockfile is always saved to disk, even if it is already up to date with your project's `package.json`(s). +To generate a lockfile without installing to `node_modules`, use the `--lockfile-only` flag. The lockfile is always saved to disk, even if it is already up to date with your project's `package.json`(s), unless `--frozen-lockfile` (or `--production`) is set. ```bash terminal icon="terminal" bun install --lockfile-only ``` +The flag is also accepted by `bun add`, `bun remove` and `bun update`, which still edit `package.json` and write `bun.lock` but leave `node_modules` untouched. + `--lockfile-only` still populates the global install cache with registry metadata and git/tarball dependencies. @@ -57,7 +59,9 @@ For more on the format, see [the blog post](https://bun.com/blog/bun-lock-text-l When running `bun install` in a project without a `bun.lock`, Bun automatically migrates existing lockfiles: - `yarn.lock` (v1) -- `package-lock.json` (npm) +- `package-lock.json` (npm, `lockfileVersion` 2, 3 or 4) - `pnpm-lock.yaml` (pnpm) +A `package-lock.json` written by npm 6 or older (`lockfileVersion` 1) is not migrated; Bun prints a warning and resolves from `package.json` instead. The migrated lockfile also carries over the `overrides` from `package.json`, so `bun install --frozen-lockfile` works immediately after `bun pm migrate`. + The original lockfile is preserved and can be removed manually after verification. diff --git a/docs/pm/npmrc.mdx b/docs/pm/npmrc.mdx index 550366950777..2fc285bb3b4d 100644 --- a/docs/pm/npmrc.mdx +++ b/docs/pm/npmrc.mdx @@ -5,6 +5,10 @@ description: Bun loads configuration options from [`.npmrc`](https://docs.npmjs.com/cli/v10/configuring-npm/npmrc) files, so you can reuse your existing registry and scope configuration. +Bun reads `~/.npmrc` (or `$XDG_CONFIG_HOME/.npmrc` if it exists), then the project's `./.npmrc`, then `bunfig.toml`; `NPM_CONFIG_USERCONFIG` is not consulted. When the same option is set in more than one place, `./.npmrc` overrides `~/.npmrc`, `bunfig.toml` overrides both `.npmrc` files (the project's `bunfig.toml` overriding the global `~/.bunfig.toml`), the `BUN_CONFIG_REGISTRY` / `NPM_CONFIG_REGISTRY` and `BUN_CONFIG_TOKEN` / `NPM_CONFIG_TOKEN` environment variables override the default registry and its token from any of those files, and command-line flags such as `--registry` override everything. Registry credentials that only exist in `.npmrc` (`///:_authToken`, `username`, `_password`, `_auth`) are matched to the default and scoped registries that end up in effect by host and path, whether the registry URL was set in `.npmrc` or `bunfig.toml` and regardless of a trailing slash; a token, username or password set on a registry directly in `bunfig.toml` takes precedence over these lines. + +Values in both `~/.npmrc` and `./.npmrc` may reference environment variables: `${NAME}` is replaced with the variable's value and left as the literal text `${NAME}` when the variable is unset, while `${NAME?}` becomes an empty string when unset. + We recommend migrating your `.npmrc` file to Bun's [`bunfig.toml`](/runtime/bunfig) format, which supports more options, including Bun-specific ones. diff --git a/docs/pm/overrides.mdx b/docs/pm/overrides.mdx index 8cb771da2dbd..2ff0a671e64b 100644 --- a/docs/pm/overrides.mdx +++ b/docs/pm/overrides.mdx @@ -45,11 +45,6 @@ If a security vulnerability is introduced in `bar@4.5.6`, you may want to pin `b Add `bar` to the `"overrides"` field in `package.json`. Bun defers to the specified version range when determining which version of `bar` to install, whether it's a dependency or a metadependency. - - Bun only supports top-level `"overrides"`, not [nested - overrides](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#overrides). - - {/* prettier-ignore */} ```json package.json icon="file-json" { @@ -63,12 +58,14 @@ Add `bar` to the `"overrides"` field in `package.json`. Bun defers to the specif } ``` +Bun only reads `"overrides"` and `"resolutions"` from the root `package.json`; the field is ignored in workspace packages, and pnpm's `pnpm.overrides` field and `pnpm-workspace.yaml` are only consulted when migrating a `pnpm-lock.yaml`, which moves those rules into `"overrides"`. + +Rules also apply to `peerDependencies`. An overridden peer dependency is still a peer dependency; only its version range changes. + ## `"resolutions"` `"resolutions"` is Yarn's alternative to `"overrides"`, with similar syntax. Bun supports it to make migration from Yarn easier. -As with `"overrides"`, _nested resolutions_ are not supported. - {/* prettier-ignore */} ```json package.json icon="file-json" { @@ -81,3 +78,86 @@ As with `"overrides"`, _nested resolutions_ are not supported. } // [!code ++] } ``` + +## Values + +A value can be any dependency specifier, not only a version range. `npm:` swaps a package for a fork, and `catalog:` (or `catalog:`) keeps the overridden version in sync with a [workspace catalog](/pm/catalogs): + +```json package.json icon="file-json" +{ + "name": "my-app", + "overrides": { + "quux": "npm:@myorg/quux@^1.0.0", + "foo": "catalog:" + } +} +``` + +A value of `"$name"` copies the range you declared for `name` in your own `dependencies` (or `devDependencies`, `peerDependencies`, `optionalDependencies`); the referenced package doesn't have to be the one being overridden, so `"bar": "$foo"` pins `bar` to whatever range you declared for `foo`. If the root doesn't declare `name`, Bun looks in the workspace packages instead, which must all declare the same range for it. + +## Nested overrides + +A rule can be scoped to the dependencies of one package. Bun applies it to that package's direct dependency on the overridden package and nothing else, so the same package can resolve to different versions under different parents. + +The npm object form, the pnpm `>` form, and a parent with a version range are all accepted in `"overrides"`: + +```json package.json icon="file-json" +{ + "name": "my-app", + "overrides": { + "micromatch": { + ".": "^4.0.5", + "picomatch": "^2.3.2" + }, + "micromatch>picomatch": "^2.3.2", + "micromatch@^4>picomatch": "^2.3.2" + } +} +``` + +`"."` inside an object overrides `micromatch` itself, like a top-level `"micromatch"` rule. + +`"resolutions"` accepts Yarn's path form. `**` is accepted for compatibility, but only the parent's direct dependency is affected either way: + +```json package.json icon="file-json" +{ + "name": "my-app", + "resolutions": { + "micromatch/picomatch": "^2.3.2", + "**/micromatch/**/picomatch": "^2.3.2" + } +} +``` + +`$name` references and the other [value forms](#values) work in nested rules too. + +When several rules match one dependency, rules scoped to a parent with a version range win over rules scoped to a parent without one, which win over top-level rules. Within each of those groups, a rule with a matching [version selector](#version-scoped-overrides) wins over one without, and if several selectors match, the one whose range text sorts first is used. + +## Version-scoped overrides + +The overridden package's key can carry a version selector, so a rule only rewrites some of the edges pointing at that package. This is the shape `pnpm audit --fix` writes and the one npm documents, and it works in top-level and nested rules alike: + +```json package.json icon="file-json" +{ + "name": "my-app", + "overrides": { + "semver@<7.5.2": "7.5.2", + "webpack>terser@4": "4.8.1", + "terser@4": { + ".": "4.8.1" + } + } +} +``` + +`"terser@4": { ".": "4.8.1" }` is the object spelling of `"terser@4": "4.8.1"`; a selector on a child key, as in `{ "webpack": { "terser@4": "4.8.1" } }`, scopes that nested rule the same way. + +The selector is compared with the range each dependent _declares_ for that dependency, not with the version that would be installed, and the rule applies to every edge whose declared range overlaps the selector. `"semver@<7.5.2"` rewrites an edge declared as `^7.3.0`, since that range could still pick `7.3.x`, but leaves an edge declared as `^7.5.2` alone. Edges declared with a dist-tag such as `latest`, or with a `catalog:`, `workspace:`, git, or URL specifier, never match a selector. An `npm:` prefix inside a selector is ignored. + +## Limitations + +- Only one parent level is supported. `a>b>c`, `a/b/c`, and objects nested more than one level deep are ignored with a warning. +- `"pkg@"` (pnpm's convergence override, an empty selector) is not supported and is skipped with a warning. +- pnpm's `"-"` value, which removes a dependency, is not supported. Bun skips the rule with a warning and the dependency resolves from its declared range as if the rule weren't there. +- A nested rule can't point at a `file:` path outside the project. +- A `bun.lock` that contains nested or version-scoped rules is written as `lockfileVersion` 3, which older versions of Bun refuse to read (run `bun upgrade`). The exception is a lockfile that Bun already has to keep at `lockfileVersion` 1 — one with an npm package resolved to a tarball outside the default registry and no integrity hash, or a git dependency with an unsafe tag — which stays at 1 with the rules written as objects, so the version stamp never depends on the registries configured on the machine that wrote it. Projects without such rules keep their existing `lockfileVersion`. diff --git a/docs/runtime/bunfig.mdx b/docs/runtime/bunfig.mdx index af34fadf6f59..86f250678ec6 100644 --- a/docs/runtime/bunfig.mdx +++ b/docs/runtime/bunfig.mdx @@ -430,7 +430,7 @@ peer = true Whether `bun install` runs in "production mode". Default `false`. -In production mode, `"devDependencies"` are not installed. The `--production` CLI flag overrides this setting. +In production mode, `"devDependencies"` are not installed, and `bun.lock` is treated as frozen, the same as setting [`install.frozenLockfile`](#install-frozenlockfile) to `true`. The `--production` CLI flag turns production mode on for a single install; there is no flag to turn it off when it is enabled here. It applies to every command that runs an install, not only `bun install`: with it enabled, `bun add`, `bun remove` and `bun update` fail with `lockfile had changes, but lockfile is frozen` whenever they would change `bun.lock`, and `bun update --prod` (which only selects what to update) does not turn it on or off. ```toml title="bunfig.toml" icon="settings" [install] diff --git a/docs/runtime/index.mdx b/docs/runtime/index.mdx index 36924689a424..5333c55d7e2d 100644 --- a/docs/runtime/index.mdx +++ b/docs/runtime/index.mdx @@ -144,7 +144,7 @@ bun run --bun vite In a monorepo, the `--filter` argument runs a script in many packages at once. -`bun run --filter