Compare commits

...

3 Commits

Author SHA1 Message Date
autofix-ci[bot]
f2f5ce1d2a [autofix.ci] apply automated fixes 2025-09-29 11:33:17 +00:00
Claude Bot
46288ee202 feat: implement --json flag for JSON output format
- Add --json boolean flag that modifies output format to JSON
- Works with --print: outputs expression result as JSON
- Works with --eval: acts like --print but outputs JSON
- Supports capturing module exports for main modules (CommonJS/ESM)
- Uses jsonStringify for proper JSON serialization
- Handles all JavaScript types including promises, dates, objects, arrays
- Shows error messages for non-serializable types (BigInt, circular refs)

Test results:
  bun --print '42' --json              # outputs: 42
  bun --print '({x: 1})' --json        # outputs: {"x":1}
  bun --eval '({x: 1})' --json         # outputs: {"x":1}
  bun --eval 'null' --json             # outputs: null
  bun --eval 'undefined' --json        # outputs: (nothing)

Includes comprehensive tests covering all data types and edge cases.
2025-09-29 11:31:42 +00:00
Claude Bot
f920e401c1 feat: add --json flag for JSON output format
- Add --json boolean flag that works with --print and --eval
- When used with --print: outputs result as JSON instead of console format
- When used with --eval: prints result as JSON (like --print but JSON formatted)
- Uses existing jsonStringify JSValue binding for proper JSON serialization
- Handles all JS types including promises, dates, objects, arrays, etc.

Examples:
  bun --print '1 + 1' --json       # outputs: 2
  bun --print '({x: 1})' --json    # outputs: {"x":1}
  bun --eval '({x: 1})' --json     # outputs: {"x":1}
2025-09-29 11:12:22 +00:00
8 changed files with 444 additions and 12 deletions

View File

