Version 1 is here! - Package Overview
bun-workspaces was in alpha for several months, and now version 1 is here! The package is officially at 1.1.2 at the time of writing.
Along with this first stable version is the first blog post! Let's take a walk through some of the core features of bun-workspaces, both its CLI and its TypeScript API.



Background
Before diving directly into features, I'd like to give some background to the package first. You can skip forward
to get right to how bun-workspaces works.
Fundamentals
It's good to familiarize yourself with the basics of Bun's workspaces features. If you're coming from Node, Bun workspaces are extremely similar to npm workspaces.
The general idea is not very complicated: Normally you have one package in a git repo, defined by a package.json at the root. When using workspaces,
you can nest any number of package.jsons in subdirectories in your project, with configuration for this in your root package.json.
These workspaces can import and export code between each other like local modules that are named by their package.json, acting like local node_modules.
I have more about this in the documentation.
History
Inspiration
The inspiration for bun-workspaces comes from my experience working with TypeScript and monorepos in general.
I've worked in setups where I was publishing private npm packages when I wished I had a monorepo,
and I've worked in complex monorepo setups, such as managing microfrontends with Nx.
The bare-bones monorepo features of Node and Bun don't offer a lot in terms of features, but the alternatives are generally heavyweight frameworks that require a steep learning curve and complex configuration, like Nx.
For years, I've wanted to meet in the middle and have a monorepo power user layer that works in tandem with native workspaces, but I didn't get around to it while working previous jobs.
How It Started
I originally created bun-workspaces to finally start
a project that works with native workspaces, but it wasn't very serious and was mostly a workaround for a now irrelevant limitation of Bun's --filter feature
at first.
But, I noticed that several developers liked the idea. The positive reactions I've received and the usefulness I've found for myself have now propelled the project forward from a tiny, slapped-together package with a basic CLI and README to the project you see now that has a well-tested CLI and TypeScript library, a full documentation website with an in-browser demo of the CLI, this blog, and more.
Going Forward
I am just as much of a user or more than any of my current users, so I'm driven to keep adding functionality, with the hypothesis that a lot of monorepo frameworks' features can be achieved in a more lightweight package that works with, rather than against, native tooling.
I don't think well-automated monorepos are just for large enterprise projects, so I want there to be tooling that gives more accessible power to users that are solo, in smaller teams, or simply have projects that aren't worth a heavy framework.
You can see a more detailed roadmap here.
The Features
The main documentation intends to provide the true exhaustive description of bun-workspaces's features,
so I won't repeat everything here, but I would like to highlight some core features.
In general, you can say bun-workspaces has two main jobs:
- Getting metadata about a monorepo's workspace structure
- Running scripts across workspaces with advanced control, in parallel (by default) or sequentially
A CLI and API in Parity
This package started out as only a CLI. The idea of shipping a TypeScript library (the API) along with the CLI sounded like a lot of extra work at first.
However, I had already designed CLI features to have strong CLI-agnostic code behind them, a good practice for any kind of interface, so I already almost had a TypeScript library. Making it public just meant taking extra care to its design and ensuring that it's documented.
Benefits
This has become a great boon to the project, as it forces the core features to be designed strongly before thinking about the CLI, encourages another strong layer of testing to diagnose core logic issues separately from CLI issues, and allows users to choose either shell scripting or JavaScript/TypeScript scripting with first-class support to accomplish the same thing.
Comparison
We can directly look at a shell script and a TypeScript script that perform essentially the same functions below:
CLI
# A shell alias for simplicity
alias bw="bunx bun-workspaces"
# List workspaces
bw ls
# Get info about my-workspace in JSON format
bw info my-workspace --json --pretty
# Get same info using a configured alias (optional bw.workspace.json)
bw info my-alias --json --pretty
# Run the "lint" script in parallel across all workspaces
# and write a report to results.json
bw run lint --json-outfile=results.json
API
import { createFileSystemProject } from "bun-workspaces";
// Initialize Project, by default at process.cwd()
const myProject = createFileSystemProject();
// List of Workspaces
const myWorkspaces = myProject.workspaces;
// Workspace aliases are validated to be unique from workspace names,
// so this is unambiguous
const myWorkspace = myProject.findWorkspaceByNameOrAlias("my-alias");
// Run the lint script in parallel across workspaces
const { output, summary } = myProject.runScriptAcrossWorkspaces({
script: "lint",
});
for await(const { chunk, metadata } of output.text()){
// console.log(chunk); // the output chunk's content (string)
// console.log(metadata.streamName); // "stdout" or "stderr"
// console.log(metadata.workspace); // the Workspace the output is from
}
// Object with details about exit results etc.
// This is the same data as the CLI's --json-outfile
const results = await summary;
See the full CLI docs and full API docs for more.
Aliases and Configuration Files
Part of bun-workspaces's philosophy is to never require special configuration, since it
can determine a project's structure simply from metadata available from Bun.
That being said, optional config can be used, such as to define aliases for workspaces,
which can make it easier to reference a workspace with a long package.json name such as
@my-organization/my-long-package-name.
These aliases can't clash with workspace names so that they can also unambiguously represent a workspace.
An example configuration file that might be at my-project/my-workspaces/bw.workspace.json:
{
"alias": "my-alias"
}
You can read more about configuration here.
Workspace Patterns
Workspaces can be filtered or selected for certain features using workspace patterns. You can read more about these here.
CLI
# Filter the workspace list by patterns.
# This could be used to test patterns
# before using the run command.
#
# my-workspace may match a name or alias,
# while "my-pattern-*" matches by name pattern only.
#
# The "path:" specifier matches workspace directories
# relative to the project root via glob
bw ls my-workspace "my-pattern-*" "path:my-glob/**/*"
# Run for workspaces that have my-script and match the patterns.
# You can see that it's still possible to match aliases by wildcard
bw run my-script "alias:my-alias-*" "path:my-glob/**/*"
# In general, any positional arguments for commands have
# alternative flag options
bw run \
--workspace-patterns="my-workspace path:my-glob/**/*" \
--script="my-script"
API
// Similar to listing by pattern via the CLI
const myWorkspaces = myProject.findWorkspacesByPattern(
"my-workspace",
"my-pattern-*",
"path:my-glob/**/*"
);
// Same as the above CLI run command
const { output, summary } = myProject.runScriptAcrossWorkspaces({
workspacePatterns: [
"alias:my-alias-*",
"path:my-glob/**/*"
],
script: "my-script",
});
Inline Scripts via the Bun Shell
Running scripts in bun-workspaces usually means running a command from the "scripts" in workspaces' respective package.jsons.
However, it's possible to also write "inline scripts," which are simply plain shell commands.
Bun created the Bun Shell as a cross-platform bash-like shell, and this turned about to be perfect for this feature.
The following code uses the Bun Shell to run the inline command in all workspaces, so its behavior should be consistent across all major operating systems:
CLI
bw run "echo 'This is my inline script'" --inline
API
myProject.runScriptAcrossWorkspaces({
script: "echo 'This is my inline script'",
inline: true
});
You aren't limited to only the Bun Shell but can also force the system shell, which is sh for Linux/macOS/WSL and cmd for Windows.
You can read more on inline scripts here.
In Conclusion & The Future
Above doesn't cover everything that bun-workspaces can currently do.
However, I realize that still many features should be added to satiate those users that are used to the heavier monorepo frameworks.
I'm one of those users myself, so I will be working continuously towards a more and more advanced feature set, without sacrificing the no-config easy entry that only requires a valid Bun monorepo.
Now that version 1 has solidified, I'm free to build on top of this foundation that has proven itself for the features thus far with little technical debt, and I will strive to keep about the same weekly release cadence that I've kept up for many months already.
Keep an eye out for new releases as I work on the roadmap, and consider subscribing to the blog at the top right of the page, as I will write new posts for big releases!

