bun install can install system packages next to npm packages. You list them under systemDependencies in package.json, keyed as <source>:<id>. Bun resolves them, records them in bun.lock, and installs them. It reads winget's index directly and never calls winget.exe.
{
"name": "my-app",
"systemDependencies": {
"winget:jqlang.jq": "*",
"winget:Microsoft.PowerToys": "0.100.0"
}
}
Adding and removing
bun add winget:jqlang.jq
bun add winget:Microsoft.PowerToys@0.100.0
bun remove winget:jqlang.jq
A specifier is <source>:<id>[@<range>]. If you leave out the range, Bun writes "*". Package ids are case-insensitive. The key written to package.json is the one you typed.
Ranges compare version components numerically, the way winget does. Supported ranges are *, an exact version, 1.2.x, >=, >, <=, <, ^, ~ and alternatives joined with ||.
How resolution works
Bun reads the winget community source the same way winget does:
- It downloads
source2.msixand readsPublic/index.dbwith Bun's built-in SQLite reader. The index is cached for one hour. - It downloads
packages/<id>/<hash>/versionData.mszymland decompresses the list of versions. - It downloads the manifest for the chosen version and checks it against the sha256 listed in the version data.
The result is pinned in the "system" block of bun.lock (or bun.lockb). Each entry records the version, the manifest URL and the manifest's sha256. bun install --frozen-lockfile fails when systemDependencies no longer matches the lockfile.
Installing
On Windows, bun install picks the installer that best matches the host architecture, the requested scope and the locale. Before running anything, it checks the installer against the manifest's InstallerSha256.
| Installer type | What Bun does |
|---|---|
portable, zip | Extracts into node_modules/.system/winget/<id> and creates node_modules/.bin/<command> shims |
msi, wix, burn, inno, nullsoft | Runs the installer silently with the manifest's switches |
exe | Runs the installer only if the manifest provides a Silent switch |
msix, appx | Runs Add-AppxPackage |
Some installers need administrator rights: those with ElevationRequirement, machine-scoped ones, and msi, inno or burn installers with no scope. Bun refuses to run these unless the shell is already elevated or BUN_SYSTEM_ELEVATE=1 is set.
Bun records what it installed in node_modules/.system/.bun-system/installed.json. When an entry leaves the lockfile, bun install uninstalls it: msiexec /x for msi packages, Remove-AppxPackage for msix packages, and the registered uninstall command for the others.
On other platforms, winget packages are still resolved and locked, but installing them is skipped with a warning. A lockfile created on Linux or macOS therefore works unchanged on Windows.
Environment variables
| Variable | Effect |
|---|---|
BUN_WINGET_SOURCE | Base URL of the winget source. Default: https://cdn.winget.microsoft.com/cache |
BUN_SYSTEM_ROOT | Install into this directory instead of node_modules/.system. Shims go to <root>/bin |
BUN_SYSTEM_ELEVATE | Set to 1 to allow installers that need administrator rights |
BUN_SYSTEM_ARCH | Override the host architecture: x64, arm64 or x86 |
BUN_SYSTEM_SCOPE | Prefer user or machine installers |
BUN_SYSTEM_LOCALE | Preferred installer locale, for example en-US |
BUN_SYSTEM_REFRESH | Set to 1 to download the index again even if the cached copy is less than an hour old |
Downloaded indexes, manifests and installers are cached under <install cache>/system/winget.