{"slug": "bun-v1-3-5", "title": "Bun v1.3.5", "summary": "Bun v1.3.5 introduces a new built-in API for pseudo-terminal (PTY) support, allowing developers to create and manage interactive terminal applications like shells and text editors directly from Bun. The update also adds compile-time feature flags via `import { feature } from \"bun:bundle\"`, enabling static dead-code elimination that removes unused code paths from bundles based on build-time flags. Additionally, the release improves the accuracy of `Bun.stringWidth` for calculating terminal display widths.", "body_md": "To install Bun\ncurl -fsSL https://bun.sh/install | bash\nnpm install -g bun\npowershell -c \"irm bun.sh/install.ps1|iex\"\nscoop install bun\nbrew tap oven-sh/bun\nbrew install bun\ndocker pull oven/bun\ndocker run --rm --init --ulimit memlock=-1:-1 oven/bun\nTo upgrade Bun\nbun upgrade\nBun.Terminal\nAPI for pseudo-terminal (PTY) support\nBun now has a built-in API for creating and managing pseudo-terminals, enabling interactive terminal applications like shells, vim\n, htop\n, and any program that expects to run in a real TTY.\nUse the new terminal\noption in Bun.spawn()\nto attach a PTY to your subprocess:\nconst commands = [\"echo Hello from PTY!\", \"exit\"];\nconst proc = Bun.spawn([\"bash\"], {\nterminal: {\ncols: 80,\nrows: 24,\ndata(terminal, data) {\nprocess.stdout.write(data);\nif (data.includes(\"$\")) {\nterminal.write(commands.shift() + \"\\n\");\n}\n},\n},\n});\nawait proc.exited;\nproc.terminal.close();\nWith a PTY attached, the subprocess sees process.stdout.isTTY\nas true\n, enabling colored output, cursor movement, and interactive prompts that normally require a real terminal.\nRunning interactive programs\nconst proc = Bun.spawn([\"vim\", \"file.txt\"], {\nterminal: {\ncols: process.stdout.columns,\nrows: process.stdout.rows,\ndata(term, data) {\nprocess.stdout.write(data);\n},\n},\n});\nproc.exited.then((code) => process.exit(code));\n// Handle terminal resize\nprocess.stdout.on(\"resize\", () => {\nproc.terminal.resize(process.stdout.columns, process.stdout.rows);\n});\n// Forward input\nprocess.stdin.setRawMode(true);\nfor await (const chunk of process.stdin) {\nproc.terminal.write(chunk);\n}\nReusable terminals\nCreate a standalone terminal with new Bun.Terminal()\nto reuse across multiple subprocesses:\nawait using terminal = new Bun.Terminal({\ncols: 80,\nrows: 24,\ndata(term, data) {\nprocess.stdout.write(data);\n},\n});\nconst proc1 = Bun.spawn([\"echo\", \"first\"], { terminal });\nawait proc1.exited;\nconst proc2 = Bun.spawn([\"echo\", \"second\"], { terminal });\nawait proc2.exited;\n// Terminal is closed automatically by `await using`\nThe Terminal\nobject provides full PTY control with write()\n, resize()\n, setRawMode()\n, ref()\n/unref()\n, and close()\nmethods.\nNote: Terminal support is only available on POSIX systems (Linux, macOS). If you're interested in using this API on Windows, please file an issue and we will implement it.\nCompile-time Feature Flags for Dead-Code Elimination\nBun's bundler now supports compile-time feature flags via import { feature } from \"bun:bundle\"\n. This enables statically-analyzable dead-code elimination—code paths can be completely removed from your bundle based on which flags are enabled at build time.\nimport { feature } from \"bun:bundle\";\nif (feature(\"PREMIUM\")) {\n// Only included when PREMIUM flag is enabled\ninitPremiumFeatures();\n}\nif (feature(\"DEBUG\")) {\n// Eliminated entirely when DEBUG flag is disabled\nconsole.log(\"Debug mode\");\n}\nThe feature()\nfunction is replaced with true\nor false\nat bundle time. Combined with minification, unreachable branches are eliminated completely:\n// Input\nimport { feature } from \"bun:bundle\";\nconst mode = feature(\"PREMIUM\") ? \"premium\" : \"free\";\n// Output (with --feature PREMIUM --minify)\nvar mode = \"premium\";\nCLI\n# Enable feature during build\nbun build --feature=PREMIUM ./app.ts --outdir ./out\n# Enable at runtime\nbun run --feature=DEBUG ./app.ts\n# Enable in tests\nbun test --feature=MOCK_API\n# Multiple flags\nbun build --feature=PREMIUM --feature=DEBUG ./app.ts\nJavaScript API\nawait Bun.build({\nentrypoints: [\"./app.ts\"],\noutdir: \"./out\",\nfeatures: [\"PREMIUM\", \"DEBUG\"],\n});\nType Safety\nFor autocomplete and compile-time validation, augment the Registry\ninterface:\n// env.d.ts\ndeclare module \"bun:bundle\" {\ninterface Registry {\nfeatures: \"DEBUG\" | \"PREMIUM\" | \"BETA_FEATURES\";\n}\n}\nNow feature(\"TYPO\")\nbecomes a type error.\nUse cases include: platform-specific builds, environment-based features, A/B testing variants, and paid tier features.\nImproved Bun.stringWidth\nAccuracy\nBun.stringWidth\nnow correctly calculates terminal display width for a much wider range of Unicode characters, ANSI escape sequences, and emoji.\nZero-width Character Support\nPreviously unhandled invisible characters are now correctly measured as zero-width:\n- Soft hyphen (U+00AD)\n- Word joiner and invisible operators (U+2060-U+2064)\n- Arabic formatting characters\n- Indic script combining marks (Devanagari through Malayalam)\n- Thai and Lao combining marks\n- Tag characters and more\nANSI Escape Sequence Handling\n- CSI sequences: Now properly handles all CSI final bytes (0x40-0x7E), not just\nm\n. Cursor movement, erase, scroll, and other control sequences are correctly excluded from width calculation. - OSC sequences: Added support for OSC sequences including OSC 8 hyperlinks, with both BEL and ST terminators.\n- Fixed:\nESC ESC\nstate machine bug that incorrectly reset state.\nGrapheme-aware Emoji Width\nEmoji are now measured correctly as single graphemes:\nBun.stringWidth(\"🇺🇸\"); // Now: 2 (was: 1) - flag emoji\nBun.stringWidth(\"👋🏽\"); // Now: 2 (was: 4) - emoji + skin tone\nBun.stringWidth(\"👨👩👧\"); // Now: 2 (was: 8) - ZWJ family sequence\nBun.stringWidth(\"\\u2060\"); // Now: 0 (was: 1) - word joiner\nProperly handles flag emoji, skin tone modifiers, ZWJ sequences (family, professions), keycap sequences, and variation selectors.\nV8 Value Type Checking APIs\nBun now implements additional V8 C++ API methods for type checking that are commonly used by native Node.js modules:\nv8::Value::IsMap()\n- checks if a value is a Mapv8::Value::IsArray()\n- checks if a value is an Arrayv8::Value::IsInt32()\n- checks if a value is a 32-bit integerv8::Value::IsBigInt()\n- checks if a value is a BigInt\nThis improves compatibility with native addons that rely on these type checking APIs.\nContent-Disposition\nsupport for S3 uploads\nBun's built-in S3 client now supports the contentDisposition\noption, allowing you to control how browsers handle downloaded files. This is useful for setting filenames or specifying whether files should be displayed inline or downloaded as attachments.\nimport { s3 } from \"bun\";\n// Force download with a specific filename\nconst file = s3.file(\"report.pdf\", {\ncontentDisposition: 'attachment; filename=\"quarterly-report.pdf\"',\n});\n// Or set it when writing\nawait s3.write(\"image.png\", imageData, {\ncontentDisposition: \"inline\",\n});\nThe option works across all S3 upload methods—simple uploads, multipart uploads, and streaming uploads.\nThanks to @AltanM for the contribution!\nEnvironment Variable Expansion in .npmrc\nQuoted Values\nFixed environment variable expansion in quoted .npmrc\nvalues and added support for the ?\noptional modifier, matching npm's behavior.\nPreviously, environment variables inside quoted strings weren't being expanded. Now all three syntaxes work consistently:\n# All expand to the value when NPM_TOKEN is set\ntoken = ${NPM_TOKEN}\ntoken = \"${NPM_TOKEN}\"\ntoken = '${NPM_TOKEN}'\nThe ?\nmodifier allows graceful handling of undefined environment variables:\n# Without ? - undefined vars are left as-is\ntoken = ${NPM_TOKEN} # → ${NPM_TOKEN}\n# With ? - undefined vars expand to empty string\ntoken = ${NPM_TOKEN?} # → (empty)\nauth = \"Bearer ${TOKEN?}\" # → Bearer\nBug Fixes\nNetworking\n- Fixed: macOS kqueue event loop bug that could cause 100% CPU usage with writable sockets when no actual I/O was pending. This was caused by a filter comparison in the kqueue event handling that used bitwise AND (\n&\n) instead of equality (==\n). Combined with missingEV_ONESHOT\nflags on writable events, this caused the event loop to spin continuously even when no I/O was pending in certain cases. - Fixed: incorrect behavior in certain cases when automatically re-subscribing to writable sockets after a write failure\n- Fixed:\nfetch()\nthrowing an error when a proxy object without aurl\nproperty was passed, restoring compatibility with libraries liketaze\nthat passURL\nobjects as proxy values - Fixed: HTTP proxy authentication failing silently with 401 Unauthorized when passwords exceed 4096 characters (e.g., JWT tokens used as proxy credentials)\n- Fixed: potential crash when upgrading an existing TCP socket to TLS\nWindows fixes\n- Fixed: WebSocket crash on Windows when publishing large messages with\nperMessageDeflate: true\ndue to a zlib version mismatch between headers and linked library - Fixed: A panic in error handling on Windows when\n.bunx\nmetadata files were corrupted, now gracefully falls back to the slow path instead of panicking - Fixed:\nbunx\npanicking on Windows when passing empty string arguments in certain cases and incorrectly splitting quoted arguments containing spaces\nNode.js compatibility\n- Fixed:\nurl.domainToASCII()\nandurl.domainToUnicode()\nthrowingTypeError\ninstead of returning an empty string for invalid domains, matching Node.js behavior - Fixed: Native modules failing with\nsymbol 'napi_register_module_v1' not found\nwhen loaded multiple times, such as during hot module reloading or when the same native addon is required in both the main thread and a worker. - Fixed: node:http server's\nrequest.socket._secureEstablished\nreturning incorrect values on HTTPS servers under concurrent connections in certain cases\nTypeScript definitions\n- Fixed: TypeScript type errors when using\nexpect().not.toContainKey()\nand related matchers where the argument was incorrectly inferred asnever\n, preventing any value from being passed. The matchers now properly fall back toPropertyKey\nwhen type inference fails. - Fixed: Compatibility with\n@types/node@25\nin@types/bun\n. - Fixed: TypeScript type compatibility with\n@types/node@25.0.2\nwhereprocess.noDeprecation\nproperty type definition changed\nWeb APIs\n- Fixed:\nResponse.clone()\nandRequest.clone()\nincorrectly locking the original body whenresponse.body\norrequest.body\nwas accessed before callingclone()\n.\nBundler\n- Fixed: transpiler incorrectly simplifying object spread expressions with nullish coalescing to empty objects (e.g.,\n{...k, a: k?.x ?? {}}\n) which produced invalid JavaScript output and caused \"Expected CommonJS module to have a function wrapper\" errors when running Webpack-generated bundles.\nYAML\n- Fixed:\nYAML.stringify\nnot quoting strings ending with colons (e.g.,\"tin:\"\n), which causedYAML.parse\nto fail with \"Unexpected token\" when parsing the output back - Fixed YAML 1.2 spec compliance issue treating\nyes\n,Yes\n,YES\n,no\n,No\n,NO\n,on\n,On\n,ON\n,off\n,Off\n,OFF\n,y\n,Y\nas boolean values instead of string values. These are booleans in YAML 1.1 and not in YAML 1.2.\nSecurity\n- Fixed: Security issue where default trusted dependencies list could be spoofed by non-npm packages using matching names through\nfile:\n,link:\n,git:\n, orgithub:\ndependencies. These sources now require explicittrustedDependencies\nconfiguration to run lifecycle scripts. Thanks to @orenyomtov for the report! - Fixed: Internal JSC\nLoader\nproperty leaking intonode:vm\ncontexts when it should not be visible in sandboxed environments. Thanks to @ChipMonto for the report!\nLinux fixes\n- Fixed:\nBun.write\nandfs.copyFile\nfailing on eCryptfs and other encrypted filesystems on Linux", "url": "https://wpnews.pro/news/bun-v1-3-5", "canonical_source": "https://bun.com/blog/bun-v1.3.5", "published_at": "2025-12-17 10:11:00+00:00", "updated_at": "2026-05-22 20:42:09.053811+00:00", "lang": "en", "topics": ["developer-tools", "open-source"], "entities": ["Bun"], "alternates": {"html": "https://wpnews.pro/news/bun-v1-3-5", "markdown": "https://wpnews.pro/news/bun-v1-3-5.md", "text": "https://wpnews.pro/news/bun-v1-3-5.txt", "jsonld": "https://wpnews.pro/news/bun-v1-3-5.jsonld"}}