diff --git a/docs/guides/util/base64.mdx b/docs/guides/util/base64.mdx index 8a976ad02474..5a088240ed1b 100644 --- a/docs/guides/util/base64.mdx +++ b/docs/guides/util/base64.mdx @@ -40,10 +40,11 @@ const text = bytes.toString("utf8"); The older [`btoa()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa) and [`atob()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/atob) APIs are still available for compatibility, but they operate on binary strings instead of byte arrays. Avoid them in new code, especially when handling arbitrary binary data or non-ASCII text. - ```ts - const encoded = btoa("bun"); // => "YnVu" - const decoded = atob(encoded); // => "bun" - ``` +```ts +const encoded = btoa("bun"); // => "YnVu" +const decoded = atob(encoded); // => "bun" +``` + --- diff --git a/docs/runtime/web-apis.mdx b/docs/runtime/web-apis.mdx index 97e781ae04d9..7c07a493e421 100644 --- a/docs/runtime/web-apis.mdx +++ b/docs/runtime/web-apis.mdx @@ -8,22 +8,22 @@ Some Web APIs aren't relevant in the context of a server-first runtime like Bun, The following Web APIs are partially or completely supported. -| Category | APIs | -| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| HTTP | [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers), [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController), [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) | -| URLs | [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) | -| Web Workers | [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker), [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/postMessage), [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone), [`MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort), [`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel), [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel) | -| Streams | [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream), [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream), [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream), [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy), [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) and associated classes | -| Blob | [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) | -| WebSockets | [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) | +| Category | APIs | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| HTTP | [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers), [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController), [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) | +| URLs | [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) | +| Web Workers | [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker), [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/postMessage), [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone), [`MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort), [`MessageChannel`](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel), [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel) | +| Streams | [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream), [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream), [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream), [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy), [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) and associated classes | +| Blob | [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) | +| WebSockets | [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) | | Encoding and decoding | [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), [`Uint8Array.prototype.toBase64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64), [`Uint8Array.fromBase64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64), [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder), [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder), [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob), [`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/btoa) | -| JSON | [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) | -| Timeouts | [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout), [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout) | -| Intervals | [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval), [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) | -| Crypto | [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto), [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto), [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey) | -| Debugging | [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console), [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/Performance) | -| Microtasks | [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) | -| Errors | [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError) | -| User interaction | [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) (intended for interactive CLIs) | -| Realms | [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm) | -| Events | [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget), [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event), [`ErrorEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent), [`CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent), [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent) | +| JSON | [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) | +| Timeouts | [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout), [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout) | +| Intervals | [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval), [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) | +| Crypto | [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto), [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto), [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey) | +| Debugging | [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console), [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/Performance) | +| Microtasks | [`queueMicrotask`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) | +| Errors | [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError) | +| User interaction | [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) (intended for interactive CLIs) | +| Realms | [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm) | +| Events | [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget), [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event), [`ErrorEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent), [`CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent), [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent) | diff --git a/src/bundler/ParseTask.rs b/src/bundler/ParseTask.rs index f25c680c4b08..270396771f18 100644 --- a/src/bundler/ParseTask.rs +++ b/src/bundler/ParseTask.rs @@ -2282,7 +2282,12 @@ pub mod parse_worker { // above. `BackRef` is `Copy`; the deref to `&BundleV2` is safe. let worker_ctx = unsafe { (*worker_raw).ctx }; - let will_close_file_descriptor = matches!(task.contents_or_fd, ContentsOrFd::Fd { .. }) + // Only close a descriptor this task opened. A valid `file` was borrowed + // from the resolver's entry cache (symlink-resolved files cache their fd + // there); closing it leaves a stale fd for the next in-process build. + let opened_own_fd = + matches!(task.contents_or_fd, ContentsOrFd::Fd { file, .. } if !file.is_valid()); + let will_close_file_descriptor = opened_own_fd && entry.fd.is_valid() && entry.fd.stdio_tag().is_none() && worker_ctx.bun_watcher.is_none(); diff --git a/test/bundler/bun-build-api.test.ts b/test/bundler/bun-build-api.test.ts index 8d17f5515707..e609d4fa9f98 100644 --- a/test/bundler/bun-build-api.test.ts +++ b/test/bundler/bun-build-api.test.ts @@ -1,7 +1,7 @@ import assert from "assert"; import { afterEach, describe, expect, test } from "bun:test"; -import { readFileSync, writeFileSync } from "fs"; -import { bunEnv, bunExe, isASAN, isDebug, tempDirWithFiles, tempDirWithFilesAnon } from "harness"; +import { mkdirSync, readFileSync, symlinkSync, writeFileSync } from "fs"; +import { bunEnv, bunExe, isASAN, isDebug, isWindows, tempDir, tempDirWithFiles, tempDirWithFilesAnon } from "harness"; import path, { join } from "path"; import { SourceMapConsumer } from "source-map"; import { buildNoThrow } from "./buildNoThrow"; @@ -446,6 +446,49 @@ describe("Bun.build", () => { Bun.gc(true); }); + // https://github.com/oven-sh/bun/issues/33099 + // A package reached through a symlinked node_modules entry caches its file + // descriptor in the resolver. A second in-process Bun.build() used to reuse a + // descriptor the first build had already closed, failing with EBADF. The test + // host must also import the package so its fd is cached before the builds run. + test.concurrent.skipIf(isWindows)( + "repeated in-process builds of a symlinked package do not reuse a closed fd", + async () => { + using dir = tempDir("build-symlink-fd-cache", { + "vendor/pkg/package.json": `{"name":"pkg","version":"1.0.0","type":"module","exports":"./index.js"}`, + "vendor/pkg/index.js": `export const value = 1;\n`, + "entry.ts": `import { value } from "pkg";\nconsole.log(value);\n`, + "repro.test.ts": ` + import { it } from "bun:test"; + import { value } from "pkg"; + void value; + it("builds", async () => { + for (let i = 1; i <= 3; i++) { + const result = await Bun.build({ entrypoints: ["./entry.ts"] }); + if (!result.success) { + throw new AggregateError(result.logs, "build " + i + " failed"); + } + } + console.log("ALL_BUILDS_OK"); + }); + `, + }); + mkdirSync(join(String(dir), "node_modules"), { recursive: true }); + symlinkSync("../vendor/pkg", join(String(dir), "node_modules", "pkg")); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "test", "repro.test.ts"], + env: bunEnv, + cwd: String(dir), + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stdout + stderr).toContain("ALL_BUILDS_OK"); + expect(exitCode).toBe(0); + }, + ); + test.concurrent("errors are returned as an array", async () => { const x = await buildNoThrow({ entrypoints: [join(import.meta.dir, "does-not-exist.ts")],