A companion for filtering monorepo workspaces, by package name or package dir. Because all package manager's are weird. Useful for running scripts on a subset of workspaces. The primary reason is because Bun's --filter feature is buggy, but it's also useful for other package manger, it also runs shell scripts using the
execa
package.
The library is especially useful when you need to:
- Programmatically find and filter workspace packages
- Run commands or scripts on a subset of packages
- Execute package manager commands across filtered workspaces
- Run shell commands in specific workspace directories
bun add workspaces-filter
npm install workspaces-filter
or use the CLI directly:
bunx workspaces-filter
npx workspaces-filter
pnpm dlx workspaces-filter
# or install globally
npm install -g workspaces-filter
workspaces-filter/0.8.1
Usage:
$ workspaces-filter <pattern> [...command]
Commands:
<pattern> [...command] Select by package name or workspace directory
For more info, run any command with the `--help` flag:
$ workspaces-filter --help
Options:
--print <mode> Print the names/folders of selected packages, without running command
--cwd <dir> Current working directory (default: /home/charlike/code/mid-april-2025/workspaces-filter)
--pm, --package-manager <pm> The package manager to use. Defaults to packageManager from root package.json, or Bun
-v, --version Display version number
-h, --help Display this message
Examples:
workspaces-filter . build # run in all packages of all workspaces
workspaces-filter _ build # because the "*" would not work if raw
workspaces-filter '*' build # should be quoted to avoid shell globbing
workspaces-filter "*preset*" build
workspaces-filter "*preset*" add foo-pkg barry-pkg
workspaces-filter "*preset*" add --dev typescript
workspaces-filter "./packages/foo" -- echo "Hello, World!"
workspaces-filter "./packages/*preset*" -- pwd
workspaces-filter "*preset*" --print names
workspaces-filter "*preset*" --print json
workspaces-filter "*preset*" --print dirs
[!NOTE]
To run a shell command in selected/filtered packages, use
--
right after the pattern!
[!CAUTION]
Keep in mind that if a workspace package has a script called
add
, it would run it while you may want to runbun add
,npm add
orpnpm add
. Just don't name your scripts like that or expect buggy behaviors. It's easy to assume it would have conflicts. Or use--
to run a shell command in selected package dirs.
npx workspaces-filter '*preset*' build
pnpm dlx workspaces-filter '*preset*' add foo-pkg
bunx workspaces-filter '*preset*' add --dev typescript
It checks if there is a script in package's scripts field (thus runs it with bun run
, npm run
or
pnpm run
), if not runs the package manager command (bun add
, npm add
), or a shell command if
_
or sh
is provided right after the pattern, like so
bunx workspaces-filter './packages/foo' -- echo 'Hello, World!' # runs `echo 'Hello, World!'` in the `./packages/foo` workspace
bunx workspaces-filter './packages/*preset*' -- pwd # runs `pwd` in each workspace
You can run pnpm dlx
like so
pnpx workspaces-filter '*preset*' dlx esmc
The package can also be used programmatically in your Node.js/TypeScript applications:
Generated using docks.
Filters workspace packages based on provided glob patterns and search patterns.
-
wsGlobs
{Array<string>} - Array of workspace glob patterns to search for package.json files. -
pattern
{Array<string>} - String or array of strings to filter workspaces by name or directory. -
cwd
- Optional current working directory (defaults toprocess.cwd()
).
- {Error} - When no workspace globs are provided.
- {Error} - When no pattern is provided.
- {Promise<Graph>} - Resolving to a Graph object containing filtered workspace metadata.
import { filter } from 'workspaces-filter';
// Filter workspaces matching 'pkg-*' pattern
const graph = await filter(['packages/*'], 'pkg-*');
// Filter multiple patterns
const graph = await filter(['packages/*'], ['pkg-1', 'pkg-2']);
// Filter with package dirs
const graph = await filter(['packages/*'], ['packages/foo']);
// Filter with custom working directory
const graph = await filter(['packages/*'], '*', '/path/to/project');
Executes a shell command or a package script in the context of each package in the graph.
-
args
{Array<string>} - Arguments to pass to the command. -
graph
{Graph} - Graph object containing package metadata. -
options
{RunCommandOnOptions} - Optional configuration for running the command.
- {Promise<Graph>} - Resolving to the input graph object.
import { filter, runCommandOn } from 'workspaces-filter';
const graph = await filter(['packages/*'], ['@scope/*']);
console.log(graph);
type RunCommandOnOptions = {
cwd?: string;
isShell?: boolean;
packageManager?: string;
onTestCallback?: (_err: any, _ok: any) => void | Promise<void>;
};
// Run a shell command in each package
await runCommandOn(['echo', 'Hello, World!'], graph, { isShell: true } as RunCommandOnOptions);
// Run a package script in each package
await runCommandOn(['build'], graph);
Licensed under the MIT License