Files
bun.sh/test/cli/run/workspaces.test.ts
robobun 6c3005e412 feat: add --workspaces support for bun run (#22415)
## Summary

This PR implements the `--workspaces` flag for the `bun run` command,
allowing scripts to be run in all workspace packages as defined in the
`"workspaces"` field in package.json.

Fixes the infinite loop issue reported in
https://github.com/threepointone/bun-workspace-bug-repro

## Changes

- Added `--workspaces` flag to run scripts in all workspace packages
- Added `--if-present` flag to gracefully skip packages without the
script
- Root package is excluded when using `--workspaces` to prevent infinite
recursion
- Added comprehensive tests for the new functionality

## Usage

```bash
# Run "test" script in all workspace packages
bun run --workspaces test

# Skip packages that don't have the script
bun run --workspaces --if-present build

# Combine with filters
bun run --filter="@scope/*" test
```

## Behavior

The `--workspaces` flag must come **before** the script name (matching
npm's behavior):
-  `bun run --workspaces test` 
-  `bun run test --workspaces` (treated as passthrough to script)

## Test Plan

- [x] Added test cases in `test/cli/run/workspaces.test.ts`
- [x] Verified fix for infinite loop issue in
https://github.com/threepointone/bun-workspace-bug-repro
- [x] Tested with `--if-present` flag
- [x] All tests pass locally

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-06 13:57:47 -07:00

104 lines
2.7 KiB
TypeScript

import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
test("bun run --workspaces runs script in all workspace packages", async () => {
const dir = tempDirWithFiles("workspaces-test", {
"package.json": JSON.stringify({
name: "root",
workspaces: ["packages/*"],
scripts: {
test: "echo root test",
},
}),
"packages/a/package.json": JSON.stringify({
name: "a",
scripts: {
test: "echo package a test",
},
}),
"packages/b/package.json": JSON.stringify({
name: "b",
scripts: {
test: "echo package b test",
},
}),
});
const proc = Bun.spawn({
cmd: [bunExe(), "run", "--workspaces", "test"],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(0);
expect(stdout).toContain("package a test");
expect(stdout).toContain("package b test");
// Root should not be included when using --workspaces
expect(stdout).not.toContain("root test");
});
test("bun run --workspaces --if-present succeeds when script is missing", async () => {
const dir = tempDirWithFiles("workspaces-if-present", {
"package.json": JSON.stringify({
name: "root",
workspaces: ["packages/*"],
}),
"packages/a/package.json": JSON.stringify({
name: "a",
scripts: {
test: "echo package a test",
},
}),
"packages/b/package.json": JSON.stringify({
name: "b",
// No test script
}),
});
const proc = Bun.spawn({
cmd: [bunExe(), "run", "--workspaces", "--if-present", "test"],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(0);
expect(stdout).toContain("package a test");
// Should not fail for package b
});
test("bun run --workspaces fails when no packages have the script", async () => {
const dir = tempDirWithFiles("workspaces-no-script", {
"package.json": JSON.stringify({
name: "root",
workspaces: ["packages/*"],
}),
"packages/a/package.json": JSON.stringify({
name: "a",
}),
"packages/b/package.json": JSON.stringify({
name: "b",
}),
});
const proc = Bun.spawn({
cmd: [bunExe(), "run", "--workspaces", "nonexistent"],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(1);
expect(stderr).toContain("No workspace packages have script");
});