Skip to content

Decode module sources with the // @bun pragma as UTF-8 - #37162

Open
robobun wants to merge 5 commits into
mainfrom
farm/68cb0a3b/fix-bun-pragma-utf8
Open

Decode module sources with the // @bun pragma as UTF-8#37162
robobun wants to merge 5 commits into
mainfrom
farm/68cb0a3b/fix-bun-pragma-utf8

Conversation

@robobun

@robobun robobun commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes #37161

A file with the // @bun pragma had its non-ASCII string literals decoded as latin-1 at runtime, one code point per UTF-8 byte, so "\u2014" (U+2014) became U+00E2 U+0080 U+0094. Bun.build writes the pragma into its output, so bundles that contain raw UTF-8 bytes were corrupted without anyone opting in.

Repro:

$ printf 'const DASH = "\xe2\x80\x94"\nconsole.log([...DASH].map(c => c.codePointAt(0).toString(16)).join(" "))\n' > repro.js
$ bun repro.js
2014
$ printf '// @bun\n' | cat - repro.js > repro-pragma.js
$ bun repro-pragma.js
e2 80 94

Cause: the pragma makes the loader skip the transpiler and hand the raw source bytes to JSC directly, and both the sync path (src/runtime/jsc_hooks.rs) and the async transpiler-store path (src/jsc/RuntimeTranspilerStore.rs) built the source string with String::clone_latin1, which maps each byte to one code point. That is only correct for pure ASCII.

Fix: use String::clone_utf8 at both sites. It keeps the Latin-1 fast path for all-ASCII sources and does a real UTF-8 decode otherwise, matching how the non-pragma path decodes the same bytes.