@@ -155,7 +155,7 @@ pub const Run = struct {
return;
}
bun.jsc.initialize(ctx.runtime_options.eval.eval_and_print);
bun.jsc.initialize(ctx.runtime_options.eval.print or ctx.runtime_options.json);
js_ast.Expr.Data.Store.create();
js_ast.Stmt.Data.Store.create();
@@ -169,7 +169,7 @@ pub const Run = struct {
.args = ctx.args,
.store_fd = ctx.debug.hot_reload != .none,
.smol = ctx.runtime_options.smol,
.eval = ctx.runtime_options.eval.eval_and_print,
.eval = ctx.runtime_options.eval.print or ctx.runtime_options.json,
.debugger = ctx.runtime_options.debugger,
.dns_result_order = DNSResolver.Order.fromStringOrDie(ctx.runtime_options.dns_result_order),
.is_main_thread = true,
@@ -192,7 +192,7 @@ pub const Run = struct {
script_source.* = logger.Source.initPathString(entry_path, ctx.runtime_options.eval.script);
vm.module_loader.eval_source = script_source;
if (ctx.runtime_options.eval.eval_and_print) {
if (ctx.runtime_options.eval.print or ctx.runtime_options.json) {
b.options.dead_code_elimination = false;
}
}
@@ -263,6 +263,9 @@ pub const Run = struct {
vm.hot_reload = this.ctx.debug.hot_reload;
vm.onUnhandledRejection = &onUnhandledRejectionBeforeClose;
// Enable capturing entry point result if --json is set (and not using --eval with script)
vm.capture_entry_point_result = this.ctx.runtime_options.json and this.ctx.runtime_options.eval.script.len == 0;
this.addConditionalGlobals();
do_redis_preconnect: {
// This must happen within the API lock, which is why it's not in the "doPreconnect" function
@@ -412,7 +415,10 @@ pub const Run = struct {
vm.eventLoop().autoTickActive();
}
if (this.ctx.runtime_options.eval.eval_and_print) {
// Handle --print or --json flags (both for eval and regular entry points)
const should_print = this.ctx.runtime_options.eval.print or this.ctx.runtime_options.json;
if (should_print) {
const to_print = brk: {
const result: jsc.JSValue = vm.entry_point_result.value.get() orelse .js_undefined;
if (result.asAnyPromise()) |promise| {
@@ -437,7 +443,22 @@ pub const Run = struct {
break :brk result;
};
to_print.print(vm.global, .Log, .Log);
if (this.ctx.runtime_options.json) {
var str = bun.String.empty;
defer str.deref();
to_print.jsonStringify(vm.global, 0, &str) catch |err| {
vm.global.reportActiveExceptionAsUnhandled(err);
return;
};
// Print the JSON string to stdout
const utf8 = str.toUTF8(bun.default_allocator);
defer utf8.deinit();
_ = Output.writer().write(utf8.slice()) catch {};
_ = Output.writer().write("\n") catch {};
Output.flush();
} else {
to_print.print(vm.global, .Log, .Log);
}
}
vm.onBeforeExit();

View File

@@ -18,6 +18,8 @@ comptime {
@export(&setEntryPointEvalResultESM, .{ .name = "Bun__VM__setEntryPointEvalResultESM" });
@export(&setEntryPointEvalResultCJS, .{ .name = "Bun__VM__setEntryPointEvalResultCJS" });
@export(&specifierIsEvalEntryPoint, .{ .name = "Bun__VM__specifierIsEvalEntryPoint" });
@export(&shouldCaptureEntryPointResult, .{ .name = "Bun__VM__shouldCaptureEntryPointResult" });
@export(&isMainModule, .{ .name = "Bun__VM__isMainModule" });
@export(&string_allocation_limit, .{ .name = "Bun__stringSyntheticAllocationLimit" });
@export(&allowAddons, .{ .name = "Bun__VM__allowAddons" });
@export(&allowRejectionHandledWarning, .{ .name = "Bun__VM__allowRejectionHandledWarning" });
@@ -136,6 +138,8 @@ entry_point_result: struct {
value: jsc.Strong.Optional = .empty,
cjs_set_value: bool = false,
} = .{},
capture_entry_point_result: bool = false,
main_module_key: jsc.Strong.Optional = .empty,
auto_install_dependencies: bool = false,
@@ -799,6 +803,26 @@ pub fn specifierIsEvalEntryPoint(this: *VirtualMachine, specifier: JSValue) call
return false;
}
pub fn shouldCaptureEntryPointResult(this: *VirtualMachine) callconv(.C) bool {
return this.capture_entry_point_result;
}
pub fn isMainModule(this: *VirtualMachine, specifier: JSValue) callconv(.C) bool {
// If we're capturing for --json and no main module has been set yet
if (this.capture_entry_point_result and !this.main_module_key.has()) {
this.main_module_key.set(this.global, specifier);
return true;
}
// Check if this matches the stored main module
if (this.main_module_key.has()) {
const main_key = this.main_module_key.get() orelse return false;
return specifier.isSameValue(main_key, this.global) catch return false;
}
return false;
}
pub fn setEntryPointEvalResultESM(this: *VirtualMachine, result: JSValue) callconv(.C) void {
// allow esm evaluate to set value multiple times
if (!this.entry_point_result.cjs_set_value) {

View File

@@ -108,6 +108,7 @@ static bool canPerformFastEnumeration(Structure* s)
extern "C" bool Bun__VM__specifierIsEvalEntryPoint(void*, EncodedJSValue);
extern "C" void Bun__VM__setEntryPointEvalResultCJS(void*, EncodedJSValue);
extern "C" bool Bun__VM__isMainModule(void*, EncodedJSValue);
static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObject, JSCommonJSModule* moduleObject, JSString* dirname, JSValue filename)
{
@@ -145,7 +146,9 @@ static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObj
moduleObject->hasEvaluated = true;
};
if (Bun__VM__specifierIsEvalEntryPoint(globalObject->bunVM(), JSValue::encode(filename))) [[unlikely]] {
bool isEvalOrMainModule = Bun__VM__specifierIsEvalEntryPoint(globalObject->bunVM(), JSValue::encode(filename)) || Bun__VM__isMainModule(globalObject->bunVM(), JSValue::encode(filename));
if (isEvalOrMainModule) [[unlikely]] {
initializeModuleObject();
scope.assertNoExceptionExceptTermination();
@@ -163,7 +166,14 @@ static bool evaluateCommonJSModuleOnce(JSC::VM& vm, Zig::GlobalObject* globalObj
RETURN_IF_EXCEPTION(scope, false);
ASSERT(result);
Bun__VM__setEntryPointEvalResultCJS(globalObject->bunVM(), JSValue::encode(result));
if (Bun__VM__specifierIsEvalEntryPoint(globalObject->bunVM(), JSValue::encode(filename))) {
Bun__VM__setEntryPointEvalResultCJS(globalObject->bunVM(), JSValue::encode(result));
} else if (Bun__VM__isMainModule(globalObject->bunVM(), JSValue::encode(filename))) {
// For --json main modules, capture the module.exports
auto exports_final = moduleObject->exportsObject();
RETURN_IF_EXCEPTION(scope, false);
Bun__VM__setEntryPointEvalResultCJS(globalObject->bunVM(), JSValue::encode(exports_final));
}
RELEASE_AND_RETURN(scope, true);
}

View File

@@ -4440,6 +4440,7 @@ JSC::JSValue GlobalObject::moduleLoaderEvaluate(JSGlobalObject* lexicalGlobalObj
extern "C" bool Bun__VM__specifierIsEvalEntryPoint(void*, EncodedJSValue);
extern "C" void Bun__VM__setEntryPointEvalResultESM(void*, EncodedJSValue);
extern "C" bool Bun__VM__isMainModule(void*, EncodedJSValue);
JSC::JSValue EvalGlobalObject::moduleLoaderEvaluate(JSGlobalObject* lexicalGlobalObject,
JSModuleLoader* moduleLoader, JSValue key,
@@ -4449,7 +4450,7 @@ JSC::JSValue EvalGlobalObject::moduleLoaderEvaluate(JSGlobalObject* lexicalGloba
Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
if (scriptFetcher && scriptFetcher.isObject()) [[unlikely]] {
if (Bun__VM__specifierIsEvalEntryPoint(globalObject->bunVM(), JSValue::encode(key))) {
if (Bun__VM__specifierIsEvalEntryPoint(globalObject->bunVM(), JSValue::encode(key)) || Bun__VM__isMainModule(globalObject->bunVM(), JSValue::encode(key))) {
Bun__VM__setEntryPointEvalResultESM(globalObject->bunVM(), JSValue::encode(scriptFetcher));
}
return scriptFetcher;
@@ -4458,7 +4459,7 @@ JSC::JSValue EvalGlobalObject::moduleLoaderEvaluate(JSGlobalObject* lexicalGloba
JSC::JSValue result = moduleLoader->evaluateNonVirtual(lexicalGlobalObject, key, moduleRecordValue,
scriptFetcher, sentValue, resumeMode);
if (Bun__VM__specifierIsEvalEntryPoint(globalObject->bunVM(), JSValue::encode(key))) {
if (Bun__VM__specifierIsEvalEntryPoint(globalObject->bunVM(), JSValue::encode(key)) || Bun__VM__isMainModule(globalObject->bunVM(), JSValue::encode(key))) {
Bun__VM__setEntryPointEvalResultESM(globalObject->bunVM(), JSValue::encode(result));
}

View File

@@ -371,8 +371,9 @@ pub const Command = struct {
sql_preconnect: bool = false,
eval: struct {
script: []const u8 = "",
eval_and_print: bool = false,
print: bool = false,
} = .{},
json: bool = false,
preconnect: []const []const u8 = &[_][]const u8{},
dns_result_order: []const u8 = "verbatim",
/// `--expose-gc` makes `globalThis.gc()` available. Added for Node

View File

@@ -91,6 +91,7 @@ pub const runtime_params_ = [_]ParamType{
clap.parseParam("-i Auto-install dependencies during execution. Equivalent to --install=fallback.") catch unreachable,
clap.parseParam("-e, --eval <STR> Evaluate argument as a script") catch unreachable,
clap.parseParam("-p, --print <STR> Evaluate argument as a script and print the result") catch unreachable,
clap.parseParam("--json Output result as JSON instead of using console formatter") catch unreachable,
clap.parseParam("--prefer-offline Skip staleness checks for packages in the Bun runtime and resolve from disk") catch unreachable,
clap.parseParam("--prefer-latest Use the latest matching versions of packages in the Bun runtime, always checking npm") catch unreachable,
clap.parseParam("--port <STR> Set the default port for Bun.serve") catch unreachable,
@@ -645,7 +646,7 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
if (comptime cmd == .RunAsNodeCommand) {
// TODO: prevent `node --port <script>` from working
ctx.runtime_options.eval.script = port_str;
ctx.runtime_options.eval.eval_and_print = true;
ctx.runtime_options.eval.print = true;
} else {
opts.port = std.fmt.parseInt(u16, port_str, 10) catch {
Output.errFmt(
@@ -703,10 +704,11 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
if (args.option("--print")) |script| {
ctx.runtime_options.eval.script = script;
ctx.runtime_options.eval.eval_and_print = true;
ctx.runtime_options.eval.print = true;
} else if (args.option("--eval")) |script| {
ctx.runtime_options.eval.script = script;
}
ctx.runtime_options.json = args.flag("--json");
ctx.runtime_options.if_present = args.flag("--if-present");
ctx.runtime_options.smol = args.flag("--smol");
ctx.runtime_options.preconnect = args.options("--fetch-preconnect");

View File

@@ -0,0 +1,35 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
describe("--json flag simple tests", () => {
test("--print with --json outputs JSON", async () => {
const proc = Bun.spawn({
cmd: [bunExe(), "--print", "42", "--json"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const output = await new Response(proc.stdout).text();
await proc.exited;
expect(output.trim()).toBe("42");
proc.unref();
});
test("--eval with --json outputs JSON", async () => {
const proc = Bun.spawn({
cmd: [bunExe(), "--eval", "({x: 1, y: 2})", "--json"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const output = await new Response(proc.stdout).text();
await proc.exited;
const parsed = JSON.parse(output.trim());
expect(parsed).toEqual({ x: 1, y: 2 });
proc.unref();
});
});

338
test/cli/json-flag.test.ts Normal file
View File

@@ -0,0 +1,338 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
describe("--json flag", () => {
describe("with --print", () => {
test("primitive values", async () => {
// Number
{
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "42", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
expect(exitCode).toBe(0);
expect(stderr).toBe("");
expect(stdout.trim()).toBe("42");
}
// String
{
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", '"hello world"', "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
expect(stdout.trim()).toBe('"hello world"');
}
// Boolean
{
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "true", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
expect(stdout.trim()).toBe("true");
}
// null
{
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "null", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
expect(stdout.trim()).toBe("null");
}
// undefined (should output nothing)
{
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "undefined", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
expect(stdout.trim()).toBe("");
}
});
test("complex objects", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "({x: 1, y: 'test', z: [1,2,3], nested: {a: true}})", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
expect(exitCode).toBe(0);
expect(stderr).toBe("");
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual({
x: 1,
y: "test",
z: [1, 2, 3],
nested: { a: true },
});
});
test("arrays", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "[1, 'two', {three: 3}, [4,5]]", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual([1, "two", { three: 3 }, [4, 5]]);
});
test("dates", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", 'new Date("2024-01-15T12:30:00.000Z")', "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
expect(stdout.trim()).toBe('"2024-01-15T12:30:00.000Z"');
});
test("promises", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "Promise.resolve({resolved: true})", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual({ resolved: true });
});
test("circular references show error message", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "(() => { const obj = {}; obj.circular = obj; return obj; })()", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
// Error is printed to stdout, not stderr
expect(stdout).toContain("JSON.stringify cannot serialize cyclic structures");
});
test("functions are undefined in JSON", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "({fn: () => {}, value: 42})", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual({ value: 42 }); // fn should be omitted
});
test("--print without --json uses console formatter", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "({x: 1, y: 2})"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
// Console formatter includes spaces and formatting
expect(stdout.trim()).toContain("x:");
expect(stdout.trim()).toContain("y:");
expect(stdout.trim()).not.toBe('{"x":1,"y":2}');
});
});
describe("with --eval", () => {
test("--eval without --json doesn't print", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--eval", "42"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
expect(stdout.trim()).toBe("");
});
test("--eval with --json prints result as JSON", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--eval", "({result: 'success'})", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual({ result: "success" });
});
test("expressions with side effects", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--eval", "console.log('side effect'); ({value: 123})", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const lines = stdout.trim().split("\n");
expect(lines[0]).toBe("side effect"); // console.log output
expect(JSON.parse(lines[1])).toEqual({ value: 123 }); // JSON output
});
});
describe("--json with regular script files", () => {
test("regular script files do not output JSON (only --print and --eval do)", async () => {
const dir = tempDirWithFiles("json-flag-script", {
"script.js": `
console.log("This will show");
const data = { value: 123 };
data; // This won't be captured - regular scripts don't have return values
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "--json", "script.js"],
env: bunEnv,
cwd: dir,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
// Only console.log output appears, no JSON
expect(stdout.trim()).toBe("This will show");
});
test("--json flag is primarily for --print and --eval", async () => {
// Test to document that --json is meant for use with --print and --eval
// Regular script files don't have a meaningful return value to capture
const dir = tempDirWithFiles("json-flag-doc", {
"data.js": `module.exports = { value: 42 };`,
});
// This shows the intended usage - evaluating the module
await using proc = Bun.spawn({
cmd: [bunExe(), "--eval", "require('./data.js')", "--json"],
env: bunEnv,
cwd: dir,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual({ value: 42 });
});
});
describe("edge cases", () => {
test("BigInt serialization", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "({bigint: 123n})", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
// BigInt error is printed to stdout
expect(stdout).toContain("JSON.stringify cannot serialize BigInt");
});
test("Symbol serialization", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "({sym: Symbol('test'), value: 1})", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
// Symbols are omitted in JSON
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual({ value: 1 });
});
test("NaN and Infinity", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "({nan: NaN, inf: Infinity, negInf: -Infinity})", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual({
nan: null,
inf: null,
negInf: null,
});
});
test("deeply nested objects", async () => {
const deepObj = "({a: {b: {c: {d: {e: {f: {g: {h: 'deep'}}}}}}}})";
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", deepObj, "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed.a.b.c.d.e.f.g.h).toBe("deep");
});
test("large arrays", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--print", "Array.from({length: 1000}, (_, i) => i)", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed.length).toBe(1000);
expect(parsed[0]).toBe(0);
expect(parsed[999]).toBe(999);
});
});
describe("flag combinations", () => {
test("--json can appear before --print", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--json", "--print", "({order: 'reversed'})"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual({ order: "reversed" });
});
test("--json can appear before --eval", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--json", "--eval", "({order: 'eval-reversed'})"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual({ order: "eval-reversed" });
});
test("multiple --json flags are idempotent", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "--json", "--print", "({multiple: true})", "--json"],
env: bunEnv,
stderr: "pipe",
});
const [stdout] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
const parsed = JSON.parse(stdout.trim());
expect(parsed).toEqual({ multiple: true });
});
});
});