Scarlet.Bun.MSBuild
1.1.0
See the version list below for details.
dotnet add package Scarlet.Bun.MSBuild --version 1.1.0
NuGet\Install-Package Scarlet.Bun.MSBuild -Version 1.1.0
<PackageReference Include="Scarlet.Bun.MSBuild" Version="1.1.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Scarlet.Bun.MSBuild" Version="1.1.0" />
<PackageReference Include="Scarlet.Bun.MSBuild"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Scarlet.Bun.MSBuild --version 1.1.0
#r "nuget: Scarlet.Bun.MSBuild, 1.1.0"
#:package Scarlet.Bun.MSBuild@1.1.0
#addin nuget:?package=Scarlet.Bun.MSBuild&version=1.1.0
#tool nuget:?package=Scarlet.Bun.MSBuild&version=1.1.0
Scarlet.Bun.MSBuild
An MSBuild task package that integrates Bun - a fast all-in-one JavaScript runtime - into your .NET build process. This package allows you to run Bun commands as part of your .NET project build, enabling JavaScript/TypeScript bundling, minification, and other Bun-powered operations.
Table of Contents
- Features
- Installation
- Runtime Options
- Usage
- Example: JavaScript/SCSS Build Script
- Development
- Requirements
- License
- Credits
- Contributing
Features
- ✅ Cross-platform support (Windows, Linux, macOS - x64 and ARM64)
- ✅ Multiple runtime options: embedded packages or on-demand download
- ✅ Easy MSBuild integration
- ✅ Supports modern .NET / .NET Core (no .NET Framework support)
- ✅ Execute any Bun command during build
Installation
Install the main MSBuild task package:
dotnet add package Scarlet.Bun.MSBuild
Or via Package Manager:
Install-Package Scarlet.Bun.MSBuild
Note: The base package does not include any Bun runtime. You must choose a runtime option (see Runtime Options below).
Runtime Options
After installing Scarlet.Bun.MSBuild, you need to provide the Bun runtime. There are three approaches to choose from based on your needs:
Option 1: Runtime Download
Download the Bun runtime automatically during build by setting the BunRuntimeDownload property:
<PropertyGroup>
<BunRuntimeDownload>true</BunRuntimeDownload>
<BunVersionDownload>1.3.6</BunVersionDownload>
<BunRuntimeDirectory>$(MSBuildProjectDirectory)/runtimes</BunRuntimeDirectory>
</PropertyGroup>
Advantages:
- Simplest configuration - just set a few properties
- No additional package dependencies to manage
- Each developer/CI agent only downloads the runtime package they need
- Runtime downloaded only once and cached locally for subsequent builds
- Can easily switch Bun versions by changing
BunVersionDownloadproperty
See Using Runtime Download for detailed configuration.
Option 2: Platform-Specific Runtime Packages
Install only the runtime package(s) you need for your target platform(s):
# For Windows x64
dotnet add package Scarlet.Bun.Runtime.windows-x64-baseline
# For Windows ARM64
dotnet add package Scarlet.Bun.Runtime.windows-aarch64
# For Linux x64
dotnet add package Scarlet.Bun.Runtime.linux-x64-baseline
# For Linux ARM64
dotnet add package Scarlet.Bun.Runtime.linux-aarch64
# For macOS x64
dotnet add package Scarlet.Bun.Runtime.darwin-x64-baseline
# For macOS ARM64 (Apple Silicon)
dotnet add package Scarlet.Bun.Runtime.darwin-aarch64
Note: Runtime packages are versioned independently from
Scarlet.Bun.MSBuild. Their package version corresponds to the bundled Bun version (e.g., package version1.3.6contains Bun1.3.6).
Advantages:
- Explicit control over which runtimes are included
- No runtime downloads during build (runtimes come from NuGet packages)
- Works offline
Trade-offs:
- Package with needed runtime version might be missing
Available Runtime Packages:
Option 3: Conditional Package References
Use MSBuild conditions to reference only the runtime package matching the current build platform:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Scarlet.Bun.MSBuild" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<PropertyGroup>
<IsWindows Condition="'$(OS)' == 'Windows_NT'">true</IsWindows>
<IsLinux Condition="Exists('/proc')">true</IsLinux>
<IsMacOS Condition="Exists('/System/Library/CoreServices/SystemVersion.plist')">true</IsMacOS>
</PropertyGroup>
<PropertyGroup>
<IsARM64 Condition="'$(PROCESSOR_ARCHITECTURE)' == 'ARM64' OR '$(PROCESSOR_IDENTIFIER)' == 'ARM64'">true</IsARM64>
<IsX64 Condition="'$(PROCESSOR_ARCHITECTURE)' == 'AMD64' OR '$(PROCESSOR_IDENTIFIER)' == 'AMD64'">true</IsX64>
</PropertyGroup>
<ItemGroup Condition="'$(IsWindows)' == 'true' AND '$(IsX64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.windows-x64-baseline" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup Condition="'$(IsWindows)' == 'true' AND '$(IsARM64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.windows-aarch64" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup Condition="'$(IsLinux)' == 'true' AND '$(IsX64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.linux-x64-baseline" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup Condition="'$(IsLinux)' == 'true' AND '$(IsARM64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.linux-aarch64" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup Condition="'$(IsMacOS)' == 'true' AND '$(IsX64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.darwin-x64-baseline" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<ItemGroup Condition="'$(IsMacOS)' == 'true' AND '$(IsARM64)' == 'true'">
<PackageReference Include="Scarlet.Bun.Runtime.darwin-aarch64" Version="*" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
</Project>
NB! You can add them without conditions, but the runtime packages have big size.
Advantages:
- Builds work on any platform without modification
- Each developer/CI agent only downloads the runtime package they need
- No runtime downloads during build (runtimes come from NuGet packages)
- Deterministic builds with version-locked packages
- Works offline
Trade-offs:
- Package with needed runtime version might be missing
- More verbose project file configuration
- Need to maintain platform detection logic
Which option should I choose?
- Use Option 1 (Runtime Download) if you want a multi-platform with the simplest setup and configuration
- Use Option 2 (Single Runtime Package) if you want a single platform and want embedded runtime (without downloads)
- Use Option 3 (Conditional References) if you want a multi-platform and want embedded runtimes (without downloads)
How the Runtime Is Discovered
Bun runs on the machine doing the build, not on the machine the project targets, so NuGet's usual RID
resolution is the wrong mechanism here — it resolves against $(RuntimeIdentifier). Instead, each
Scarlet.Bun.Runtime.* package contributes a BunRuntimePack item from its build/*.props, and the task
picks the one matching the build host:
<ItemGroup>
<BunRuntimePack Include="Scarlet.Bun.Runtime.darwin-aarch64">
<Rid>osx-arm64</Rid>
<RuntimesPath>...\runtimes\</RuntimesPath>
<Variant>default</Variant>
<Priority>0</Priority>
</BunRuntimePack>
</ItemGroup>
You normally never write one of these. You would if you want the build to use a Bun you supply yourself — a musl build, a non-baseline build, or a locally compiled one — without waiting for a runtime package:
<ItemGroup>
<BunRuntimePack Include="MyCompany.Bun.linux-x64-musl">
<Rid>linux-x64</Rid>
<RuntimesPath>$(MSBuildProjectDirectory)/bun/runtimes</RuntimesPath>
<Priority>100</Priority>
</BunRuntimePack>
</ItemGroup>
| Metadata | Required | Description |
|---|---|---|
Rid |
Yes | The runtime identifier this pack serves, for example osx-arm64 |
RuntimesPath |
Yes | Directory containing <rid>/native/bun (bun.exe on Windows) |
Variant |
No | Bun build variant, shown in build logs and error messages |
Priority |
No | Higher wins when several packs serve the same Rid. Defaults to 0; ties are broken by pack id so the result never depends on restore order |
Precedence: an explicit BunRuntimeDirectory wins over every pack, and BunRuntimeDownload=true bypasses
pack resolution entirely. If a pack's bun is missing, the next candidate for the same RID is tried.
Note: Runtime packages are development dependencies, so their props apply to the project that references them directly. They do not flow to projects that reference that project — add the
PackageReferencein each project that runs Bun, or in a sharedDirectory.Build.props.
Legacy contract (deprecated): runtime packages also still set a
BunRuntime_<rid>property (for exampleBunRuntime_osx_arm64), pointing at the package root. It exists so that new runtime packages keep working with olderScarlet.Bun.MSBuildversions and vice versa. Runtime packages declareBunRuntimePackfrom version 1.4.2 onwards; if a runtime is found only through the property — meaning the package is older than that — the build logs a message at normal verbosity (dotnet build -v:n) telling you which package to update. It is a message rather than a warning because pinning an older runtime package is how you pin a Bun version, and that must not fail builds usingTreatWarningsAsErrors. The property will be removed in a future major version.
Usage
Basic Example
Add the following to your .csproj file to run a Bun script during build:
<Target Name="BunInstall" BeforeTargets="Build">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="Bun"
Properties="BunCommand=install;BunWorkingDirectory=$(MSBuildProjectDirectory)" />
</Target>
<Target Name="BunBuild" AfterTargets="BunInstall">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="Bun"
Properties="BunCommand=run;BunArguments=build.mjs;BunWorkingDirectory=$(MSBuildProjectDirectory)" />
</Target>
Using Runtime Download
If you prefer to download the Bun runtime dynamically instead of using embedded runtimes, you can enable the BunRuntimeDownload option as a global property:
<PropertyGroup>
<BunRuntimeDownload>true</BunRuntimeDownload>
<BunVersionDownload>1.3.6</BunVersionDownload>
<BunRuntimeDirectory>$(MSBuildProjectDirectory)/runtimes</BunRuntimeDirectory>
</PropertyGroup>
<Target Name="BunInstall" BeforeTargets="Build">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="Bun"
Properties="BunCommand=install;BunWorkingDirectory=$(MSBuildProjectDirectory)" />
</Target>
When using BunRuntimeDownload=true:
- The
BunRuntimeDirectoryproperty is required and specifies where to download the runtime - The
BunVersionDownloadproperty is optional (defaults to latest version if not specified) - Only the runtime for the current platform will be downloaded
- The runtime is cached in the specified directory and reused on subsequent builds
Multi-Target Framework Projects
When your project targets multiple frameworks (<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>), MSBuild dispatches parallel inner builds for each TFM. Bun commands like install must run once before the inner builds start, otherwise concurrent writes to node_modules will fail on Windows.
Use BeforeTargets="DispatchToInnerBuilds;PreBuildEvent" to hook into the outer build (multi-TFM) or normal build (single-TFM), and add a condition to skip inner builds:
<Target Name="BunInstall"
BeforeTargets="DispatchToInnerBuilds;PreBuildEvent"
Condition="'$(TargetFrameworks)' == '' OR '$(TargetFramework)' == ''">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="Bun"
Properties="BunCommand=install;BunArguments=--frozen-lockfile;BunWorkingDirectory=$(MSBuildProjectDirectory)" />
</Target>
<Target Name="BunBuild"
AfterTargets="BunInstall"
BeforeTargets="DispatchToInnerBuilds;PreBuildEvent"
Condition="'$(TargetFrameworks)' == '' OR '$(TargetFramework)' == ''">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="Bun"
Properties="BunCommand=run;BunArguments=build.mjs;BunWorkingDirectory=$(MSBuildProjectDirectory)" />
</Target>
The condition works because:
- Outer build (multi-TFM):
TargetFrameworkis empty — target runs - Inner builds (per-TFM): both
TargetFrameworksandTargetFrameworkare set — target skips - Single-TFM build:
TargetFrameworksis empty — target runs
Note: This pattern also works for single-TFM projects, so you can use it as the default regardless of whether you multi-target.
Task Parameters
The BunRunTask supports the following parameters:
| Parameter | Required | Description | Default |
|---|---|---|---|
Command |
Yes | The Bun command to execute (e.g., "run", "install", "build") | - |
Arguments |
No | Arguments to pass to the Bun command | "" |
WorkingDirectory |
No | Working directory for command execution | Current directory |
RuntimeDirectory |
No | Path to the runtime directory containing Bun executables. Overrides RuntimePacks when set. Required when using BunRuntimeDownload. |
null |
RuntimePacks |
No | The Bun runtimes available to the build, normally @(BunRuntimePack). See How the Runtime Is Discovered. |
empty |
TimeoutMilliseconds |
No | Timeout in milliseconds (0 = no timeout) | 0 |
ContinueOnError |
No | Whether to continue build if command fails | false |
BunRuntimeDownload |
No | When true, downloads the Bun runtime from GitHub releases instead of using embedded runtimes | false |
BunVersionDownload |
No | Specific Bun version to download (e.g., "1.3.6"). If not specified, downloads latest version. Only used when BunRuntimeDownload=true. |
latest |
DownloadMutexTimeoutSeconds |
No | Maximum seconds to wait for the download mutex when another process is already downloading. Only used when BunRuntimeDownload=true. |
300 |
Output Parameters
| Parameter | Description |
|---|---|
ExitCode |
The exit code of the executed command |
StandardOutput |
Standard output from the command |
StandardError |
Standard error from the command |
Example: JavaScript/SCSS Build Script
Here's an example build.mjs script that bundles JavaScript and compiles SCSS:
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { minify } from "terser";
import * as sass from "sass";
const scriptFilename = fileURLToPath(import.meta.url);
const scriptDirectory = path.dirname(scriptFilename);
const jsInputDir = path.join(scriptDirectory, "scripts");
const jsOutputFile = path.join(scriptDirectory, "wwwroot/js/bundle.min.js");
const scssInput = path.join(scriptDirectory, "styles/main.scss");
const scssOutput = path.join(scriptDirectory, "wwwroot/css/site.min.css");
async function buildJS() {
console.log("Building JS bundle...");
let files = fs
.readdirSync(jsInputDir)
.filter((f) => f.endsWith(".js"))
.sort();
let code = "";
for (const file of files) {
const filePath = path.join(jsInputDir, file);
console.log("Adding", filePath);
code += fs.readFileSync(filePath, "utf-8") + "\n";
}
const minified = await minify(code);
const outDir = path.dirname(jsOutputFile);
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
fs.writeFileSync(jsOutputFile, minified.code, "utf-8");
console.log("✓ JS bundle created");
}
function buildSCSS() {
console.log("Building SCSS...");
const result = sass.compile(scssInput, {
style: "compressed",
sourceMap: false,
});
fs.mkdirSync(path.dirname(scssOutput), { recursive: true });
fs.writeFileSync(scssOutput, result.css);
console.log("✓ CSS bundle created");
}
await buildJS();
buildSCSS();
Don't forget to add dependencies in package.json:
{
"type": "module",
"dependencies": {
"terser": "^5.36.0",
"sass": "^1.83.4"
}
}
Development
Building the Package
dotnet build
Running Tests
Unit tests:
dotnet test tests/Scarlet.Bun.MSBuild.Tests/Scarlet.Bun.MSBuild.Tests.csproj
Integration tests:
dotnet test tests/Scarlet.Bun.MSBuild.IntegrationTests/Scarlet.Bun.MSBuild.IntegrationTests.csproj
All tests:
dotnet test
Creating a Package
dotnet pack src/Scarlet.Bun.MSBuild/Scarlet.Bun.MSBuild.csproj
Requirements
- .NET / .NET Core (no .NET Framework support)
- Supported on Windows, Linux, and macOS
License
This project is licensed under the MIT License - see the LICENSE file for details.
Bundled Software Licenses
This package distributes Bun binaries, which include:
- Bun: MIT License - Copyright (c) Jarred Sumner and contributors
- JavaScriptCore/WebKit: LGPL-2.1 License - Bun statically links JavaScriptCore and WebKit components
Per the LGPL-2.1 license requirements, the complete source code and build instructions for Bun (including its statically linked JavaScriptCore components) are available at:
- Bun source: https://github.com/oven-sh/bun
- Patched WebKit/JavaScriptCore: https://github.com/oven-sh/webkit
To relink Bun with modifications to JavaScriptCore:
git clone https://github.com/oven-sh/bun
cd bun
git submodule update --init --recursive
make jsc
zig build
For more information, see the Bun License Documentation.
Credits
- Built by ScarletKuro
- Uses Bun - a fast all-in-one JavaScript runtime
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Microsoft.Build.Framework (>= 18.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.4.0 | 64 | 10/19/2026 |
| 1.3.0 | 197 | 9/19/2026 |
| 1.2.0 | 121 | 9/17/2026 |
| 1.1.0 | 136 | 9/17/2026 |
| 1.0.0 | 122 | 8/11/2026 |
| 1.0.0-preview.3 | 129 | 4/12/2026 |
| 1.0.0-preview.2 | 102 | 2/15/2026 |