Bytecode sidecars (// @bun @bytecode) are safe: bun build --bytecode forces --target=bun, whose printer escapes to ASCII, so the SourceCodeKey hash is unchanged for bundler output. If a source with a sidecar does contain raw non-ASCII bytes, the key no longer matches and JSC silently falls back to parsing (verified manually with an edited bundle, no crash, correct output).

Tests: added import, require, and entry-point cases with raw UTF-8 to test/bundler/transpiler/runtime-transpiler.test.ts. All three fail on bun 1.3.6 (e2 80 94 instead of 2014) and pass with this change, along with the rest of the file.


Second commit: fixing this surfaced a related pre-existing bug in the same loader path. The transpiler-cache-hit branch for the entry point returns early and skips the is_main -> has_loaded = true write the full parse path does. With has_loaded still false, an import of a file with an unknown extension (for example ./template.hbs) falls into the "potentially the main module" fallback and is parsed as tsx, failing with Expected JSX element name but found "!". So an entry point above the 4 KiB cache minimum that imports such an asset works on the first run and fails on every cache-hit run after. Debug builds discard restored cache entries, which is why this only reproduces in release builds (the existing // @bun > async transpiler and require() tests in runtime-transpiler.test.ts fail on any release build with a warm cache, including current main). Fixed by setting has_loaded on the cache-hit path too, with a test in test/cli/run/transpiler-cache.test.ts that enables cache restore in debug builds via BUN_DEBUG_ENABLE_RESTORE_FROM_TRANSPILER_CACHE.

Third commit: review found the same has_loaded gap in the already-bundled early return itself, so a // @bun entry point importing an unknown-extension asset hit the same tsx fallback in every build type. Same guard applied there, with a test in runtime-transpiler.test.ts.


[review] gate passed · iteration 3 · 4 files touched

fails on main (without fix)
ASAN without fix: 5 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bundler/transpiler/runtime-transpiler.test.ts test/cli/run/transpiler-cache.test.ts
bun test v1.4.0 (2f3024cfd)

test/cli/run/transpiler-cache.test.ts:
(pass) transpiler cache > works [919.71ms]
(pass) transpiler cache > works with empty files [639.78ms]
(pass) transpiler cache > ignores files under the minimum cache size [383.84ms]
87 |     writeFileSync(join(temp_dir, "a.js"), `import asset from "./asset.hbs";\n${padding}console.log(typeof asset);\n`);
88 |     expect(await bunRun(join(temp_dir, "a.js"), env)).toSpawn("string");
89 |     expect(newCacheCount()).toBe(1);
90 |     // The second run loads the entry point from the transpiler cache; the
91 |     // unknown extension must still get the file loader, not the tsx fallback.
92 |     expect(await bunRun(join(temp_dir, "a.js"), env)).toSpawn("string");
                                                           ^
error: expect(received).toSpawn(expectedStdout)

Expected process to exit with code 0 but got 1
stderr: 1 | <!DOCTYPE html>
     ^
error: Expected JSX element name 
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (89b7b73f9)

test/cli/run/transpiler-cache.test.ts:
(pass) transpiler cache > works [20.99ms]
(pass) transpiler cache > works with empty files [29.09ms]
(pass) transpiler cache > ignores files under the minimum cache size [8.20ms]
(pass) transpiler cache > cache hit on the entry point keeps unknown-extension imports on the file loader [19.50ms]
(pass) transpiler cache > it is indeed content addressable [26.79ms]
(pass) transpiler cache > doing 50 buns at once does not crash [94.25ms]
(pass) transpiler cache > disables the cache instead of falling back to the shared temp directory [20.05ms]
(pass) transpiler cache > works if the cache is not user-readable [25.83ms]
(pass) transpiler cache > works if the cache is not user-writable [9.39ms]
(pass) transpiler cache > does not inline process.env [17.54ms]
(pass) transpiler cache > --feature flag invalidates cache [50.60ms]
(pass) rejects cached module records containing out-of-range string indices [59.55ms]

test/bundler/transpiler/runtime-transpiler.test.ts:
(pass) use strict causes CommonJS [34.62ms]
(pass) non-ascii regexp literals [0.11ms]
(pass) ascii regex with escapes [0.03ms]
(pass) // @b
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bundler/transpiler/runtime-transpiler.test.ts test/cli/run/transpiler-cache.test.ts
bun test v1.4.0 (2f3024cfd)

test/cli/run/transpiler-cache.test.ts:
(pass) transpiler cache > works [640.65ms]
(pass) transpiler cache > works with empty files [702.36ms]
(pass) transpiler cache > ignores files under the minimum cache size [288.93ms]
(pass) transpiler cache > cache hit on the entry point keeps unknown-extension imports on the file loader [734.73ms]
(pass) transpiler cache > it is indeed content addressable [1011.56ms]
(pass) transpiler cache > doing 50 buns at once does not crash [3252.26ms]
(pass) transpiler cache > disables the cache instead of falling back to the shared temp directory [662.35ms]
(pass) transpiler cache > works if the cache is not user-readable [947.88ms]
(pass) transpiler cache > works if the cache is not user-writable [306.76ms]
(pass) transpiler cache > does not inline process.env [786.99ms]
(pass) transpiler cache > --feature flag invalidates cache [1896.98ms]
(pass) rejects cached module records containing o
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 747ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/12] gen generated_host_exports.rs
generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 239 extern-C blocks audited
[1/12] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m   Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp)
�[1m�[92m   Compiling�[0m bun_paths 
... (truncated)
diff hotspot
src/jsc/RuntimeTranspilerStore.rs                  |  2 +-
 src/runtime/jsc_hooks.rs                           | 17 +++++++-
 test/bundler/transpiler/runtime-transpiler.test.ts | 51 ++++++++++++++++++++++
 test/cli/run/transpiler-cache.test.ts              | 11 +++++
 4 files changed, 79 insertions(+), 2 deletions(-)

gate history · 3 passed · 1 rejected · iteration 3

evidence per changed file
file                                                reads  edits  tests
src/jsc/RuntimeTranspilerStore.rs                       1      1      0
src/runtime/jsc_hooks.rs                                4      3      0
test/bundler/transpiler/runtime-transpiler.test.ts      1      3      0
test/cli/run/transpiler-cache.test.ts                   1      2      0

root cause · written by the author bot

When a file carried the // @Bun pragma, the already-bundled fast path in the module loader converted the raw source bytes with a Latin-1 decode instead of a UTF-8 decode, so every non-ASCII character was expanded into one code point per UTF-8 byte, producing mojibake like U+2014 becoming U+00E2 U+0080 U+0094. The fix switches both affected sites, the synchronous loader path and the async transpiler worker path, to the UTF-8 decoding helper already used elsewhere, which still takes the Latin-1 fast path for all-ASCII sources so pure-ASCII behavior is unchanged. It also sets the VM's has_load…

Files carrying the // @Bun pragma skip the transpiler and hand their raw
bytes to JSC as the module source. Both the sync and async load paths
built that string with clone_latin1, which treats each byte as one code
point, so every non-ASCII UTF-8 sequence turned into mojibake at
runtime. Use clone_utf8 instead, which keeps the Latin-1 fast path for
all-ASCII sources.

A bytecode sidecar generated from ASCII output is unaffected; if the
source has non-ASCII bytes the SourceCodeKey hash no longer matches and
JSC silently falls back to parsing.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The runtime transpiler now preserves UTF-8 source contents for already-bundled modules. Main VM entry points are marked as loaded on bundled and transpiler-cache-hit paths. Tests cover Unicode loading and unknown-extension imports.

Changes

Runtime UTF-8 decoding and cache loading

Layer / File(s) Summary
UTF-8 source conversion
src/jsc/RuntimeTranspilerStore.rs, src/runtime/jsc_hooks.rs
Already-bundled source contents use UTF-8 conversion instead of Latin-1 conversion. Bundled main modules set vm.has_loaded.
Unicode runtime coverage
test/bundler/transpiler/runtime-transpiler.test.ts
Tests verify UTF-8 preservation for dynamic import, require(), and executable entry-point loading.
Transpiler-cache entry loading
src/runtime/jsc_hooks.rs, test/cli/run/transpiler-cache.test.ts
Transpiler-cache hits set vm.has_loaded. The regression test verifies that unknown-extension imports remain file loads on cache hits.

Possibly related PRs

  • oven-sh/bun#33866: Changes the same runtime source-handling paths for already-bundled and transpiled source contents.
  • oven-sh/bun#35971: Modifies runtime transpiler cache-hit handling.
  • oven-sh/bun#36334: Modifies loader handling in src/runtime/jsc_hooks.rs.

Suggested reviewers: jarred-sumner, cirospaciari, dylan-conway

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes fix the linked issue by decoding pragma sources as UTF-8 in both loading paths and add regression tests for the affected runtime behavior.
Out of Scope Changes check ✅ Passed The has_loaded corrections address related early-return behavior in the same loader path and support the added unknown-extension regression tests.
Title check ✅ Passed The title clearly and concisely describes the primary UTF-8 decoding fix for module sources using the // @bun pragma.
Description check ✅ Passed The description explains the cause, fix, scope, regression tests, verification results, and linked issue in sufficient detail.

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the claude label Aug 7, 2026
@robobun

robobun commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

The automated review was rate limited and can run once the limit resets. The diff is ready for review in the meantime.

@chrisbbreuer

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — targeted encoding fix with matching regression tests.

Checked that String::clone_utf8 keeps the Latin-1 fast path for all-ASCII input (BunString__fromBytes auto-detects), so no perf change for typical bundler output. Both changed sites are the parallel sync/async already_bundled branches, and neighboring raw-source paths in jsc_hooks.rs already use clone_utf8. The ensure_hash() call after the async-path change still runs; the PR description's bytecode-sidecar analysis (ASCII-only under --target=bun, silent reparse otherwise) checks out. Tests cover import, require, and entry-point, and mix Latin-1-range and BMP code points.

Extended reasoning...

Overview

Two one-token changes swapping String::clone_latin1 for String::clone_utf8 in the // @bun pragma fast path — once in the async worker-thread transpiler store (src/jsc/RuntimeTranspilerStore.rs) and once in the sync module-loader path (src/runtime/jsc_hooks.rs). Three regression tests added to the existing // @bun describe block in test/bundler/transpiler/runtime-transpiler.test.ts.

Security risks

None. This is a source-decoding fix; no user-controlled sizing, no new allocations, no FFI surface change. clone_utf8 and clone_latin1 have identical ownership semantics (fresh WTF::StringImpl, refcount 1, owned by OwnedResolvedSource), so no lifetime or refcount balance changes.

Level of scrutiny

Low-to-medium. The diff is two identifiers plus tests. I verified clone_utf8 in src/bun_core/string/mod.rs — it calls BunString__fromBytes, which auto-detects all-ASCII and takes the Latin-1 fast path, so the common case (bundler output with escaped non-ASCII) is byte-identical to before, and ensure_hash() on the async path produces the same hash. The neighboring disable_transpilying, JSON, and bun:main branches in jsc_hooks.rs already use clone_utf8 for raw file bytes, so this brings the pragma path in line with established convention.

Other factors

Tests follow repo conventions: tempDir with using, bunEnv/bunExe, concurrent pipe draining, stderr/stdout asserted before exit code, placed in the existing test file next to related coverage, issue link in a comment. The test string deliberately mixes Latin-1-range code points (U+00B7, U+00E4) with BMP-only ones (U+2014, U+2713), which distinguishes the two decoders. The PR description explicitly covers the bytecode-sidecar hash-mismatch edge case and both sibling code paths were fixed together.

The transpiler-cache-hit path in transpile_source_code_inner returns
early and skipped the is_main -> has_loaded = true write that the full
parse path performs. With has_loaded still false, a later import of a
file with an unknown extension fell into the potentially-the-main-module
fallback and was parsed as tsx instead of using the file loader. So
running an entry point larger than the 4 KiB cache minimum that imports
an asset like ./template.hbs succeeded on the first run and failed with
a JSX parse error on every run after.

Debug builds discard restored cache entries, which is why this only
reproduced in release builds (or with
BUN_DEBUG_ENABLE_RESTORE_FROM_TRANSPILER_CACHE=1, as the test uses).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/cli/run/transpiler-cache.test.ts`:
- Around line 92-93: Extend the second-run assertion in the transpiler cache
test after the bunRun call to verify the cache state using the existing
cache-state assertion used by the test harness. Keep the successful "string"
output check, and assert that the cache still contains the expected single entry
so the test proves restoration rather than only execution success.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 48303540-47c8-47ba-b313-e138014e0dd3

📥 Commits

Reviewing files that changed from the base of the PR and between f2c971a and 8246d41.

📒 Files selected for processing (2)
  • src/runtime/jsc_hooks.rs
  • test/cli/run/transpiler-cache.test.ts

Comment thread test/cli/run/transpiler-cache.test.ts
Comment thread src/runtime/jsc_hooks.rs
The already-bundled early return has the same problem as the
transpiler-cache-hit return one branch below: a // @Bun entry point
returned without setting has_loaded, so importing a file with an
unknown extension from it was parsed as tsx via the
potentially-the-main-module fallback instead of using the file loader.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the follow-up commit addressed the sibling already_bundled early return I flagged, and the bug hunt found nothing further.

What was reviewed:

  • clone_latin1clone_utf8 at both already-bundled sites; BunString__fromBytes keeps the ASCII → Latin-1 fast path, so no regression for pure-ASCII sources.
  • Both new has_loaded = true guards match the existing full-print-path pattern at jsc_hooks.rs:3114; checked the remaining early returns (empty .cjs/.cts, ExportsObject) — neither can carry unknown-extension imports.
  • Bytecode sidecar hash concern: --bytecode forces --target=bun (ASCII-escaping printer), and a mismatch falls back to parsing rather than crashing.
  • New tests follow harness conventions (tempDir, concurrent pipe drain, stderr/stdout before exitCode) and the cache test asserts newCacheCount() == 0 on the hit run.
Extended reasoning...

Overview

Two one-token encoding fixes (String::clone_latin1String::clone_utf8) on the // @bun already-bundled fast path in src/jsc/RuntimeTranspilerStore.rs (async worker path) and src/runtime/jsc_hooks.rs (sync path), plus two copies of the existing if is_main { has_loaded = true } guard placed before the already-bundled and transpiler-cache-hit early returns in transpile_source_code_inner. Five new tests across test/bundler/transpiler/runtime-transpiler.test.ts and test/cli/run/transpiler-cache.test.ts.

Security risks

None. This is a source-decoding correctness change; clone_utf8 calls BunString__fromBytes, which auto-detects all-ASCII → Latin-1 and otherwise does a real UTF-8 decode via WTF, so behavior for ASCII sources is byte-identical and non-ASCII sources now decode correctly instead of being mojibake'd. No user-controlled input reaches a new sink.

Level of scrutiny

Medium — module loader hot path, but the changes are mechanical: a helper swap to the one already used everywhere else for source bytes (the cache-hit branch at line 2884 already used clone_utf8), and a guard that copies the existing pattern at line 3114 verbatim. I audited the other early returns in transpile_source_code_inner between the already-bundled branch and the full-print path: the empty-.cjs/.cts return (line 2813) has no imports, and the ExportsObject return (line 2760) is for non-JS loaders — neither can trigger the Loader::Tsx fallback, so the bug class is now covered.

Other factors

My previous review flagged that the already_bundled return itself was missing the same has_loaded guard added to the cache-hit branch; commit 89b7b73 addressed that with a test. CodeRabbit's request to assert newCacheCount() == 0 after the cache-hit run was addressed in 1cf6eba. All inline threads are resolved. The PR body's evidence gate shows all five new tests fail on main and pass on the PR under both ASAN-debug and release. Tests follow repo harness conventions (tempDir, bunEnv, concurrent stdout/stderr/exited drain, output asserted before exit code, Buffer.alloc(n, fill) for padding).

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the follow-up commit applied the has_loaded guard to the already_bundled return with a matching test, so both early returns now mirror the full-print path.

What was reviewed:

  • clone_latin1clone_utf8 at both pragma sites; BunString__fromBytes keeps the Latin-1 fast path for all-ASCII input, so no regression there.
  • The two new is_main → has_loaded = true writes match the existing guard at jsc_hooks.rs:3114 and the loader-fallback read at :4464.
  • Bytecode-sidecar hash concern: --target=bun escapes to ASCII, so the SourceCodeKey is unchanged for real bundler output (per description; falls back to parsing otherwise).
  • Tests cover import/require/entry-point for UTF-8 and both has_loaded early returns; CodeRabbit's cache-count assertion was added.
Extended reasoning...

Overview

Four files changed. The core fix is two String::clone_latin1String::clone_utf8 swaps on the // @bun pragma fast path — one in the sync loader (src/runtime/jsc_hooks.rs:2798) and one in the async transpiler store (src/jsc/RuntimeTranspilerStore.rs:997). clone_utf8 calls BunString__fromBytes, which auto-detects all-ASCII and stays Latin-1, so the fast path is preserved and only non-ASCII sources take the UTF-8 decode. Two additional if is_main { (*jsc_vm).has_loaded = true } writes were added before the already-bundled and transpiler-cache-hit early returns, copying the identical pattern from the full-print path at line 3114. Five tests were added across two existing test files.

Security risks

None. No user-controlled paths, no auth/crypto/permissions. The encoding change decodes source bytes JSC will parse anyway; a malformed UTF-8 sequence would produce replacement characters, not a memory-safety issue (BunString__fromBytes is the same helper the non-pragma path already uses on the same bytes).

Level of scrutiny

Moderate — module loading is a hot, correctness-critical path, but the changes are mechanical: two encoding-helper swaps to an existing helper with the right semantics, and two field writes copying an existing in-scope pattern byte-for-byte. is_main is computed at line 2171 and jsc_vm is already dereferenced under the same SAFETY contract throughout the function. I checked the two other early returns between 2760 and 3114 (ExportsObject at 2760, empty .cjs/.cts at 2810): neither can carry imports from the entry point, so they don't need the guard.

Other factors

My prior review found the missing guard on the already-bundled return; commit 89b7b73 addressed it with a test in runtime-transpiler.test.ts. CodeRabbit's request for a post-second-run newCacheCount() assertion was applied in 1cf6eba. All review threads are resolved. The PR body's mechgate output shows the new tests failing on both debug+ASAN and release builds without the fix (5 fail / 1 fail respectively) and passing with it. Tests follow harness conventions (tempDir, bunEnv, concurrent pipe drain, exit code asserted last, Buffer.alloc(n, fill) for padding).

@robobun

robobun commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

CI status: the diff is green. Across builds 90322 and 90360, every lane that runs the new tests passed (194-195 of 196 jobs). The remaining failures are unrelated to this change:

  • build 90360: the x64-android build lane timed out downloading the Rust nightly manifest from static.rust-lang.org (network infra), and the Windows 2019 x64 lane hit a segfault in test/bake/deinitialization.test.ts (dev server teardown, not touched by this diff; reported separately).
  • build 90322: a worker-terminate exception-scope assertion in test/js/node/test/parallel/test-worker-message-port-transfer-terminate.js on the ASAN lane (also reported separately).

One retrigger was already spent. Ready for review and merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

// @bun pragma makes non-ASCII string literals decode as latin-1 at runtime

2 participants