mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 07:28:53 +00:00
Compare commits
7 Commits
dylan/pyth
...
claude/imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6da932dcf9 | ||
|
|
35325055fe | ||
|
|
07af79cbbd | ||
|
|
e28b8661c2 | ||
|
|
005c133fc4 | ||
|
|
b3f91460b9 | ||
|
|
3c886c7ec8 |
@@ -166,6 +166,38 @@ will execute `<script>` in both `bar` and `baz`, but not in `foo`.
|
||||
|
||||
Find more details in the docs page for [filter](https://bun.com/docs/cli/filter#running-scripts-with-filter).
|
||||
|
||||
### Workspaces
|
||||
|
||||
Similar to npm, you can use the `--workspace` argument to run scripts in specific workspace packages.
|
||||
|
||||
Use `bun run --workspace <name> <script>` to execute `<script>` in the workspace package named `<name>`.
|
||||
|
||||
```bash
|
||||
bun run --workspace foo test
|
||||
```
|
||||
|
||||
You can specify multiple workspaces by using the flag multiple times:
|
||||
|
||||
```bash
|
||||
bun run --workspace foo --workspace bar test
|
||||
```
|
||||
|
||||
Or use the short flag `-w`:
|
||||
|
||||
```bash
|
||||
bun run -w foo -w bar test
|
||||
```
|
||||
|
||||
Unlike `--filter`, the `--workspace` flag requires exact package name matches and executes scripts in the order they appear in your `package.json` workspaces configuration, not in dependency order.
|
||||
|
||||
You can also combine `--workspace` with `--filter`:
|
||||
|
||||
```bash
|
||||
bun run --workspace foo --filter 'ba*' test
|
||||
```
|
||||
|
||||
This will run the `test` script in both the `foo` workspace (exact match) and any packages matching `ba*` (pattern match).
|
||||
|
||||
## `bun run -` to pipe code from stdin
|
||||
|
||||
`bun run -` lets you read JavaScript, TypeScript, TSX, or JSX from stdin and execute it without writing to a temporary file first.
|
||||
|
||||
@@ -411,6 +411,7 @@ pub const Command = struct {
|
||||
runtime_options: RuntimeOptions = .{},
|
||||
|
||||
filters: []const []const u8 = &.{},
|
||||
workspaces: []const []const u8 = &.{},
|
||||
|
||||
preloads: []const string = &.{},
|
||||
has_loaded_global_config: bool = false,
|
||||
@@ -1083,7 +1084,7 @@ pub const Command = struct {
|
||||
const ctx = try Command.init(allocator, log, .RunCommand);
|
||||
ctx.args.target = .bun;
|
||||
|
||||
if (ctx.filters.len > 0) {
|
||||
if (ctx.filters.len > 0 or ctx.workspaces.len > 0) {
|
||||
FilterRun.runScriptsWithFilter(ctx) catch |err| {
|
||||
Output.prettyErrorln("<r><red>error<r>: {s}", .{@errorName(err)});
|
||||
Global.exit(1);
|
||||
|
||||
@@ -113,6 +113,7 @@ 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("-w, --workspace <STR>... Run a script in the specified workspace packages") 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,
|
||||
};
|
||||
@@ -380,6 +381,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.workspaces = args.options("--workspace");
|
||||
|
||||
if (args.option("--elide-lines")) |elide_lines| {
|
||||
if (elide_lines.len > 0) {
|
||||
|
||||
@@ -475,8 +475,49 @@ pub fn runScriptsWithFilter(ctx: Command.Context) !noreturn {
|
||||
|
||||
const pkgscripts = pkgjson.scripts orelse continue;
|
||||
|
||||
if (!filter_instance.matches(path, pkgjson.name))
|
||||
// Check if package matches either --filter or --workspace criteria
|
||||
const matches_filter = ctx.filters.len > 0 and filter_instance.matches(path, pkgjson.name);
|
||||
const matches_workspace = ctx.workspaces.len > 0 and blk: {
|
||||
for (ctx.workspaces) |workspace| {
|
||||
// Determine if workspace pattern is a path or name
|
||||
const is_path = workspace.len > 0 and workspace[0] == '.';
|
||||
if (is_path) {
|
||||
// For path patterns, try to match against the package directory path
|
||||
// Convert workspace pattern to absolute path for comparison
|
||||
var path_buf: bun.PathBuffer = undefined;
|
||||
const parts = [_][]const u8{workspace};
|
||||
const abs_workspace_path = bun.path.joinAbsStringBuf(fsinstance.top_level_dir, &path_buf, &parts, .loose);
|
||||
|
||||
// Normalize both paths for comparison
|
||||
const normalized_workspace = bun.strings.withoutTrailingSlash(abs_workspace_path);
|
||||
const normalized_path = bun.strings.withoutTrailingSlash(path);
|
||||
|
||||
if (bun.strings.eql(normalized_workspace, normalized_path)) {
|
||||
break :blk true;
|
||||
}
|
||||
} else {
|
||||
// For name patterns, match against package name
|
||||
if (bun.strings.eql(workspace, pkgjson.name)) {
|
||||
break :blk true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break :blk false;
|
||||
};
|
||||
|
||||
if (ctx.filters.len > 0 and ctx.workspaces.len > 0) {
|
||||
// Both --filter and --workspace specified: package must match at least one
|
||||
if (!matches_filter and !matches_workspace) continue;
|
||||
} else if (ctx.filters.len > 0) {
|
||||
// Only --filter specified
|
||||
if (!matches_filter) continue;
|
||||
} else if (ctx.workspaces.len > 0) {
|
||||
// Only --workspace specified
|
||||
if (!matches_workspace) continue;
|
||||
} else {
|
||||
// Neither specified (shouldn't happen, but handle gracefully)
|
||||
continue;
|
||||
}
|
||||
|
||||
const PATH = try RunCommand.configurePathForRunWithPackageJsonDir(ctx, dirpath, &this_transpiler, null, dirpath, ctx.debug.run_in_bun);
|
||||
|
||||
@@ -565,38 +606,44 @@ pub fn runScriptsWithFilter(ctx: Command.Context) !noreturn {
|
||||
// &state.handles[i];
|
||||
}
|
||||
}
|
||||
// compute dependencies (TODO: maybe we should do this only in a workspace?)
|
||||
for (state.handles) |*handle| {
|
||||
var iter = handle.config.deps.map.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
var sfa = std.heap.stackFallback(256, ctx.allocator);
|
||||
const alloc = sfa.get();
|
||||
const buf = try alloc.alloc(u8, entry.key_ptr.len());
|
||||
defer alloc.free(buf);
|
||||
const name = entry.key_ptr.slice(buf);
|
||||
// is it a workspace dependency?
|
||||
if (map.get(name)) |pkgs| {
|
||||
for (pkgs.items) |dep| {
|
||||
try dep.dependents.append(handle);
|
||||
handle.remaining_dependencies += 1;
|
||||
// compute dependencies
|
||||
// Skip dependency order computation for --workspace to preserve workspace definition order
|
||||
const is_workspace_mode = ctx.workspaces.len > 0 and ctx.filters.len == 0;
|
||||
if (!is_workspace_mode) {
|
||||
for (state.handles) |*handle| {
|
||||
var iter = handle.config.deps.map.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
var sfa = std.heap.stackFallback(256, ctx.allocator);
|
||||
const alloc = sfa.get();
|
||||
const buf = try alloc.alloc(u8, entry.key_ptr.len());
|
||||
defer alloc.free(buf);
|
||||
const name = entry.key_ptr.slice(buf);
|
||||
// is it a workspace dependency?
|
||||
if (map.get(name)) |pkgs| {
|
||||
for (pkgs.items) |dep| {
|
||||
try dep.dependents.append(handle);
|
||||
handle.remaining_dependencies += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if there is a dependency cycle
|
||||
// check if there is a dependency cycle (skip in workspace mode)
|
||||
var has_cycle = false;
|
||||
for (state.handles) |*handle| {
|
||||
if (hasCycle(handle)) {
|
||||
has_cycle = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if there is, we ignore dependency order completely
|
||||
if (has_cycle) {
|
||||
if (!is_workspace_mode) {
|
||||
for (state.handles) |*handle| {
|
||||
handle.dependents.clearRetainingCapacity();
|
||||
handle.remaining_dependencies = 0;
|
||||
if (hasCycle(handle)) {
|
||||
has_cycle = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if there is, we ignore dependency order completely
|
||||
if (has_cycle) {
|
||||
for (state.handles) |*handle| {
|
||||
handle.dependents.clearRetainingCapacity();
|
||||
handle.remaining_dependencies = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,6 +656,17 @@ pub fn runScriptsWithFilter(ctx: Command.Context) !noreturn {
|
||||
}
|
||||
}
|
||||
|
||||
// In workspace mode, serialize execution to preserve workspace definition order
|
||||
if (is_workspace_mode) {
|
||||
for (0..state.handles.len - 1) |i| {
|
||||
// Only create dependencies between different packages, not within the same package
|
||||
if (!bun.strings.eql(state.handles[i].config.package_name, state.handles[i + 1].config.package_name)) {
|
||||
try state.handles[i].dependents.append(&state.handles[i + 1]);
|
||||
state.handles[i + 1].remaining_dependencies += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// start inital scripts
|
||||
for (state.handles) |*handle| {
|
||||
if (handle.remaining_dependencies == 0) {
|
||||
|
||||
426
test/cli/run/workspace.test.ts
Normal file
426
test/cli/run/workspace.test.ts
Normal file
@@ -0,0 +1,426 @@
|
||||
import { spawnSync } from "bun";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
import { join } from "path";
|
||||
|
||||
const cwd_root = tempDirWithFiles("testworkspace", {
|
||||
packages: {
|
||||
pkga: {
|
||||
"index.js": "console.log('pkga');",
|
||||
"package.json": JSON.stringify({
|
||||
name: "pkga",
|
||||
scripts: {
|
||||
present: "echo scripta",
|
||||
test: "echo testa",
|
||||
},
|
||||
}),
|
||||
},
|
||||
pkgb: {
|
||||
"index.js": "console.log('pkgb');",
|
||||
"package.json": JSON.stringify({
|
||||
name: "pkgb",
|
||||
scripts: {
|
||||
present: "echo scriptb",
|
||||
test: "echo testb",
|
||||
},
|
||||
}),
|
||||
},
|
||||
pkgc: {
|
||||
"index.js": "console.log('pkgc');",
|
||||
"package.json": JSON.stringify({
|
||||
name: "pkgc",
|
||||
scripts: {
|
||||
present: "echo scriptc",
|
||||
test: "echo testc",
|
||||
},
|
||||
}),
|
||||
},
|
||||
scoped: {
|
||||
"index.js": "console.log('scoped');",
|
||||
"package.json": JSON.stringify({
|
||||
name: "@scoped/scoped",
|
||||
scripts: {
|
||||
present: "echo scriptd",
|
||||
test: "echo testd",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
"package.json": JSON.stringify({
|
||||
name: "ws",
|
||||
scripts: {
|
||||
present: "echo rootscript",
|
||||
},
|
||||
workspaces: ["packages/pkga", "packages/pkgb", "packages/pkgc", "packages/scoped"],
|
||||
}),
|
||||
});
|
||||
|
||||
const cwd_packages = join(cwd_root, "packages");
|
||||
const cwd_a = join(cwd_packages, "pkga");
|
||||
const cwd_b = join(cwd_packages, "pkgb");
|
||||
const cwd_c = join(cwd_packages, "pkgc");
|
||||
const cwd_d = join(cwd_packages, "scoped");
|
||||
|
||||
function runWithWorkspaceSuccess({
|
||||
cwd,
|
||||
workspace,
|
||||
target_pattern,
|
||||
antipattern,
|
||||
command = ["present"],
|
||||
env = {},
|
||||
}: {
|
||||
cwd: string;
|
||||
workspace: string | string[];
|
||||
target_pattern: RegExp | RegExp[];
|
||||
antipattern?: RegExp | RegExp[];
|
||||
command?: string[];
|
||||
env?: Record<string, string | undefined>;
|
||||
}) {
|
||||
const cmd = [bunExe(), "run"];
|
||||
|
||||
if (Array.isArray(workspace)) {
|
||||
for (const w of workspace) {
|
||||
cmd.push("--workspace", w);
|
||||
}
|
||||
} else {
|
||||
cmd.push("-w", workspace);
|
||||
}
|
||||
|
||||
for (const c of command) {
|
||||
cmd.push(c);
|
||||
}
|
||||
|
||||
const { exitCode, stdout, stderr } = spawnSync({
|
||||
cwd,
|
||||
cmd,
|
||||
env: { ...bunEnv, ...env },
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
const stdoutval = stdout.toString();
|
||||
for (const r of Array.isArray(target_pattern) ? target_pattern : [target_pattern]) {
|
||||
expect(stdoutval).toMatch(r);
|
||||
}
|
||||
if (antipattern !== undefined) {
|
||||
for (const r of Array.isArray(antipattern) ? antipattern : [antipattern]) {
|
||||
expect(stdoutval).not.toMatch(r);
|
||||
}
|
||||
}
|
||||
expect(stderr.toString()).toBeEmpty();
|
||||
expect(exitCode).toBe(0);
|
||||
}
|
||||
|
||||
function runWithWorkspaceFailure(cwd: string, workspace: string, scriptname: string, result: RegExp) {
|
||||
const { exitCode, stdout, stderr } = spawnSync({
|
||||
cwd: cwd,
|
||||
cmd: [bunExe(), "run", "--workspace", workspace, scriptname],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
expect(stdout.toString()).toBeEmpty();
|
||||
expect(stderr.toString()).toMatch(result);
|
||||
expect(exitCode).not.toBe(0);
|
||||
}
|
||||
|
||||
describe("bun run --workspace", () => {
|
||||
const dirs = [cwd_root, cwd_packages, cwd_a, cwd_b, cwd_c, cwd_d];
|
||||
const packages = [
|
||||
{
|
||||
name: "pkga",
|
||||
output: /scripta/,
|
||||
},
|
||||
{
|
||||
name: "pkgb",
|
||||
output: /scriptb/,
|
||||
},
|
||||
{
|
||||
name: "pkgc",
|
||||
output: /scriptc/,
|
||||
},
|
||||
{
|
||||
name: "@scoped/scoped",
|
||||
output: /scriptd/,
|
||||
},
|
||||
];
|
||||
|
||||
const names = packages.map(p => p.name);
|
||||
for (const d of dirs) {
|
||||
for (const { name, output } of packages) {
|
||||
test(`resolve ${name} from ${d}`, () => {
|
||||
runWithWorkspaceSuccess({ cwd: d, workspace: name, target_pattern: output });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const d of dirs) {
|
||||
test(`resolve all workspaces from ${d}`, () => {
|
||||
runWithWorkspaceSuccess({
|
||||
cwd: d,
|
||||
workspace: names,
|
||||
target_pattern: [/scripta/, /scriptb/, /scriptc/, /scriptd/],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test("workspace ordering follows package.json order", () => {
|
||||
// Test that workspaces are executed in the order they appear in package.json
|
||||
const { exitCode, stdout, stderr } = spawnSync({
|
||||
cwd: cwd_root,
|
||||
cmd: [bunExe(), "run", "--workspace", "pkga", "--workspace", "pkgb", "--workspace", "pkgc", "present"],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr.toString()).toBeEmpty();
|
||||
const output = stdout.toString();
|
||||
|
||||
// Check that the order matches workspace definition order
|
||||
const indexA = output.indexOf("scripta");
|
||||
const indexB = output.indexOf("scriptb");
|
||||
const indexC = output.indexOf("scriptc");
|
||||
|
||||
expect(indexA).toBeLessThan(indexB);
|
||||
expect(indexB).toBeLessThan(indexC);
|
||||
});
|
||||
|
||||
test("workspace filtering is exact match", () => {
|
||||
runWithWorkspaceSuccess({
|
||||
cwd: cwd_root,
|
||||
workspace: "pkga",
|
||||
target_pattern: /scripta/,
|
||||
antipattern: [/scriptb/, /scriptc/, /scriptd/],
|
||||
});
|
||||
});
|
||||
|
||||
test("non-existent workspace fails", () => {
|
||||
runWithWorkspaceFailure(cwd_root, "nonexistent", "present", /No packages matched the filter/);
|
||||
});
|
||||
|
||||
test("multiple workspaces", () => {
|
||||
runWithWorkspaceSuccess({
|
||||
cwd: cwd_root,
|
||||
workspace: ["pkga", "pkgc"],
|
||||
target_pattern: [/scripta/, /scriptc/],
|
||||
antipattern: [/scriptb/, /scriptd/],
|
||||
});
|
||||
});
|
||||
|
||||
test("scoped packages work", () => {
|
||||
runWithWorkspaceSuccess({
|
||||
cwd: cwd_root,
|
||||
workspace: "@scoped/scoped",
|
||||
target_pattern: /scriptd/,
|
||||
antipattern: [/scripta/, /scriptb/, /scriptc/],
|
||||
});
|
||||
});
|
||||
|
||||
test("different script names work", () => {
|
||||
runWithWorkspaceSuccess({
|
||||
cwd: cwd_root,
|
||||
workspace: "pkga",
|
||||
target_pattern: /testa/,
|
||||
command: ["test"],
|
||||
});
|
||||
});
|
||||
|
||||
test("workspace with missing script fails gracefully", () => {
|
||||
runWithWorkspaceFailure(cwd_root, "pkga", "nonexistent", /No packages matched the filter/);
|
||||
});
|
||||
|
||||
test("combine --workspace with --filter", () => {
|
||||
// Both --workspace and --filter should work together
|
||||
const { exitCode, stdout, stderr } = spawnSync({
|
||||
cwd: cwd_root,
|
||||
cmd: [bunExe(), "run", "--workspace", "pkga", "--filter", "pkgb", "present"],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr.toString()).toBeEmpty();
|
||||
const output = stdout.toString();
|
||||
|
||||
// Should run both pkga (via --workspace) and pkgb (via --filter)
|
||||
expect(output).toMatch(/scripta/);
|
||||
expect(output).toMatch(/scriptb/);
|
||||
});
|
||||
|
||||
test("short flag -w works", () => {
|
||||
runWithWorkspaceSuccess({
|
||||
cwd: cwd_root,
|
||||
workspace: "pkga",
|
||||
target_pattern: /scripta/,
|
||||
antipattern: [/scriptb/, /scriptc/, /scriptd/],
|
||||
});
|
||||
});
|
||||
|
||||
test("multiple -w flags work", () => {
|
||||
const { exitCode, stdout, stderr } = spawnSync({
|
||||
cwd: cwd_root,
|
||||
cmd: [bunExe(), "run", "-w", "pkga", "-w", "pkgb", "present"],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr.toString()).toBeEmpty();
|
||||
const output = stdout.toString();
|
||||
|
||||
expect(output).toMatch(/scripta/);
|
||||
expect(output).toMatch(/scriptb/);
|
||||
});
|
||||
|
||||
test("workspace supports file paths", () => {
|
||||
runWithWorkspaceSuccess({
|
||||
cwd: cwd_root,
|
||||
workspace: "./packages/pkga",
|
||||
target_pattern: /scripta/,
|
||||
antipattern: [/scriptb/, /scriptc/, /scriptd/],
|
||||
});
|
||||
});
|
||||
|
||||
test("workspace supports relative paths", () => {
|
||||
runWithWorkspaceSuccess({
|
||||
cwd: cwd_packages,
|
||||
workspace: "./pkgb",
|
||||
target_pattern: /scriptb/,
|
||||
antipattern: [/scripta/, /scriptc/, /scriptd/],
|
||||
});
|
||||
});
|
||||
|
||||
test("can mix file paths and package names", () => {
|
||||
runWithWorkspaceSuccess({
|
||||
cwd: cwd_root,
|
||||
workspace: ["./packages/pkga", "pkgb"],
|
||||
target_pattern: [/scripta/, /scriptb/],
|
||||
antipattern: [/scriptc/, /scriptd/],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("workspace without package names", () => {
|
||||
// Test workspace functionality with packages that don't have name field
|
||||
const cwd_no_names = tempDirWithFiles("testworkspace-no-names", {
|
||||
packages: {
|
||||
unnamed1: {
|
||||
"package.json": JSON.stringify({
|
||||
// No name field
|
||||
scripts: {
|
||||
present: "echo unnamed1-script",
|
||||
},
|
||||
}),
|
||||
},
|
||||
unnamed2: {
|
||||
"package.json": JSON.stringify({
|
||||
// No name field
|
||||
scripts: {
|
||||
present: "echo unnamed2-script",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
"package.json": JSON.stringify({
|
||||
name: "ws",
|
||||
workspaces: ["packages/unnamed1", "packages/unnamed2"],
|
||||
}),
|
||||
});
|
||||
|
||||
test("workspace works with packages without name field using paths", () => {
|
||||
const { exitCode, stdout, stderr } = spawnSync({
|
||||
cwd: cwd_no_names,
|
||||
cmd: [bunExe(), "run", "--workspace", "./packages/unnamed1", "present"],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr.toString()).toBeEmpty();
|
||||
const output = stdout.toString();
|
||||
|
||||
expect(output).toMatch(/unnamed1-script/);
|
||||
expect(output).not.toMatch(/unnamed2-script/);
|
||||
});
|
||||
|
||||
test("workspace works with multiple unnamed packages using paths", () => {
|
||||
const { exitCode, stdout, stderr } = spawnSync({
|
||||
cwd: cwd_no_names,
|
||||
cmd: [bunExe(), "run", "--workspace", "./packages/unnamed1", "--workspace", "./packages/unnamed2", "present"],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr.toString()).toBeEmpty();
|
||||
const output = stdout.toString();
|
||||
|
||||
expect(output).toMatch(/unnamed1-script/);
|
||||
expect(output).toMatch(/unnamed2-script/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("workspace ordering test", () => {
|
||||
// Test with a different workspace order to ensure it's preserved
|
||||
const cwd_reordered = tempDirWithFiles("testworkspace-reordered", {
|
||||
packages: {
|
||||
pkga: {
|
||||
"package.json": JSON.stringify({
|
||||
name: "pkga",
|
||||
scripts: {
|
||||
present: "echo scripta",
|
||||
},
|
||||
}),
|
||||
},
|
||||
pkgb: {
|
||||
"package.json": JSON.stringify({
|
||||
name: "pkgb",
|
||||
scripts: {
|
||||
present: "echo scriptb",
|
||||
},
|
||||
}),
|
||||
},
|
||||
pkgc: {
|
||||
"package.json": JSON.stringify({
|
||||
name: "pkgc",
|
||||
scripts: {
|
||||
present: "echo scriptc",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
"package.json": JSON.stringify({
|
||||
name: "ws",
|
||||
workspaces: ["packages/pkgc", "packages/pkga", "packages/pkgb"], // Different order
|
||||
}),
|
||||
});
|
||||
|
||||
test("workspace ordering follows package.json order (reordered)", () => {
|
||||
const { exitCode, stdout, stderr } = spawnSync({
|
||||
cwd: cwd_reordered,
|
||||
cmd: [bunExe(), "run", "--workspace", "pkga", "--workspace", "pkgb", "--workspace", "pkgc", "present"],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stderr.toString()).toBeEmpty();
|
||||
const output = stdout.toString();
|
||||
|
||||
// Check that the order matches workspace definition order (c, a, b)
|
||||
const indexA = output.indexOf("scripta");
|
||||
const indexB = output.indexOf("scriptb");
|
||||
const indexC = output.indexOf("scriptc");
|
||||
|
||||
// In this test, the workspace order is pkgc, pkga, pkgb
|
||||
// So we should see c before a, and a before b
|
||||
expect(indexC).toBeLessThan(indexA);
|
||||
expect(indexA).toBeLessThan(indexB);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user