mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 22:01:47 +00:00
Compare commits
1 Commits
riskymh/im
...
claude/imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
477d709a0c |
235
docs/cli/run-all.md
Normal file
235
docs/cli/run-all.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# bun run --all
|
||||
|
||||
The `--all` flag for `bun run` enables sequential execution of multiple package.json scripts or source files, providing a drop-in replacement for popular tools like `npm-run-all`.
|
||||
|
||||
## Syntax
|
||||
|
||||
```bash
|
||||
bun run --all <target1> <target2> [target3] ...
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
bun --all <target1> <target2> [target3] ...
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Sequential Execution
|
||||
|
||||
The `--all` flag runs each target sequentially (one after another), not in parallel:
|
||||
|
||||
```bash
|
||||
bun run --all clean build test
|
||||
```
|
||||
|
||||
This will run:
|
||||
1. `clean` script
|
||||
2. `build` script (after clean completes)
|
||||
3. `test` script (after build completes)
|
||||
|
||||
### Pattern Matching
|
||||
|
||||
The flag supports pattern matching using `:*` and `:` suffixes to match multiple scripts:
|
||||
|
||||
#### Using `:*` suffix
|
||||
|
||||
```bash
|
||||
bun run --all "test:*"
|
||||
```
|
||||
|
||||
Matches all scripts starting with `test:`:
|
||||
- `test:unit`
|
||||
- `test:integration`
|
||||
- `test:e2e`
|
||||
|
||||
#### Using `:` suffix
|
||||
|
||||
```bash
|
||||
bun run --all "build:"
|
||||
```
|
||||
|
||||
Equivalent to `build:*`, matches all scripts starting with `build:`:
|
||||
- `build:dev`
|
||||
- `build:prod`
|
||||
- `build:staging`
|
||||
|
||||
### Mixed Targets
|
||||
|
||||
You can mix script names, patterns, and file paths:
|
||||
|
||||
```bash
|
||||
bun run --all clean "test:*" ./scripts/deploy.js
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"clean": "rm -rf dist/",
|
||||
"build": "tsc",
|
||||
"test": "jest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Run all scripts in sequence
|
||||
bun run --all clean build test
|
||||
```
|
||||
|
||||
### Pattern Matching
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test:unit": "jest --testPathPattern=unit",
|
||||
"test:integration": "jest --testPathPattern=integration",
|
||||
"test:e2e": "playwright test",
|
||||
"build:lib": "tsc -p tsconfig.lib.json",
|
||||
"build:app": "vite build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Run all test scripts
|
||||
bun run --all "test:*"
|
||||
|
||||
# Run all build scripts
|
||||
bun run --all "build:*"
|
||||
|
||||
# Mix patterns and explicit scripts
|
||||
bun run --all clean "build:*" "test:*"
|
||||
```
|
||||
|
||||
### Real-world Build Pipeline
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"clean": "rimraf dist coverage",
|
||||
"lint": "eslint src/",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:lib": "rollup -c",
|
||||
"build:docs": "typedoc",
|
||||
"test:unit": "vitest run",
|
||||
"test:integration": "playwright test"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Complete CI pipeline
|
||||
bun run --all clean lint typecheck "build:*" "test:*"
|
||||
```
|
||||
|
||||
### Running Files
|
||||
|
||||
```bash
|
||||
# Run multiple JavaScript files
|
||||
bun run --all ./scripts/setup.js ./scripts/build.js ./scripts/deploy.js
|
||||
|
||||
# Mix scripts and files
|
||||
bun run --all clean ./scripts/custom-build.js test
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Failure Behavior
|
||||
|
||||
When a target fails:
|
||||
- The command continues executing remaining targets
|
||||
- The overall command exits with a non-zero status
|
||||
- Error details are displayed for failed targets
|
||||
|
||||
```bash
|
||||
bun run --all success1 failing-script success2
|
||||
# Output: success1 runs, failing-script fails, success2 still runs
|
||||
# Exit code: non-zero
|
||||
```
|
||||
|
||||
### Missing Targets
|
||||
|
||||
If a target doesn't exist:
|
||||
- An error is reported for that target
|
||||
- Execution continues with remaining targets
|
||||
- Overall command fails
|
||||
|
||||
### Pattern Matching Edge Cases
|
||||
|
||||
If a pattern matches no scripts:
|
||||
- An error is reported
|
||||
- The command fails immediately
|
||||
|
||||
```bash
|
||||
bun run --all "nonexistent:*"
|
||||
# Error: No targets found matching the given patterns
|
||||
```
|
||||
|
||||
## npm-run-all Compatibility
|
||||
|
||||
The `--all` flag is designed as a drop-in replacement for `npm-run-all`:
|
||||
|
||||
### npm-run-all
|
||||
```bash
|
||||
npm-run-all clean build test
|
||||
npm-run-all "test:*"
|
||||
npm-run-all --serial clean build test
|
||||
```
|
||||
|
||||
### bun equivalent
|
||||
```bash
|
||||
bun run --all clean build test
|
||||
bun run --all "test:*"
|
||||
bun run --all clean build test # always serial
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Current Limitations
|
||||
|
||||
- **Sequential Only**: The current implementation runs targets sequentially. Parallel execution may be added in future versions.
|
||||
- **Pattern Expansion**: Patterns are expanded against package.json scripts. More complex glob patterns are not currently supported.
|
||||
- **Workspace Support**: Workspace-aware execution is planned for future versions.
|
||||
|
||||
### Future Enhancements
|
||||
|
||||
The implementation is designed to easily support:
|
||||
- Parallel execution with `--parallel` flag
|
||||
- Advanced glob patterns
|
||||
- Workspace package filtering
|
||||
- Output formatting options
|
||||
|
||||
## Migration from npm-run-all
|
||||
|
||||
To migrate from `npm-run-all` to `bun run --all`:
|
||||
|
||||
1. Replace `npm-run-all` with `bun run --all`
|
||||
2. Remove `--serial` flag (always sequential)
|
||||
3. Keep existing pattern syntax unchanged
|
||||
4. Update CI/build scripts accordingly
|
||||
|
||||
### Before
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "npm-run-all clean lint build:*",
|
||||
"test": "npm-run-all --serial test:unit test:integration"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### After
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build": "bun run --all clean lint build:*",
|
||||
"test": "bun run --all test:unit test:integration"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -410,6 +410,7 @@ pub const Command = struct {
|
||||
runtime_options: RuntimeOptions = .{},
|
||||
|
||||
filters: []const []const u8 = &.{},
|
||||
run_all: bool = false,
|
||||
|
||||
preloads: []const string = &.{},
|
||||
has_loaded_global_config: bool = false,
|
||||
|
||||
@@ -112,12 +112,12 @@ pub const runtime_params_ = [_]ParamType{
|
||||
|
||||
pub const auto_or_run_params = [_]ParamType{
|
||||
clap.parseParam("-F, --filter <STR>... Run a script in all workspace packages matching the pattern") catch unreachable,
|
||||
clap.parseParam("--all Run multiple scripts or files sequentially") catch unreachable,
|
||||
clap.parseParam("-b, --bun Force a script or package to use Bun's runtime instead of Node.js (via symlinking node)") catch unreachable,
|
||||
clap.parseParam("--shell <STR> Control the shell used for package.json scripts. Supports either 'bun' or 'system'") catch unreachable,
|
||||
};
|
||||
|
||||
pub const auto_only_params = [_]ParamType{
|
||||
// clap.parseParam("--all") catch unreachable,
|
||||
clap.parseParam("--silent Don't print the script command") catch unreachable,
|
||||
clap.parseParam("--elide-lines <NUMBER> Number of lines of script output shown when using --filter (default: 10). Set to 0 to show all lines.") catch unreachable,
|
||||
clap.parseParam("-v, --version Print version and exit") catch unreachable,
|
||||
@@ -379,6 +379,7 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
|
||||
|
||||
if (cmd == .RunCommand or cmd == .AutoCommand) {
|
||||
ctx.filters = args.options("--filter");
|
||||
ctx.run_all = args.flag("--all");
|
||||
|
||||
if (args.option("--elide-lines")) |elide_lines| {
|
||||
if (elide_lines.len > 0) {
|
||||
|
||||
@@ -1330,6 +1330,52 @@ pub const RunCommand = struct {
|
||||
_ = _bootAndHandleError(ctx, absolute_script_path.?, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Match a pattern against script names, supporting :* and : suffixes
|
||||
fn matchesPattern(script_name: []const u8, pattern: []const u8) bool {
|
||||
if (strings.eql(script_name, pattern)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle pattern:* (match anything starting with "pattern:")
|
||||
if (strings.endsWith(pattern, ":*")) {
|
||||
const prefix = pattern[0..pattern.len - 1]; // Remove the '*'
|
||||
return strings.startsWith(script_name, prefix);
|
||||
}
|
||||
|
||||
// Handle pattern: (same as pattern:*)
|
||||
if (strings.endsWith(pattern, ":")) {
|
||||
return strings.startsWith(script_name, pattern);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Find all scripts matching the given patterns
|
||||
fn findMatchingScripts(
|
||||
allocator: std.mem.Allocator,
|
||||
package_json: *const PackageJSON,
|
||||
patterns: []const []const u8,
|
||||
) !std.ArrayList([]const u8) {
|
||||
var matches = std.ArrayList([]const u8).init(allocator);
|
||||
|
||||
if (package_json.scripts) |scripts| {
|
||||
var script_iter = scripts.iterator();
|
||||
while (script_iter.next()) |entry| {
|
||||
const script_name = entry.key_ptr.*;
|
||||
|
||||
for (patterns) |pattern| {
|
||||
if (matchesPattern(script_name, pattern)) {
|
||||
try matches.append(script_name);
|
||||
break; // Don't add the same script multiple times
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
ctx: Command.Context,
|
||||
cfg: struct {
|
||||
@@ -1337,7 +1383,7 @@ pub const RunCommand = struct {
|
||||
log_errors: bool,
|
||||
allow_fast_run_for_extensions: bool,
|
||||
},
|
||||
) !bool {
|
||||
) anyerror!bool {
|
||||
const bin_dirs_only = cfg.bin_dirs_only;
|
||||
const log_errors = cfg.log_errors;
|
||||
|
||||
@@ -1355,6 +1401,101 @@ pub const RunCommand = struct {
|
||||
}
|
||||
const passthrough = ctx.passthrough; // unclear why passthrough is an escaped string, it should probably be []const []const u8 and allow its users to escape it.
|
||||
|
||||
// Handle --all flag: run multiple targets sequentially
|
||||
if (ctx.run_all) {
|
||||
if (target_name.len == 0 and positionals.len == 0) {
|
||||
if (log_errors) {
|
||||
Output.prettyErrorln("<r><red>error<r>: --all flag requires at least one target", .{});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Collect all targets and expand patterns
|
||||
var all_targets = std.ArrayList([]const u8).init(ctx.allocator);
|
||||
defer all_targets.deinit();
|
||||
|
||||
if (target_name.len > 0) {
|
||||
try all_targets.append(target_name);
|
||||
}
|
||||
for (positionals) |pos| {
|
||||
try all_targets.append(pos);
|
||||
}
|
||||
|
||||
// Check if any targets are patterns that need expansion
|
||||
var expanded_targets = std.ArrayList([]const u8).init(ctx.allocator);
|
||||
defer expanded_targets.deinit();
|
||||
|
||||
// Get package.json for pattern expansion
|
||||
var this_transpiler: transpiler.Transpiler = undefined;
|
||||
const root_dir_info = configureEnvForRun(ctx, &this_transpiler, null, log_errors, false) catch |err| {
|
||||
if (log_errors) {
|
||||
Output.prettyErrorln("<r><red>error<r>: Failed to configure environment: {s}", .{@errorName(err)});
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (all_targets.items) |target| {
|
||||
// Check if this is a pattern (ends with :* or :)
|
||||
if (strings.endsWith(target, ":*") or strings.endsWith(target, ":")) {
|
||||
// Expand pattern against package.json scripts
|
||||
if (root_dir_info.enclosing_package_json) |package_json| {
|
||||
var matches = findMatchingScripts(ctx.allocator, package_json, &[_][]const u8{target}) catch |err| {
|
||||
if (log_errors) {
|
||||
Output.prettyErrorln("<r><red>error<r>: Failed to expand pattern '{s}': {s}", .{ target, @errorName(err) });
|
||||
}
|
||||
continue;
|
||||
};
|
||||
defer matches.deinit();
|
||||
|
||||
for (matches.items) |match| {
|
||||
try expanded_targets.append(match);
|
||||
}
|
||||
} else {
|
||||
// No package.json found, treat as literal target
|
||||
try expanded_targets.append(target);
|
||||
}
|
||||
} else {
|
||||
// Regular target, add as-is
|
||||
try expanded_targets.append(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (expanded_targets.items.len == 0) {
|
||||
if (log_errors) {
|
||||
Output.prettyErrorln("<r><red>error<r>: No targets found matching the given patterns", .{});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute each target sequentially
|
||||
var failed = false;
|
||||
for (expanded_targets.items) |target| {
|
||||
// Create new context with single target
|
||||
var new_ctx = ctx.*;
|
||||
var temp_positionals = [_][]const u8{target};
|
||||
new_ctx.positionals = &temp_positionals;
|
||||
new_ctx.run_all = false; // Disable --all for recursive calls
|
||||
|
||||
// Call exec recursively for this single target
|
||||
const success = exec(&new_ctx, cfg) catch |err| {
|
||||
if (log_errors) {
|
||||
Output.prettyErrorln("<r><red>error<r>: Failed to run target '{s}': {s}", .{ target, @errorName(err) });
|
||||
}
|
||||
failed = true;
|
||||
continue;
|
||||
};
|
||||
|
||||
if (!success) {
|
||||
if (log_errors) {
|
||||
Output.prettyErrorln("<r><red>error<r>: Target '{s}' failed", .{target});
|
||||
}
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return !failed;
|
||||
}
|
||||
|
||||
var try_fast_run = false;
|
||||
var skip_script_check = false;
|
||||
if (target_name.len > 0 and target_name[0] == '.') {
|
||||
|
||||
337
test/cli/run-all-edge-cases.test.ts
Normal file
337
test/cli/run-all-edge-cases.test.ts
Normal file
@@ -0,0 +1,337 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { bunExe, bunEnv, tempDirWithFiles } from "harness";
|
||||
|
||||
describe("bun run --all edge cases", () => {
|
||||
test("should handle empty scripts in package.json", async () => {
|
||||
const dir = tempDirWithFiles("run-all-empty-scripts", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {},
|
||||
}),
|
||||
"app.js": 'console.log("Running app");',
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "app.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Running app");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle scripts with special characters", async () => {
|
||||
const dir = tempDirWithFiles("run-all-special-chars", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"test:with-dashes": 'echo "Test with dashes"',
|
||||
"test_with_underscores": 'echo "Test with underscores"',
|
||||
"test.with.dots": 'echo "Test with dots"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "test:with-dashes", "test_with_underscores", "test.with.dots"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Test with dashes");
|
||||
expect(stdout).toContain("Test with underscores");
|
||||
expect(stdout).toContain("Test with dots");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle very long script output", async () => {
|
||||
const dir = tempDirWithFiles("run-all-long-output", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"long": 'for i in {1..100}; do echo "Line $i"; done',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "long"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Line 1");
|
||||
expect(stdout).toContain("Line 100");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle scripts that take time", async () => {
|
||||
const dir = tempDirWithFiles("run-all-timing", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"first": 'echo "Starting"; sleep 0.1; echo "First done"',
|
||||
"second": 'echo "Second done"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "first", "second"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
const endTime = Date.now();
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Starting");
|
||||
expect(stdout).toContain("First done");
|
||||
expect(stdout).toContain("Second done");
|
||||
expect(endTime - startTime).toBeGreaterThan(90); // Should take at least 100ms
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle absolute paths", async () => {
|
||||
const dir = tempDirWithFiles("run-all-absolute", {
|
||||
"script.js": 'console.log("Absolute path script");',
|
||||
});
|
||||
|
||||
const absolutePath = `${dir}/script.js`;
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", absolutePath],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Absolute path script");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle relative paths with ./", async () => {
|
||||
const dir = tempDirWithFiles("run-all-relative", {
|
||||
"script.js": 'console.log("Relative path script");',
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "./script.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Relative path script");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle non-existent scripts gracefully", async () => {
|
||||
const dir = tempDirWithFiles("run-all-nonexistent", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"existing": 'echo "This exists"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "existing", "nonexistent"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).not.toBe(0);
|
||||
expect(stdout).toContain("This exists"); // Should run the existing script
|
||||
expect(stderr).toContain("nonexistent"); // Should report the error
|
||||
});
|
||||
|
||||
test("should handle non-existent files gracefully", async () => {
|
||||
const dir = tempDirWithFiles("run-all-nonexistent-file", {
|
||||
"existing.js": 'console.log("This file exists");',
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "existing.js", "nonexistent.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).not.toBe(0);
|
||||
expect(stdout).toContain("This file exists"); // Should run the existing file
|
||||
expect(stderr).toContain("nonexistent.js"); // Should report the error
|
||||
});
|
||||
|
||||
test("should handle patterns with no package.json", async () => {
|
||||
const dir = tempDirWithFiles("run-all-no-package-pattern", {
|
||||
"app.js": 'console.log("App running");',
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "test:*", "app.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
// Should handle pattern as literal when no package.json
|
||||
// and continue with the file execution
|
||||
expect(stdout).toContain("App running");
|
||||
// May have warnings about test:* but should continue
|
||||
});
|
||||
|
||||
test("should handle scripts with environment variables", async () => {
|
||||
const dir = tempDirWithFiles("run-all-env", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"env-test": 'echo "NODE_ENV is $NODE_ENV"',
|
||||
"custom-env": 'echo "CUSTOM_VAR is $CUSTOM_VAR"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const customEnv = {
|
||||
...bunEnv,
|
||||
NODE_ENV: "test",
|
||||
CUSTOM_VAR: "hello-world",
|
||||
};
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "env-test", "custom-env"],
|
||||
env: customEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("NODE_ENV is test");
|
||||
expect(stdout).toContain("CUSTOM_VAR is hello-world");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle scripts with complex commands", async () => {
|
||||
const dir = tempDirWithFiles("run-all-complex-cmd", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"complex": 'echo "Start" && echo "Middle" && echo "End"',
|
||||
"with-pipe": 'echo "Hello World" | grep "World"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "complex", "with-pipe"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Start");
|
||||
expect(stdout).toContain("Middle");
|
||||
expect(stdout).toContain("End");
|
||||
expect(stdout).toContain("World");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle duplicate targets", async () => {
|
||||
const dir = tempDirWithFiles("run-all-duplicates", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"test": 'echo "Running test"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "test", "test", "test"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
// Should run the script multiple times
|
||||
const testOutputs = (stdout.match(/Running test/g) || []).length;
|
||||
expect(testOutputs).toBe(3);
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
});
|
||||
341
test/cli/run-all.test.ts
Normal file
341
test/cli/run-all.test.ts
Normal file
@@ -0,0 +1,341 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { bunExe, bunEnv, tempDirWithFiles } from "harness";
|
||||
|
||||
describe("bun run --all", () => {
|
||||
test("should run multiple scripts sequentially", async () => {
|
||||
const dir = tempDirWithFiles("run-all-basic", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"test:unit": 'echo "Running unit tests"',
|
||||
"test:integration": 'echo "Running integration tests"',
|
||||
"build": 'echo "Building project"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "test:unit", "test:integration"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Running unit tests");
|
||||
expect(stdout).toContain("Running integration tests");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should support pattern matching with :*", async () => {
|
||||
const dir = tempDirWithFiles("run-all-pattern", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"test:unit": 'echo "Unit tests"',
|
||||
"test:integration": 'echo "Integration tests"',
|
||||
"test:e2e": 'echo "E2E tests"',
|
||||
"build": 'echo "Building"',
|
||||
"lint": 'echo "Linting"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "test:*"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Unit tests");
|
||||
expect(stdout).toContain("Integration tests");
|
||||
expect(stdout).toContain("E2E tests");
|
||||
expect(stdout).not.toContain("Building");
|
||||
expect(stdout).not.toContain("Linting");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should support pattern matching with : suffix", async () => {
|
||||
const dir = tempDirWithFiles("run-all-colon", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"build:dev": 'echo "Dev build"',
|
||||
"build:prod": 'echo "Prod build"',
|
||||
"build:staging": 'echo "Staging build"',
|
||||
"test": 'echo "Testing"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "build:"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Dev build");
|
||||
expect(stdout).toContain("Prod build");
|
||||
expect(stdout).toContain("Staging build");
|
||||
expect(stdout).not.toContain("Testing");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should run source files when specified", async () => {
|
||||
const dir = tempDirWithFiles("run-all-files", {
|
||||
"script1.js": 'console.log("Script 1 executed");',
|
||||
"script2.js": 'console.log("Script 2 executed");',
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "script1.js", "script2.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Script 1 executed");
|
||||
expect(stdout).toContain("Script 2 executed");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle mix of scripts and files", async () => {
|
||||
const dir = tempDirWithFiles("run-all-mixed", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"build": 'echo "Building"',
|
||||
},
|
||||
}),
|
||||
"test.js": 'console.log("Test file executed");',
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "build", "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Building");
|
||||
expect(stdout).toContain("Test file executed");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should error when no targets provided", async () => {
|
||||
const dir = tempDirWithFiles("run-all-no-targets", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"test": 'echo "Testing"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).not.toBe(0);
|
||||
expect(stderr).toContain("--all flag requires at least one target");
|
||||
});
|
||||
|
||||
test("should continue execution when one target fails", async () => {
|
||||
const dir = tempDirWithFiles("run-all-failure", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"success1": 'echo "Success 1"',
|
||||
"failure": "exit 1",
|
||||
"success2": 'echo "Success 2"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "success1", "failure", "success2"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).not.toBe(0); // Should fail overall
|
||||
expect(stdout).toContain("Success 1");
|
||||
expect(stdout).toContain("Success 2");
|
||||
expect(stderr).toContain("failure"); // Should report the failed target
|
||||
});
|
||||
|
||||
test("should handle pattern that matches no scripts", async () => {
|
||||
const dir = tempDirWithFiles("run-all-no-match", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"build": 'echo "Building"',
|
||||
"test": 'echo "Testing"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "deploy:*"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).not.toBe(0);
|
||||
expect(stderr).toContain("No targets found matching the given patterns");
|
||||
});
|
||||
|
||||
test("should work without package.json when running files", async () => {
|
||||
const dir = tempDirWithFiles("run-all-no-package", {
|
||||
"app.js": 'console.log("App running");',
|
||||
"server.js": 'console.log("Server running");',
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "app.js", "server.js"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("App running");
|
||||
expect(stdout).toContain("Server running");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should execute scripts in order", async () => {
|
||||
const dir = tempDirWithFiles("run-all-order", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"first": 'echo "First script"',
|
||||
"second": 'echo "Second script"',
|
||||
"third": 'echo "Third script"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "first", "second", "third"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
const lines = stdout.trim().split('\n');
|
||||
expect(lines).toContain("First script");
|
||||
expect(lines).toContain("Second script");
|
||||
expect(lines).toContain("Third script");
|
||||
|
||||
// Check order (allowing for some output variation)
|
||||
const firstIndex = lines.findIndex(line => line.includes("First script"));
|
||||
const secondIndex = lines.findIndex(line => line.includes("Second script"));
|
||||
const thirdIndex = lines.findIndex(line => line.includes("Third script"));
|
||||
|
||||
expect(firstIndex).toBeLessThan(secondIndex);
|
||||
expect(secondIndex).toBeLessThan(thirdIndex);
|
||||
});
|
||||
|
||||
test("should handle complex patterns with multiple matches", async () => {
|
||||
const dir = tempDirWithFiles("run-all-complex", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"test:unit:fast": 'echo "Fast unit tests"',
|
||||
"test:unit:slow": 'echo "Slow unit tests"',
|
||||
"test:integration:api": 'echo "API integration tests"',
|
||||
"test:integration:ui": 'echo "UI integration tests"',
|
||||
"build:dev": 'echo "Dev build"',
|
||||
"build:prod": 'echo "Prod build"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "test:unit:", "test:integration:"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Fast unit tests");
|
||||
expect(stdout).toContain("Slow unit tests");
|
||||
expect(stdout).toContain("API integration tests");
|
||||
expect(stdout).toContain("UI integration tests");
|
||||
expect(stdout).not.toContain("Dev build");
|
||||
expect(stdout).not.toContain("Prod build");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
});
|
||||
257
test/regression/issue/run-all-flag.test.ts
Normal file
257
test/regression/issue/run-all-flag.test.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { bunExe, bunEnv, tempDirWithFiles } from "harness";
|
||||
|
||||
describe("bun run --all regression tests", () => {
|
||||
test("should be a drop-in replacement for npm-run-all basic usage", async () => {
|
||||
// This test ensures --all flag works like npm-run-all
|
||||
const dir = tempDirWithFiles("npm-run-all-compat", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"clean": 'echo "Cleaning..."',
|
||||
"build": 'echo "Building..."',
|
||||
"test": 'echo "Testing..."',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Test equivalent to: npm-run-all clean build test
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "clean", "build", "test"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Cleaning...");
|
||||
expect(stdout).toContain("Building...");
|
||||
expect(stdout).toContain("Testing...");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should support npm-run-all pattern syntax", async () => {
|
||||
// Test equivalent to: npm-run-all "test:*"
|
||||
const dir = tempDirWithFiles("npm-run-all-pattern", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"test:unit": 'echo "Unit tests"',
|
||||
"test:integration": 'echo "Integration tests"',
|
||||
"test:e2e": 'echo "E2E tests"',
|
||||
"build": 'echo "Building"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "test:*"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Unit tests");
|
||||
expect(stdout).toContain("Integration tests");
|
||||
expect(stdout).toContain("E2E tests");
|
||||
expect(stdout).not.toContain("Building");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should work with typical build pipeline", async () => {
|
||||
// Real-world usage example
|
||||
const dir = tempDirWithFiles("build-pipeline", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"clean": 'echo "🧹 Cleaning dist/"',
|
||||
"lint": 'echo "🔍 Linting code"',
|
||||
"typecheck": 'echo "🔍 Type checking"',
|
||||
"build:lib": 'echo "📦 Building library"',
|
||||
"build:docs": 'echo "📚 Building docs"',
|
||||
"test:unit": 'echo "🧪 Running unit tests"',
|
||||
"test:integration": 'echo "🔧 Running integration tests"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "clean", "lint", "typecheck", "build:*", "test:*"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("🧹 Cleaning dist/");
|
||||
expect(stdout).toContain("🔍 Linting code");
|
||||
expect(stdout).toContain("🔍 Type checking");
|
||||
expect(stdout).toContain("📦 Building library");
|
||||
expect(stdout).toContain("📚 Building docs");
|
||||
expect(stdout).toContain("🧪 Running unit tests");
|
||||
expect(stdout).toContain("🔧 Running integration tests");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle failure gracefully like npm-run-all", async () => {
|
||||
const dir = tempDirWithFiles("failure-handling", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"step1": 'echo "Step 1 success"',
|
||||
"step2": 'echo "Step 2 fail" && exit 1',
|
||||
"step3": 'echo "Step 3 success"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "step1", "step2", "step3"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).not.toBe(0); // Should fail overall
|
||||
expect(stdout).toContain("Step 1 success");
|
||||
expect(stdout).toContain("Step 3 success"); // Should continue after failure
|
||||
expect(stderr).toContain("step2"); // Should report which step failed
|
||||
});
|
||||
|
||||
test("should preserve script execution order", async () => {
|
||||
const dir = tempDirWithFiles("execution-order", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"a": 'echo "A" && sleep 0.05',
|
||||
"b": 'echo "B" && sleep 0.05',
|
||||
"c": 'echo "C" && sleep 0.05',
|
||||
"d": 'echo "D" && sleep 0.05',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "a", "b", "c", "d"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Extract the letters in order they appear in output
|
||||
const lines = stdout.split('\n').filter(line => line.trim().match(/^[ABCD]$/));
|
||||
expect(lines).toEqual(['A', 'B', 'C', 'D']);
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should work without explicit run command", async () => {
|
||||
// Test equivalent to: bun --all script1 script2
|
||||
const dir = tempDirWithFiles("implicit-run", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
scripts: {
|
||||
"start": 'echo "Starting app"',
|
||||
"dev": 'echo "Development mode"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "--all", "start", "dev"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout).toContain("Starting app");
|
||||
expect(stdout).toContain("Development mode");
|
||||
expect(stderr).toBe("");
|
||||
});
|
||||
|
||||
test("should handle workspace scenarios", async () => {
|
||||
// Simulate monorepo workspace scenario
|
||||
const dir = tempDirWithFiles("workspace-scenario", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "monorepo-root",
|
||||
scripts: {
|
||||
"build:ui": 'echo "Building UI package"',
|
||||
"build:api": 'echo "Building API package"',
|
||||
"build:shared": 'echo "Building shared package"',
|
||||
"test:ui": 'echo "Testing UI package"',
|
||||
"test:api": 'echo "Testing API package"',
|
||||
"lint:ui": 'echo "Linting UI package"',
|
||||
"lint:api": 'echo "Linting API package"',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Build all packages
|
||||
await using buildProc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "build:*"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [buildStdout, buildStderr, buildExitCode] = await Promise.all([
|
||||
new Response(buildProc.stdout).text(),
|
||||
new Response(buildProc.stderr).text(),
|
||||
buildProc.exited,
|
||||
]);
|
||||
|
||||
expect(buildExitCode).toBe(0);
|
||||
expect(buildStdout).toContain("Building UI package");
|
||||
expect(buildStdout).toContain("Building API package");
|
||||
expect(buildStdout).toContain("Building shared package");
|
||||
|
||||
// Test all packages
|
||||
await using testProc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "--all", "test:*"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
});
|
||||
|
||||
const [testStdout, testStderr, testExitCode] = await Promise.all([
|
||||
new Response(testProc.stdout).text(),
|
||||
new Response(testProc.stderr).text(),
|
||||
testProc.exited,
|
||||
]);
|
||||
|
||||
expect(testExitCode).toBe(0);
|
||||
expect(testStdout).toContain("Testing UI package");
|
||||
expect(testStdout).toContain("Testing API package");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user