From 03faa26c02466c86ecad3eaeb877d57263477f8b Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 13 May 2026 21:17:29 +0000 Subject: [PATCH 1/2] bake: validate app.bundlerOptions and its server/client/ssr fields are objects --- src/bake/bake.zig | 15 ++++++++++---- test/bake/app-options-validation.test.ts | 25 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 test/bake/app-options-validation.test.ts diff --git a/src/bake/bake.zig b/src/bake/bake.zig index cd621eb3de4..661c84f4235 100644 --- a/src/bake/bake.zig +++ b/src/bake/bake.zig @@ -68,14 +68,17 @@ pub const UserOptions = struct { } if (try config.getOptional(global, "bundlerOptions", JSValue)) |js_options| { + if (!js_options.isObject()) { + return global.throwInvalidArguments("'" ++ api_name ++ ".bundlerOptions' must be an object", .{}); + } if (try js_options.getOptional(global, "server", JSValue)) |server_options| { - bundler_options.server = try BuildConfigSubset.fromJS(global, server_options); + bundler_options.server = try BuildConfigSubset.fromJS(global, "server", server_options); } if (try js_options.getOptional(global, "client", JSValue)) |client_options| { - bundler_options.client = try BuildConfigSubset.fromJS(global, client_options); + bundler_options.client = try BuildConfigSubset.fromJS(global, "client", client_options); } if (try js_options.getOptional(global, "ssr", JSValue)) |ssr_options| { - bundler_options.ssr = try BuildConfigSubset.fromJS(global, ssr_options); + bundler_options.ssr = try BuildConfigSubset.fromJS(global, "ssr", ssr_options); } } @@ -202,9 +205,13 @@ const BuildConfigSubset = struct { minify_identifiers: ?bool = null, minify_whitespace: ?bool = null, - pub fn fromJS(global: *jsc.JSGlobalObject, js_options: JSValue) bun.JSError!BuildConfigSubset { + pub fn fromJS(global: *jsc.JSGlobalObject, comptime property_name: []const u8, js_options: JSValue) bun.JSError!BuildConfigSubset { var options = BuildConfigSubset{}; + if (!js_options.isObject()) { + return global.throwInvalidArguments("'" ++ api_name ++ ".bundlerOptions." ++ property_name ++ "' must be an object", .{}); + } + if (try js_options.getOptional(global, "sourcemap", JSValue)) |val| brk: { if (try bun.schema.api.SourceMapMode.fromJS(global, val)) |sourcemap| { options.source_map = sourcemap; diff --git a/test/bake/app-options-validation.test.ts b/test/bake/app-options-validation.test.ts new file mode 100644 index 00000000000..bc248b3cf5a --- /dev/null +++ b/test/bake/app-options-validation.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, test } from "bun:test"; + +describe("Bun.serve app.bundlerOptions validation", () => { + test("throws when bundlerOptions is not an object", () => { + expect(() => + Bun.serve({ + // @ts-expect-error + app: { bundlerOptions: 42 }, + }), + ).toThrow("'app.bundlerOptions' must be an object"); + }); + + for (const key of ["server", "client", "ssr"] as const) { + for (const value of [1073741824, "foo", true]) { + test(`throws when bundlerOptions.${key} is ${JSON.stringify(value)}`, () => { + expect(() => + Bun.serve({ + // @ts-expect-error + app: { bundlerOptions: { [key]: value } }, + }), + ).toThrow(`'app.bundlerOptions.${key}' must be an object`); + }); + } + } +}); From dd9f5861f54903f3b88375874ae0352bf24cdb97 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Wed, 13 May 2026 21:21:47 +0000 Subject: [PATCH 2/2] test: use describe.each/test.each for parameterized cases --- test/bake/app-options-validation.test.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/test/bake/app-options-validation.test.ts b/test/bake/app-options-validation.test.ts index bc248b3cf5a..1979da03428 100644 --- a/test/bake/app-options-validation.test.ts +++ b/test/bake/app-options-validation.test.ts @@ -10,16 +10,14 @@ describe("Bun.serve app.bundlerOptions validation", () => { ).toThrow("'app.bundlerOptions' must be an object"); }); - for (const key of ["server", "client", "ssr"] as const) { - for (const value of [1073741824, "foo", true]) { - test(`throws when bundlerOptions.${key} is ${JSON.stringify(value)}`, () => { - expect(() => - Bun.serve({ - // @ts-expect-error - app: { bundlerOptions: { [key]: value } }, - }), - ).toThrow(`'app.bundlerOptions.${key}' must be an object`); - }); - } - } + describe.each(["server", "client", "ssr"] as const)("bundlerOptions.%s", key => { + test.each([1073741824, "foo", true])("throws when value is %p", value => { + expect(() => + Bun.serve({ + // @ts-expect-error + app: { bundlerOptions: { [key]: value } }, + }), + ).toThrow(`'app.bundlerOptions.${key}' must be an object`); + }); + }); });