From 65d006aae05fbf974282ab8edfe518d3f2a55465 Mon Sep 17 00:00:00 2001 From: Kaj Kowalski Date: Thu, 8 Jan 2026 20:32:08 +0100 Subject: [PATCH] fix(parser): fix bytecode CJS pragma detection after shebang (#25868) ### What does this PR do? Fix bytecode CJS pragma detection when source file contains a shebang. When bundling with `--bytecode` and the source file has a shebang, the output silently fails to execute (exits 0, no output). Reproduction: [github.com/kjanat/bun-bytecode-banner-bug](https://github.com/kjanat/bun-bytecode-banner-bug) ```js // Bundled output: #!/usr/bin/env bun // shebang preserved // @bun @bytecode @bun-cjs // pragma on line 2 (function(exports, require, module, __filename, __dirname) { ... }) ``` The pragma parser in `hasBunPragma()` correctly skips the shebang line, but uses `self.lexer.end` instead of `contents.len` when scanning for `@bun-cjs`/`@bytecode` tokens. This causes the pragma to not be recognized. **Fix:** ```zig // Before while (cursor < self.lexer.end) : (cursor += 1) { // After while (cursor < end) : (cursor += 1) { ``` Where `end` is already defined as `contents.len` at the top of the function. ### How did you verify your code works? - Added bundler test `banner/SourceHashbangWithBytecodeAndCJSTargetBun` in `test/bundler/bundler_banner.test.ts` - Added regression tests in `test/regression/issue/bun-bytecode-shebang.test.ts` that verify: - CJS wrapper executes when source has shebang - CJS wrapper executes when source has shebang + bytecode pragma - End-to-end: bundled bytecode output with source shebang runs correctly - Ran the tests in the [kjanat/bun-bytecode-banner-bug](https://github.com/kjanat/bun-bytecode-banner-bug) repo to verify the issue is fixed --------- Signed-off-by: Kaj Kowalski Co-authored-by: Dylan Conway Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/ast/Parser.zig | 2 +- test/bundler/bundler_banner.test.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/ast/Parser.zig b/src/ast/Parser.zig index e0e6c7acd5..bc882d7ac1 100644 --- a/src/ast/Parser.zig +++ b/src/ast/Parser.zig @@ -1494,7 +1494,7 @@ pub const Parser = struct { var state: PragmaState = .{}; - while (cursor < self.lexer.end) : (cursor += 1) { + while (cursor < end) : (cursor += 1) { switch (contents[cursor]) { '\n' => break, '@' => { diff --git a/test/bundler/bundler_banner.test.ts b/test/bundler/bundler_banner.test.ts index 19bed4f424..a4db75a7eb 100644 --- a/test/bundler/bundler_banner.test.ts +++ b/test/bundler/bundler_banner.test.ts @@ -203,4 +203,33 @@ module.exports = 1;`, `); }, }); + + itBundled("banner/SourceHashbangWithBytecodeAndCJSTargetBun", { + banner: "// Copyright 2024 Example Corp", + format: "cjs", + target: "bun", + bytecode: true, + outdir: "/out", + minifyWhitespace: true, + backend: "api", + files: { + "/a.js": `#!/usr/bin/env bun +module.exports = 1; +console.log("bun!");`, + }, + onAfterBundle(api) { + const content = api.readFile("/out/a.js"); + // Shebang from source should come first, then @bun pragma + expect(content).toMatchInlineSnapshot(` + "#!/usr/bin/env bun + // @bun @bytecode @bun-cjs + (function(exports, require, module, __filename, __dirname) {// Copyright 2024 Example Corp + module.exports=1;console.log("bun!");}) + " + `); + }, + run: { + stdout: "bun!\n", + }, + }); });