What I struggled with when porting Mastro to Bun
Mauro Bieg on October 29, 2025
In our last blog post, I talked about What I learned porting Mastro from Deno to Node.js. The next step was of course to get Mastro working on Bun as well.
And again, there were some gotchas that took me some time to resolve. Let’s go through them.
fs.glob
The first thing I immediately ran into was easy to understand. Calling the Node.js fs.glob function with the withFileTypes option in Bun, while type-checking fine, results in a “not-implemented” runtime error. There’s a Bun GitHub issue about that, with @robobun making a half-hearted attempt at a PR. You might wonder who @robobun is, and looking at their profile, it says “the official helper for Bun” – appears to be some AI bot.
Anyway, I just resorted to calling fs.glob without the withFileTypes option when in Bun, and making a call to stat for each file, to determine whether it’s a folder or a file.
URLPattern
Next one up. While all three major browsers, Deno, and Node.js now support URLPattern, there is still an open issue about it in Bun.
This was also relatively easy to fix by loading the urlpattern-polyfill, which I added as a dependency to Mastro’s Bun starter template.
bun create
The first tricky one was that running
bun create @mastrojs/mastro@latest
failed to detect that we were running in Bun, and would download the wrong template – even when I checked for typeof Bun or process.versions.bun, which are the official ways to detect that.
But after publishing that package a few times, waiting for it to propagate through the CDNs, and running the command again, I was certain: neither of these work when inside bun create. Probably they’re shimming it somehow to fake Node.js compatibility. But I couldn’t find any official docs on that.
Thus we do what other such packages seem to do:
process.env.npm_config_user_agent?.startsWith("bun")
Bun.write
When generating the static files, I wanted to call Bun.write instead of the weird Node.js code. That was the second weird gotcha: Bun.write apparently doesn’t take a standard ReadableStream, only: string | ArrayBufferLike | TypedArray<ArrayBufferLike> | Blob | BlobPart[]. Just to be sure, I tried passing it a ReadableStream. It didn’t fail at runtime, rejoice! But wait. What do we find when opening the generated file in a text editor? The file contains the contents [object ReadableStream] 🤡
So what does that mean? Does Bun.write just not support streaming into a file? Will it always have to wait for the whole thing to be written into a Buffer in memory first? The official workaround is to go through a Response object, which might or might not stream 🤷🏽‍♂️
Be that as it may, I already had a Response object coming from the Mastro route handler. So I decided to special-case it and directly pass it that Response object (instead of response.body, which is a ReadableStream). Then I ran the program. And… nothing. Like, literally nothing happened. It didn’t print “Generated static site and wrote to generated/ folder” like Mastro usually does. And indeed, while it created the folder, it was empty. But exit code was zero.
So I put some console.logs and an additional try-catch around it:
console.log("before");
try {
const nrBytesWritten = await Bun.write(outFilePath, response);
console.log("after", nrBytesWritten);
} catch (e) {
console.log("caught", e);
}
The above logged “before”, and again exited with an exit code of zero. Yes, it just silently seemed to crash. Wow.
For all that the Bun.write docs like to advertise “the fastest syscalls available to copy from input into destination”, that doesn’t help a whole lot if it doesn’t work properly.
And no, I cannot be bothered to create a reproducible test case right now. For the record, this was in Bun v1.3.1 on macOS 15.7.1, and I ran it in the Mastro template for Bun after editing node_modules/@mastrojs/mastro/src/generator.js directly. It might have something to do with response coming from a dynamically imported module (the route handler), or I don’t know. I am happy to accept pull requests though, should anybody feel called to make Bun.write work in Mastro.
But for now, the moral of the story seems to be once again to just use Node.js’s old-school and verbose API. At least that works – even in Bun.
Either way, it would actually be interesting to update our benchmark to see whether streaming the Response.body into the file is actually really faster and/or consumes less memory, compared to serializing to a string first, and test it in Deno, Node.js and Bun.
Conclusion
While those were weird, the good news is that overall, supporting Bun required very few code changes to Mastro.
Let's keep in touch! 👨‍🍳
Follow us on Bluesky, or add our blog to your RSS reader (feed link).
This is the end of the page. Yet it may be the beginning of your journey with Mastro.