From 0733b5382cff539e7d8d9a0c3f90762c38c8d1c9 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Fri, 4 Sep 2026 21:09:49 +0200 Subject: [PATCH] runtime: support Bun 1.4 (#1400) --- .github/workflows/bench-native-smoke.yml | 2 +- .github/workflows/build-core.yml | 41 ++++++- .github/workflows/build-examples.yml | 2 +- .github/workflows/build-keymap.yml | 4 +- .github/workflows/build-native.yml | 2 +- .github/workflows/build-qrcode.yml | 2 +- .github/workflows/build-react.yml | 2 +- .github/workflows/build-solid.yml | 6 +- .github/workflows/build-ssh.yml | 4 +- .github/workflows/deploy.yml | 2 +- .github/workflows/format-lint.yml | 4 +- .github/workflows/format.yml | 2 +- .github/workflows/npm-latest-release.yml | 2 +- .github/workflows/pkg-pr-new.yml | 2 +- .github/workflows/review.yml | 2 +- AGENTS.md | 11 +- README.md | 2 +- bun.lock | 74 ++----------- package.json | 1 + packages/core/README.md | 2 + packages/core/package.json | 5 +- packages/core/scripts/build.ts | 1 + packages/core/scripts/dist-test.ts | 7 +- packages/core/src/renderer.ts | 13 ++- .../ffi-borrowed-pointer-callsites.test.ts | 104 ++++++++++-------- packages/core/src/zig.ts | 97 ++++++++-------- packages/examples/package.json | 2 +- packages/keymap/package.json | 2 +- packages/qrcode/package.json | 2 +- packages/react/package.json | 2 +- packages/solid/package.json | 2 +- packages/ssh/README.md | 6 +- packages/ssh/package.json | 2 +- packages/three/package.json | 2 +- packages/three/scripts/build.ts | 1 + packages/web/package.json | 2 +- .../src/content/docs/core-concepts/audio.mdx | 2 +- .../docs/getting-started/runtime-support.mdx | 2 + .../src/content/docs/reference/api-index.mdx | 3 +- .../web/src/content/docs/reference/ssh.mdx | 3 +- .../web/src/content/docs/reference/three.mdx | 10 +- .../web/src/content/packages/opentui-ssh.mdx | 2 +- 42 files changed, 236 insertions(+), 205 deletions(-) diff --git a/.github/workflows/bench-native-smoke.yml b/.github/workflows/bench-native-smoke.yml index d931e931e..e1559e7fb 100644 --- a/.github/workflows/bench-native-smoke.yml +++ b/.github/workflows/bench-native-smoke.yml @@ -13,7 +13,7 @@ concurrency: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - BUN_VERSION: 1.3.14 + BUN_VERSION: 1.4.1 ZIG_VERSION: 0.16.0 jobs: diff --git a/.github/workflows/build-core.yml b/.github/workflows/build-core.yml index f567f1401..af51695b6 100644 --- a/.github/workflows/build-core.yml +++ b/.github/workflows/build-core.yml @@ -29,7 +29,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 @@ -102,7 +102,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Setup Node uses: actions/setup-node@v6 @@ -190,16 +190,49 @@ jobs: with: name: crash-dumps-core-${{ matrix.os }}-${{ github.run_attempt }} + bun-1-compat: + name: Bun 1.3 compatibility + needs: build + runs-on: blacksmith-8vcpu-ubuntu-2404 + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup development Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.4.1 + + - name: Install dependencies + run: bun install + + - name: Download native artifacts + uses: actions/download-artifact@v8 + with: + name: native-libraries + path: packages/core/node_modules/@opentui/ + + - name: Setup runtime Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.14 + + - name: Test portable FFI arguments + working-directory: packages/core + run: | + test "$(bun --version)" = "1.3.14" + bun test src/tests/ffi-borrowed-pointer-callsites.test.ts + # Gate job for branch protection build-complete: name: Core - Build and Test - needs: [test] + needs: [test, bun-1-compat] runs-on: blacksmith-4vcpu-ubuntu-2404 if: always() steps: - name: Check test results run: | - if [ "${{ needs.test.result }}" != "success" ]; then + if [ "${{ needs.test.result }}" != "success" ] || [ "${{ needs['bun-1-compat'].result }}" != "success" ]; then echo "Tests failed" exit 1 fi diff --git a/.github/workflows/build-examples.yml b/.github/workflows/build-examples.yml index 6f10f50f9..c1d75ec56 100644 --- a/.github/workflows/build-examples.yml +++ b/.github/workflows/build-examples.yml @@ -23,7 +23,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Install dependencies FIRST (before copying built packages) run: | diff --git a/.github/workflows/build-keymap.yml b/.github/workflows/build-keymap.yml index 25b355603..99d778b1c 100644 --- a/.github/workflows/build-keymap.yml +++ b/.github/workflows/build-keymap.yml @@ -35,7 +35,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 @@ -99,7 +99,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index 11eef85af..9d273db1a 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -32,7 +32,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 diff --git a/.github/workflows/build-qrcode.yml b/.github/workflows/build-qrcode.yml index 65441c8c6..bdd69a97a 100644 --- a/.github/workflows/build-qrcode.yml +++ b/.github/workflows/build-qrcode.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 diff --git a/.github/workflows/build-react.yml b/.github/workflows/build-react.yml index a099465c7..fd1eb3eba 100644 --- a/.github/workflows/build-react.yml +++ b/.github/workflows/build-react.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 diff --git a/.github/workflows/build-solid.yml b/.github/workflows/build-solid.yml index 0a1c044cb..efed7099c 100644 --- a/.github/workflows/build-solid.yml +++ b/.github/workflows/build-solid.yml @@ -35,7 +35,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 @@ -98,7 +98,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 @@ -146,7 +146,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 diff --git a/.github/workflows/build-ssh.yml b/.github/workflows/build-ssh.yml index eb89bbe0f..dd7b422bc 100644 --- a/.github/workflows/build-ssh.yml +++ b/.github/workflows/build-ssh.yml @@ -35,7 +35,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 @@ -101,7 +101,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Setup Node uses: actions/setup-node@v6 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2ceba7e22..6f3cab5f6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -29,7 +29,7 @@ jobs: OPENTUI_INDEX_DIR: ${{ github.workspace }}/opentui-index/packages with: path: ./packages/web - package-manager: bun@1.3.14 + package-manager: bun@1.4.1 node-version: 26 deploy: diff --git a/.github/workflows/format-lint.yml b/.github/workflows/format-lint.yml index d2c8415cf..6b4b84046 100644 --- a/.github/workflows/format-lint.yml +++ b/.github/workflows/format-lint.yml @@ -23,7 +23,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Install dependencies run: bun install @@ -49,7 +49,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Install dependencies run: bun install diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 86e87b31c..4d0e99ee7 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -81,7 +81,7 @@ jobs: if: steps.run-mode.outputs.run == 'true' uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Install dependencies if: steps.run-mode.outputs.run == 'true' diff --git a/.github/workflows/npm-latest-release.yml b/.github/workflows/npm-latest-release.yml index 888b47301..2c627cdcf 100644 --- a/.github/workflows/npm-latest-release.yml +++ b/.github/workflows/npm-latest-release.yml @@ -47,7 +47,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Prepare release versions if: ${{ inputs.prepareVersion != '' }} diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml index c5ff5cc7a..3438e15ff 100644 --- a/.github/workflows/pkg-pr-new.yml +++ b/.github/workflows/pkg-pr-new.yml @@ -28,7 +28,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Setup Zig uses: mlugg/setup-zig@v2 diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 5e3c50a92..cb54cb471 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -45,7 +45,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.14 + bun-version: 1.4.1 - name: Install opencode run: curl -fsSL https://opencode.ai/install | bash diff --git a/AGENTS.md b/AGENTS.md index 1127ce7f0..ae2d8f692 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,11 +41,14 @@ terminal or platform is unavailable locally. `u32`/`u64`, not backend-only ABI names such as `usize`, `napi_env`, or `napi_value`; represent `i64`/`u64` as `bigint`, native booleans as `0`/`1`, and shared pointers as `number | bigint`. - Default to `buffer` for transient, non-null `TypedArray` parameters and pass the view directly. Do not call `ptr()`. -- Use `ptr` only when a parameter can be null, accepts a numeric native address, is a callback, or receives a raw - `ArrayBuffer`. Pass transient owner objects directly to `ptr` parameters; do not pre-resolve them. -- On Bun 1.3.14, `DataView` is not accepted by `buffer` or `ptr`. You MUST pass an equivalent typed array such as +- For a raw `ArrayBuffer`, create one `Uint8Array` view over it, keep that view, and declare `buffer`. A `buffer` call + costs less than a `ptr` call. `buffer` rejects an argument that is not a view with a `TypeError`. `ptr` sends a bad + argument to native code as an address. +- Use `ptr` only when a parameter can be null, accepts a numeric native address, or is a callback. Pass transient owner + objects directly to `ptr` parameters. Do not pre-resolve them. +- Bun 1.3 does not accept `DataView` directly for `buffer` or `ptr` parameters. Pass an equivalent typed array such as `new Uint8Array(view.buffer, view.byteOffset, view.byteLength)`. -- On Bun 1.4+, a non-null `DataView` MUST use `buffer` and be passed directly. +- On Bun 1.4+, use `buffer` when you pass a `DataView` directly to FFI. - Use `ptr(view)` only when native code stores the address beyond the call. Before resolving it, access `view.buffer` to move any inline typed-array storage into a stable `ArrayBuffer`, then keep the view alive for the complete native lifetime. The order is required: `const owner = view.buffer; const address = ptr(view)`. Calling `ptr(view)` first and diff --git a/README.md b/README.md index ea15a9918..6f2523a1a 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Add `-g` to install the skill globally. ## Development -Development requires Bun 1.3.0 or later and Zig 0.16.0. +Development requires Bun 1.4.1 or later and Zig 0.16.0. See [AGENTS.md](AGENTS.md) for engineering, tooling, and verification conventions. diff --git a/bun.lock b/bun.lock index 5acc05a66..7552a5a92 100644 --- a/bun.lock +++ b/bun.lock @@ -21,7 +21,7 @@ }, "devDependencies": { "@opentui/keymap": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "commander": "^13.1.0", "typescript": "^5", @@ -57,7 +57,7 @@ "web-tree-sitter": "0.25.10", }, "devDependencies": { - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "@types/three": "0.177.0", "typescript": "^5", @@ -72,7 +72,7 @@ "devDependencies": { "@opentui/react": "workspace:*", "@opentui/solid": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "@types/react": "^19.2.0", "react": ">=19.2.0", @@ -105,7 +105,7 @@ "devDependencies": { "@opentui/react": "workspace:*", "@opentui/solid": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "typescript": "^5", }, @@ -127,7 +127,7 @@ }, "devDependencies": { "@opentui/keymap": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "@types/react": "^19.2.0", "@types/react-reconciler": "^0.33.0", @@ -162,7 +162,7 @@ "devDependencies": { "@opentui/keymap": "workspace:*", "@types/babel__core": "7.20.5", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "solid-js": "1.9.12", "typescript": "^5", @@ -190,7 +190,7 @@ "@opentui/core": "workspace:*", "@opentui/react": "workspace:*", "@opentui/solid": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "@types/react": "^19.2.0", "@types/ssh2": "^1.15.0", @@ -211,7 +211,7 @@ "three": "0.177.0", }, "devDependencies": { - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "@types/three": "0.177.0", "commander": "^13.1.0", @@ -235,7 +235,7 @@ }, "devDependencies": { "@opentui/qrcode": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@xterm/addon-unicode11": "^0.8.0", "@xterm/headless": "^5.5.0", "typescript": "^5", @@ -677,56 +677,6 @@ "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.2", "", { "os": "android", "cpu": "arm" }, "sha512-21J6xzayjy3O6NdnlO6aXi/urvSRjm6nCI6+nF6ra2YofKruGixN9kfT+dt55HVNwfDmpDHJcaS3JuP/boNnlA=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.2", "", { "os": "android", "cpu": "arm64" }, "sha512-eXBg7ibkNUZ+sTwbFiDKou0BAckeV6kIigK7y5Ko4mB/5A1KLhuzEKovsmfvsL8mQorkoincMFGnQuIT92SKqA=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UCbaTklREjrc5U47ypLulAgg4njaqfOVLU18VrCrI+6E5MQjuG0lSWaqLlAJwsD7NpFV249XgB0Bi37Zh5Sz4g=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-dP67MA0cCMHFT2g5XyjtpVOtp7y4UyUxN3dhLdt11at5cPKnSm4lY+EhwNvDXIMzAMIo2KU+mc9wxaAQJTn7sQ=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-WDUPLUwfYV9G1yxNRJdXcvISW15mpvod1Wv3ok+Ws93w1HjIVmCIFxsG2DquO+3usMNCpJQ0wqO+3GhFdl6Fow=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Ng95wtHVEulRwn7R0tMrlUuiLVL/HXA8Lt/MYVpy88+s5ikpntzZba1qEulTuPnPIZuOPcW9wNEiqvZxZmgmqQ=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.2", "", { "os": "linux", "cpu": "arm" }, "sha512-AEXMESUDWWGqD6LwO/HkqCZgUE1VCJ1OhbvYGsfqX2Y6w5quSXuyoy/Fg3nRqiwro+cJYFxiw5v4kB2ZDLhxrw=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.2", "", { "os": "linux", "cpu": "arm" }, "sha512-ZV7EljjBDwBBBSv570VWj0hiNTdHt9uGznDtznBB4Caj3ch5rgD4I2K1GQrtbvJ/QiB+663lLgOdcADMNVC29Q=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-uvjwc8NtQVPAJtq4Tt7Q49FOodjfbf6NpqXyW/rjXoV+iZ3EJAHLNAnKT5UJBc6ffQVgmXTUL2ifYiLABlGFqA=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-s3KoWVNnye9mm/2WpOZ3JeUiediUVw6AvY/H7jNA6qgKA2V2aM25lMkVarTDfiicn/DLq3O0a81jncXszoyCFA=="], - - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-gi21faacK+J8aVSyAUptML9VQN26JRxe484IbF+h3hpG+sNVoMXPduhREz2CcYr5my0NE3MjVvQ5bMKX71pfVA=="], - - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-qSlWiXnVaS/ceqXNfnoFZh4IiCA0EwvCivivTGbEu1qv2o+WTHpn1zNmCTAoOG5QaVr2/yhCoLScQtc/7RxshA=="], - - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-rPyuLFNoF1B0+wolH277E780NUKf+KoEDb3OyoLbAO18BbeKi++YN6gC/zuJoPPDlQRL3fIxHxCxVEWiem2yXw=="], - - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-g+0ZLMook31iWV4PvqKU0i9E78gaZgYpSrYPed/4Bu+nGTgfOPtfs1h11tSSRPXSjC5EzLTjV/1A7L2Vr8pJoQ=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-i+sGeRGsjKZcQRh3BRfpLsM3LX3bi4AoEVqmGDyc50L6KfYsN45wVCSz70iQMwPWr3E5opSiLOwsC9WB4/1pqg=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-C1vLcKc4MfFV6I0aWsC7B2Y9QcsiEcvKkfxprwkPfLaN8hQf0/fKHwSF2lcYzA9g4imqnhic729VB9Fo70HO3Q=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-68gHUK/howpQjh7g7hlD9DvTTt4sNLp1Bb+Yzw2Ki0xvscm2cOdCLZNJNhd2jW8lsTPrHAHuF751BygifW4bkQ=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.2", "", { "os": "linux", "cpu": "x64" }, "sha512-1e30XAuaBP1MAizaOBApsgeGZge2/Byd6wV4a8oa6jPdHELbRHBiw7wvo4dp7Ie2PE8TZT4pj9RLGZv9N4qwlw=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4BJucJBGbuGnH6q7kpPqGJGzZnYrpAzRd60HQSt3OpX/6/YVgSsJnNzR8Ot74io50SeVT4CtCWe/RYIAymFPwA=="], - - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cT2MmXySMo58ENv8p6/O6wI/h/gLnD3D6JoajwXFZH6X9jz4hARqUhWpGuQhOgLNXscfZYRQMJvZDtWNzMAIDw=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sZnyUgGkuzIXaK3jNMPmUIyJrxu/PjmATQrocpGA1WbCPX8H5tfGgRSuYtqBYAvLuIGp8SPRb1O4d1Fkb5fXaQ=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-sDpFbenhmWjNcEbBcoTV0PWvW5rPJFvu+P7XoTY0YLGRupgLbFY0XPfwIbJOObzO7QgkRDANh65RjhPmgSaAjQ=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-GvJ03TqqaweWCigtKQVBErw2bEhu1tyfNQbarwr94wCGnczA9HF8wqEe3U/Lfu6EdeNP0p6R+APeHVwEqVxpUQ=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.2", "", { "os": "win32", "cpu": "x64" }, "sha512-KvXsBvp13oZz9JGe5NYS7FNizLe99Ny+W8ETsuCyjXiKdiGrcz2/J/N8qxZ/RSwivqjQguug07NLHqrIHrqfYw=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.2", "", { "os": "win32", "cpu": "x64" }, "sha512-xNO+fksQhsAckRtDSPWaMeT1uIM+JrDRXlerpnWNXhn1TdB3YZ6uKBMBTKP0eX9XtYEP978hHk1f8332i2AW8Q=="], - "@shikijs/core": ["@shikijs/core@4.2.0", "", { "dependencies": { "@shikijs/primitive": "4.2.0", "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ=="], "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.2.0", "", { "dependencies": { "@shikijs/types": "4.2.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og=="], @@ -759,7 +709,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + "@types/bun": ["@types/bun@1.4.1", "", { "dependencies": { "bun-types": "1.4.1" } }, "sha512-0AVGiTXGajf1rgKom3N+c5L7CBxuoyyv1i44M0nX4UDK0G/fnRAMiri93nHuVPIb429KKtAgj7HatVmmOjeQLA=="], "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], @@ -863,7 +813,7 @@ "bun-ffi-structs": ["bun-ffi-structs@0.3.1", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-3gM7PpVWLyrwxWjcilSiGuhWanhZivvo6l0u573NziPH6f/gwk6McbaYgn7oJWov6pKGRTDbrg94W5DcJsKTtQ=="], - "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "bun-types": ["bun-types@1.4.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-loKuVrAFZKfEv+JvWkHRS9GW5IqLuLRjVXN9p+vZvBN86O5hf/pBZQ5hSoyipsrMmWObZBDvWnlmKvjKTM0PdA=="], "bun-webgpu": ["bun-webgpu@0.1.7", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.7", "bun-webgpu-darwin-x64": "^0.1.7", "bun-webgpu-linux-x64": "^0.1.7", "bun-webgpu-win32-x64": "^0.1.7" } }, "sha512-KUxUp+oQIf7pPBMD4Hv1TUu7DWaOZ4ciKulTk9to9+Uc8yHoYrMW7L2SJCJ4FHHkywgf/7aLRgRx0b7i6DvGIQ=="], @@ -1405,8 +1355,6 @@ "rolldown": ["rolldown@1.2.3", "", { "dependencies": { "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.3", "@rolldown/binding-darwin-arm64": "1.2.3", "@rolldown/binding-darwin-x64": "1.2.3", "@rolldown/binding-freebsd-x64": "1.2.3", "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", "@rolldown/binding-linux-arm64-gnu": "1.2.3", "@rolldown/binding-linux-arm64-musl": "1.2.3", "@rolldown/binding-linux-ppc64-gnu": "1.2.3", "@rolldown/binding-linux-s390x-gnu": "1.2.3", "@rolldown/binding-linux-x64-gnu": "1.2.3", "@rolldown/binding-linux-x64-musl": "1.2.3", "@rolldown/binding-openharmony-arm64": "1.2.3", "@rolldown/binding-win32-arm64-msvc": "1.2.3", "@rolldown/binding-win32-x64-msvc": "1.2.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A=="], - "rollup": ["rollup@4.55.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.2", "@rollup/rollup-android-arm64": "4.55.2", "@rollup/rollup-darwin-arm64": "4.55.2", "@rollup/rollup-darwin-x64": "4.55.2", "@rollup/rollup-freebsd-arm64": "4.55.2", "@rollup/rollup-freebsd-x64": "4.55.2", "@rollup/rollup-linux-arm-gnueabihf": "4.55.2", "@rollup/rollup-linux-arm-musleabihf": "4.55.2", "@rollup/rollup-linux-arm64-gnu": "4.55.2", "@rollup/rollup-linux-arm64-musl": "4.55.2", "@rollup/rollup-linux-loong64-gnu": "4.55.2", "@rollup/rollup-linux-loong64-musl": "4.55.2", "@rollup/rollup-linux-ppc64-gnu": "4.55.2", "@rollup/rollup-linux-ppc64-musl": "4.55.2", "@rollup/rollup-linux-riscv64-gnu": "4.55.2", "@rollup/rollup-linux-riscv64-musl": "4.55.2", "@rollup/rollup-linux-s390x-gnu": "4.55.2", "@rollup/rollup-linux-x64-gnu": "4.55.2", "@rollup/rollup-linux-x64-musl": "4.55.2", "@rollup/rollup-openbsd-x64": "4.55.2", "@rollup/rollup-openharmony-arm64": "4.55.2", "@rollup/rollup-win32-arm64-msvc": "4.55.2", "@rollup/rollup-win32-ia32-msvc": "4.55.2", "@rollup/rollup-win32-x64-gnu": "4.55.2", "@rollup/rollup-win32-x64-msvc": "4.55.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-PggGy4dhwx5qaW+CKBilA/98Ql9keyfnb7lh4SR6shQ91QQQi1ORJ1v4UinkdP2i87OBs9AQFooQylcrrRfIcg=="], - "s-js": ["s-js@0.4.9", "", {}, "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], diff --git a/package.json b/package.json index 1328dd026..2090acd70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "@opentui", "private": true, + "packageManager": "bun@1.4.1", "workspaces": [ "packages/*", "packages/solid/examples" diff --git a/packages/core/README.md b/packages/core/README.md index 67bd636c1..3054c59a3 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -52,6 +52,8 @@ Then build your first app with the [quickstart](https://opentui.com/docs/getting `@opentui/core` runs on Bun 1.3.0 or later, or on Node.js 26.4.0 or later with ECMAScript modules (ESM) and `--experimental-ffi`. +Use Bun 1.4.0 or later on native Windows arm64. + The native ABI and the generated platform packages, such as `@opentui/core-linux-x64`, are internal distribution surfaces, not application APIs. diff --git a/packages/core/package.json b/packages/core/package.json index 167908e06..ff70d7eb4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,7 +58,7 @@ "license": "MIT", "devDependencies": { "@opentui/keymap": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "commander": "^13.1.0", "typescript": "^5", @@ -129,6 +129,7 @@ } }, "engines": { - "bun": ">=1.3.0" + "bun": ">=1.3.0", + "node": ">=26.4.0" } } diff --git a/packages/core/scripts/build.ts b/packages/core/scripts/build.ts index 6050491f0..5477d0772 100644 --- a/packages/core/scripts/build.ts +++ b/packages/core/scripts/build.ts @@ -585,6 +585,7 @@ if (buildLib) { homepage: packageJson.homepage, repository: packageJson.repository, bugs: packageJson.bugs, + engines: packageJson.engines, exports, dependencies: packageJson.dependencies, peerDependencies: packageJson.peerDependencies, diff --git a/packages/core/scripts/dist-test.ts b/packages/core/scripts/dist-test.ts index 517d28579..79ff7be7d 100644 --- a/packages/core/scripts/dist-test.ts +++ b/packages/core/scripts/dist-test.ts @@ -481,7 +481,12 @@ function assertNodeStaticImportFailure( } function installAndTest(nodeDir: string, bunDir: string): void { - runCommand("npm", ["install", "--ignore-scripts", "--no-package-lock"], nodeDir, "Node dist test install failed") + runCommand( + "npm", + ["install", "--engine-strict", "--ignore-scripts", "--no-package-lock"], + nodeDir, + "Node dist test install failed", + ) runCommand(nodePath, ["-e", `import(${JSON.stringify(packageJson.name)})`], nodeDir, "Node import smoke check failed") runCommand(nodePath, ["--experimental-ffi", "--no-warnings", "index.mjs"], nodeDir, "Node dist smoke tests failed", { timeout: 60_000, diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts index be52c1b09..912c119e7 100644 --- a/packages/core/src/renderer.ts +++ b/packages/core/src/renderer.ts @@ -1352,8 +1352,9 @@ export class CliRenderer extends EventEmitter implements RenderContext { private removeExitListeners(): void { if (!this._exitListenersAdded || this.exitSignals.length === 0) return + const processEvents = process as EventEmitter this.exitSignals.forEach((signal) => { - process.removeListener(signal, this.exitHandler) + processEvents.removeListener(signal, this.exitHandler) }) this._exitListenersAdded = false @@ -4388,12 +4389,14 @@ export class CliRenderer extends EventEmitter implements RenderContext { this.lib.cancelKittyImageTransport(this.rendererPtr, false) } + // Bun 1.4's types narrow process.removeListener to its memoryPressure overload. + const processEvents = process as EventEmitter if (this._usesProcessStdout) { - process.removeListener("SIGWINCH", this.sigwinchHandler) + processEvents.removeListener("SIGWINCH", this.sigwinchHandler) } - process.removeListener("uncaughtException", this.handleError) - process.removeListener("unhandledRejection", this.handleError) - process.removeListener("warning", this.warningHandler) + processEvents.removeListener("uncaughtException", this.handleError) + processEvents.removeListener("unhandledRejection", this.handleError) + processEvents.removeListener("warning", this.warningHandler) this.removeExitListeners() if (this.resizeTimeoutId !== null) { diff --git a/packages/core/src/tests/ffi-borrowed-pointer-callsites.test.ts b/packages/core/src/tests/ffi-borrowed-pointer-callsites.test.ts index 232ad28c8..11f736b55 100644 --- a/packages/core/src/tests/ffi-borrowed-pointer-callsites.test.ts +++ b/packages/core/src/tests/ffi-borrowed-pointer-callsites.test.ts @@ -68,6 +68,10 @@ function fieldOffset(struct: { layoutByName: Map }, return field.offset } +function dataView(bytes: Uint8Array): DataView { + return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) +} + function readPackedColor(packed: ArrayBuffer, offset: number): number[] { // 64-bit StyledChunk/CursorStyleOptions layout; matches the supported // x64/arm64 native targets. @@ -77,6 +81,15 @@ function readPackedColor(packed: ArrayBuffer, offset: number): number[] { } describe("borrowed pointer call sites", () => { + test("passes reusable struct views through the native FFI boundary", () => { + const buffer = lib.createEditBuffer("wcwidth") + try { + expect(lib.editBufferGetCursorPosition(buffer)).toEqual({ row: 0, col: 0, offset: 0 }) + } finally { + lib.destroyEditBuffer(buffer) + } + }) + test("preserves the native viewport lookup success result", () => { const x = new Uint32Array([1]) const y = new Uint32Array([1]) @@ -180,7 +193,7 @@ describe("borrowed pointer call sites", () => { textBufferViewMeasureForDimensions: symbols.textBufferViewMeasureForDimensions, audioGetStreamStats: symbols.audioGetStreamStats, } - const outputBuffers: Record = { + const outputViews: Record = { logical: [], visual: [], measure: [], @@ -191,25 +204,25 @@ describe("borrowed pointer call sites", () => { let lineCount = 2 let bytesReceived = 20n - symbols.editBufferGetCursorPosition = (_buffer, output: ArrayBuffer) => { - outputBuffers.logical.push(output) - LogicalCursorStruct.packInto({ row: 0, col: logicalOffset, offset: logicalOffset++ }, new DataView(output), 0) + symbols.editBufferGetCursorPosition = (_buffer, output: Uint8Array) => { + outputViews.logical.push(output) + LogicalCursorStruct.packInto({ row: 0, col: logicalOffset, offset: logicalOffset++ }, dataView(output), 0) } - symbols.editorViewGetVisualCursor = (_view, output: ArrayBuffer) => { - outputBuffers.visual.push(output) + symbols.editorViewGetVisualCursor = (_view, output: Uint8Array) => { + outputViews.visual.push(output) VisualCursorStruct.packInto( { visualRow: 0, visualCol: visualOffset, logicalRow: 0, logicalCol: visualOffset, offset: visualOffset++ }, - new DataView(output), + dataView(output), 0, ) } - symbols.textBufferViewMeasureForDimensions = (_view, _width, _height, output: ArrayBuffer) => { - outputBuffers.measure.push(output) - MeasureResultStruct.packInto({ lineCount: lineCount++, widthColsMax: 8 }, new DataView(output), 0) + symbols.textBufferViewMeasureForDimensions = (_view, _width, _height, output: Uint8Array) => { + outputViews.measure.push(output) + MeasureResultStruct.packInto({ lineCount: lineCount++, widthColsMax: 8 }, dataView(output), 0) return 1 } - symbols.audioGetStreamStats = (_engine, _streamId, output: ArrayBuffer) => { - outputBuffers.audio.push(output) + symbols.audioGetStreamStats = (_engine, _streamId, output: Uint8Array) => { + outputViews.audio.push(output) AudioStreamStatsStruct.packInto( { bytesReceived: bytesReceived++, @@ -224,7 +237,7 @@ describe("borrowed pointer call sites", () => { errorCode: 0, readyGeneration: 1, }, - new DataView(output), + dataView(output), 0, ) return 0 @@ -255,17 +268,17 @@ describe("borrowed pointer call sites", () => { expect(audioFirst.bytesReceived).toBe(20n) expect(audioSecond.bytesReceived).toBe(21n) - for (const buffers of Object.values(outputBuffers)) { - expect(buffers).toHaveLength(2) - expect(buffers[0]).toBeInstanceOf(ArrayBuffer) - expect(buffers[1]).toBe(buffers[0]) + for (const views of Object.values(outputViews)) { + expect(views).toHaveLength(2) + expect(views[0]).toBeInstanceOf(Uint8Array) + expect(views[1]).toBe(views[0]) } } finally { Object.assign(symbols, originals) } }) - test("grid draw repacks and forwards one owning ArrayBuffer", () => { + test("grid draw repacks and forwards one owning Uint8Array", () => { const calls: any[][] = [] const original = symbols.bufferDrawGrid const fg = RGBA.fromValues(1, 1, 1, 1) @@ -278,16 +291,16 @@ describe("borrowed pointer call sites", () => { drawInner: true, drawOuter: false, }) - const firstBuffer = calls[0]![8] as ArrayBuffer - expect(firstBuffer).toBeInstanceOf(ArrayBuffer) - expect(new Uint8Array(firstBuffer).slice(0, GridDrawOptionsStruct.size)).toEqual(new Uint8Array([1, 0])) + const firstView = calls[0]![8] as Uint8Array + expect(firstView).toBeInstanceOf(Uint8Array) + expect(firstView).toEqual(new Uint8Array([1, 0])) lib.bufferDrawGrid(1 as any, chars, fg, bg, offsets, 1, offsets, 1, { drawInner: false, drawOuter: true, }) - expect(calls[1]![8]).toBe(firstBuffer) - expect(new Uint8Array(firstBuffer).slice(0, GridDrawOptionsStruct.size)).toEqual(new Uint8Array([0, 1])) + expect(calls[1]![8]).toBe(firstView) + expect(firstView).toEqual(new Uint8Array([0, 1])) } finally { symbols.bufferDrawGrid = original } @@ -313,41 +326,43 @@ describe("borrowed pointer call sites", () => { try { for (const [index, name] of logicalQueries.entries()) { originals.set(name, symbols[name]) - const outputBuffers: ArrayBuffer[] = [] + const outputViews: Uint8Array[] = [] let offset = index + 2 symbols[name] = (...args: any[]) => { - const output = args.at(-1) as ArrayBuffer - outputBuffers.push(output) - LogicalCursorStruct.packInto({ row: index, col: index + 1, offset: offset++ }, new DataView(output), 0) + const output = args.at(-1) as Uint8Array + outputViews.push(output) + LogicalCursorStruct.packInto({ row: index, col: index + 1, offset: offset++ }, dataView(output), 0) } const first = (lib as any)[name](1) const second = (lib as any)[name](1) expect(first).not.toBe(second) expect(first).toEqual({ row: index, col: index + 1, offset: index + 2 }) expect(second).toEqual({ row: index, col: index + 1, offset: index + 3 }) - expect(outputBuffers[1]).toBe(outputBuffers[0]) + expect(outputViews[0]).toBeInstanceOf(Uint8Array) + expect(outputViews[1]).toBe(outputViews[0]) } originals.set("editBufferOffsetToPosition", symbols.editBufferOffsetToPosition) - const positionBuffers: ArrayBuffer[] = [] - symbols.editBufferOffsetToPosition = (_buffer, offset: number, output: ArrayBuffer) => { - positionBuffers.push(output) - LogicalCursorStruct.packInto({ row: 1, col: 2, offset }, new DataView(output), 0) + const positionViews: Uint8Array[] = [] + symbols.editBufferOffsetToPosition = (_buffer, offset: number, output: Uint8Array) => { + positionViews.push(output) + LogicalCursorStruct.packInto({ row: 1, col: 2, offset }, dataView(output), 0) return 1 } const firstPosition = lib.editBufferOffsetToPosition(1 as any, 9) const secondPosition = lib.editBufferOffsetToPosition(1 as any, 10) expect(firstPosition).toEqual({ row: 1, col: 2, offset: 9 }) expect(secondPosition).toEqual({ row: 1, col: 2, offset: 10 }) - expect(positionBuffers[1]).toBe(positionBuffers[0]) + expect(positionViews[0]).toBeInstanceOf(Uint8Array) + expect(positionViews[1]).toBe(positionViews[0]) for (const [index, name] of visualQueries.entries()) { originals.set(name, symbols[name]) - const outputBuffers: ArrayBuffer[] = [] + const outputViews: Uint8Array[] = [] let offset = index + 4 symbols[name] = (...args: any[]) => { - const output = args.at(-1) as ArrayBuffer - outputBuffers.push(output) + const output = args.at(-1) as Uint8Array + outputViews.push(output) VisualCursorStruct.packInto( { visualRow: index, @@ -356,7 +371,7 @@ describe("borrowed pointer call sites", () => { logicalCol: index + 3, offset: offset++, }, - new DataView(output), + dataView(output), 0, ) } @@ -365,7 +380,8 @@ describe("borrowed pointer call sites", () => { expect(first).not.toBe(second) expect(first.offset).toBe(index + 4) expect(second.offset).toBe(index + 5) - expect(outputBuffers[1]).toBe(outputBuffers[0]) + expect(outputViews[0]).toBeInstanceOf(Uint8Array) + expect(outputViews[1]).toBe(outputViews[0]) } } finally { for (const [name, original] of originals) symbols[name] = original @@ -602,7 +618,7 @@ describe("borrowed pointer call sites", () => { } }) - test("audioCloseStream forwards its reason and unpacks the owned output buffer", () => { + test("audioCloseStream forwards its reason and unpacks the owned output view", () => { const calls: any[][] = [] const original = symbols.audioCloseStream symbols.audioCloseStream = (...args: any[]) => { @@ -621,7 +637,7 @@ describe("borrowed pointer call sites", () => { errorCode: -3, readyGeneration: 7, }, - new DataView(args[3]), + dataView(args[3] as Uint8Array), 0, ) return 0 @@ -632,7 +648,7 @@ describe("borrowed pointer call sites", () => { expect(calls[0]![0]).toBe(11) expect(calls[0]![1]).toBe(22) expect(calls[0]![2]).toBe(NativeAudioStreamCloseReason.TransportError) - expect(calls[0]![3]).toBeInstanceOf(ArrayBuffer) + expect(calls[0]![3]).toBeInstanceOf(Uint8Array) expect(result).toEqual({ status: 0, stats: { @@ -817,9 +833,9 @@ describe("borrowed pointer call sites", () => { lib.imageComposite(handle, handle, 0, 0, 0, 255) expect(calls.get("bufferDrawImage")![2]).toBe(firstImageOptions) - expect(firstImageOptions).toBeInstanceOf(ArrayBuffer) - expect((firstImageOptions as ArrayBuffer).byteLength).toBe(ImageDrawOptionsStruct.size) - expect(ImageDrawOptionsStruct.unpack(firstImageOptions as ArrayBuffer)).toMatchObject({ + expect(firstImageOptions).toBeInstanceOf(Uint8Array) + expect((firstImageOptions as Uint8Array).byteLength).toBe(ImageDrawOptionsStruct.size) + expect(ImageDrawOptionsStruct.unpack((firstImageOptions as Uint8Array).buffer)).toMatchObject({ x: 2, y: 3, width: 4, diff --git a/packages/core/src/zig.ts b/packages/core/src/zig.ts index ece23519e..e7bca20f3 100644 --- a/packages/core/src/zig.ts +++ b/packages/core/src/zig.ts @@ -102,6 +102,15 @@ import { isBunfsPath } from "./lib/bunfs.js" import { resolveNativeLibraryPath } from "#opentui/runtime-assets" import { allocStruct } from "bun-ffi-structs" +// Struct outputs use `buffer` instead of `ptr`. A `buffer` call is about 3x cheaper on Bun 1.3 and 2.5x cheaper on +// Bun 1.4. `buffer` rejects an argument that is not a view with a TypeError. `ptr` accepts a number as a raw address. +// `buffer` also skips the Node pointer normalizer. Bun 1.3 rejects ArrayBuffer and DataView for `buffer`, so keep one +// Uint8Array view per reusable struct. +function allocFFIStruct(structDefinition: Parameters[0]) { + const storage = allocStruct(structDefinition) + return { ...storage, ffiView: new Uint8Array(storage.buffer) } +} + registerEnvVar({ name: "OPENTUI_LIBC", description: "Select Linux native libc package. Supported values: glibc, musl.", @@ -454,11 +463,11 @@ function getOpenTUILib(libPath?: string) { returns: "i32", }, embeddedTerminalCursor: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "i32", }, embeddedTerminalEncodeKey: { - args: ["u32", "ptr", "ptr", "u32", "ptr", "u32", "ptr", "u32", "ptr"], + args: ["u32", "buffer", "ptr", "u32", "ptr", "u32", "ptr", "u32", "ptr"], returns: "i32", }, embeddedTerminalEncodeMouse: { @@ -818,7 +827,7 @@ function getOpenTUILib(libPath?: string) { returns: "void", }, bufferDrawImage: { - args: ["u32", "u32", "ptr"], + args: ["u32", "u32", "buffer"], returns: "u8", }, bufferDrawPackedBuffer: { @@ -834,7 +843,7 @@ function getOpenTUILib(libPath?: string) { returns: "void", }, bufferDrawGrid: { - args: ["u32", "buffer", "buffer", "buffer", "buffer", "u32", "buffer", "u32", "ptr"], + args: ["u32", "buffer", "buffer", "buffer", "buffer", "u32", "buffer", "u32", "buffer"], returns: "void", }, bufferDrawBox: { @@ -1207,7 +1216,7 @@ function getOpenTUILib(libPath?: string) { returns: "void", }, textBufferViewMeasureForDimensions: { - args: ["u32", "u32", "u32", "ptr"], + args: ["u32", "u32", "u32", "buffer"], returns: "bool", }, bufferDrawTextBufferView: { @@ -1359,7 +1368,7 @@ function getOpenTUILib(libPath?: string) { returns: "void", }, editBufferGetCursorPosition: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "void", }, editBufferGetId: { @@ -1403,19 +1412,19 @@ function getOpenTUILib(libPath?: string) { returns: "void", }, editBufferGetNextWordBoundary: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "void", }, editBufferGetPrevWordBoundary: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "void", }, editBufferGetEOL: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "void", }, editBufferOffsetToPosition: { - args: ["u32", "u32", "ptr"], + args: ["u32", "u32", "buffer"], returns: "bool", }, editBufferPositionToOffset: { @@ -1495,7 +1504,7 @@ function getOpenTUILib(libPath?: string) { // EditorView VisualCursor methods editorViewGetVisualCursor: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "void", }, @@ -1516,23 +1525,23 @@ function getOpenTUILib(libPath?: string) { returns: "void", }, editorViewGetNextWordBoundary: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "void", }, editorViewGetPrevWordBoundary: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "void", }, editorViewGetEOL: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "void", }, editorViewGetVisualSOL: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "void", }, editorViewGetVisualEOL: { - args: ["u32", "ptr"], + args: ["u32", "buffer"], returns: "void", }, editorViewGotoVisualLineEnd: { @@ -1958,11 +1967,11 @@ function getOpenTUILib(libPath?: string) { returns: "i32", }, audioGetStreamStats: { - args: ["u32", "u32", "ptr"], + args: ["u32", "u32", "buffer"], returns: "i32", }, audioCloseStream: { - args: ["u32", "u32", "u32", "ptr"], + args: ["u32", "u32", "u32", "buffer"], returns: "i32", }, audioLoad: { @@ -3225,11 +3234,11 @@ class FFIRenderLib implements RenderLib { private readonly yogaLayout = new Float32Array(6) private readonly ffiStructStorage = { logicalCursor: { - ...allocStruct(LogicalCursorStruct), + ...allocFFIStruct(LogicalCursorStruct), result: { row: 0, col: 0, offset: 0 } as LogicalCursor, }, visualCursor: { - ...allocStruct(VisualCursorStruct), + ...allocFFIStruct(VisualCursorStruct), result: { visualRow: 0, visualCol: 0, @@ -3239,13 +3248,13 @@ class FFIRenderLib implements RenderLib { } as VisualCursor, }, measureResult: { - ...allocStruct(MeasureResultStruct), + ...allocFFIStruct(MeasureResultStruct), result: { lineCount: 0, widthColsMax: 0 } as MeasureResult, }, - embeddedTerminalCursor: allocStruct(EmbeddedTerminalCursorStruct), - embeddedTerminalKeyOptions: allocStruct(EmbeddedTerminalKeyOptionsStruct), + embeddedTerminalCursor: allocFFIStruct(EmbeddedTerminalCursorStruct), + embeddedTerminalKeyOptions: allocFFIStruct(EmbeddedTerminalKeyOptionsStruct), audioStreamStats: { - ...allocStruct(AudioStreamStatsStruct), + ...allocFFIStruct(AudioStreamStatsStruct), result: { bytesReceived: 0n, framesDecoded: 0n, @@ -3260,8 +3269,8 @@ class FFIRenderLib implements RenderLib { readyGeneration: 0, } as NativeAudioStreamStats, }, - imageDrawOptions: allocStruct(ImageDrawOptionsStruct), - gridDrawOptions: allocStruct(GridDrawOptionsStruct), + imageDrawOptions: allocFFIStruct(ImageDrawOptionsStruct), + gridDrawOptions: allocFFIStruct(GridDrawOptionsStruct), } private disposed = false private clipboardServices = new Set() @@ -3400,7 +3409,7 @@ class FFIRenderLib implements RenderLib { public embeddedTerminalCursor(handle: EmbeddedTerminalHandle): EmbeddedTerminalCursor { const storage = this.ffiStructStorage.embeddedTerminalCursor - embeddedTerminalResult(this.opentui.symbols.embeddedTerminalCursor(handle, storage.buffer), "cursor query") + embeddedTerminalResult(this.opentui.symbols.embeddedTerminalCursor(handle, storage.ffiView), "cursor query") const result = EmbeddedTerminalCursorStruct.unpack(storage.buffer) return { x: result.x, @@ -3436,7 +3445,7 @@ class FFIRenderLib implements RenderLib { const encode = (output: Uint8Array) => this.opentui.symbols.embeddedTerminalEncodeKey( handle, - options.buffer, + options.ffiView, keyCodeLength === 0 ? null : keyCode, keyCodeLength, textLength === 0 ? null : text, @@ -4063,7 +4072,7 @@ class FFIRenderLib implements RenderLib { storage.view, 0, ) - return Boolean(this.opentui.symbols.bufferDrawImage(buffer, image, storage.buffer)) + return Boolean(this.opentui.symbols.bufferDrawImage(buffer, image, storage.ffiView)) } public bufferDrawPackedBuffer( @@ -4156,7 +4165,7 @@ class FFIRenderLib implements RenderLib { columnCount, rowOffsets, rowCount, - this.ffiStructStorage.gridDrawOptions.buffer, + this.ffiStructStorage.gridDrawOptions.ffiView, ) } @@ -5373,7 +5382,7 @@ class FFIRenderLib implements RenderLib { public textBufferViewMeasureForDimensions(view: Pointer, width: number, height: number): MeasureResult | null { const storage = this.ffiStructStorage.measureResult - const success = this.opentui.symbols.textBufferViewMeasureForDimensions(view, width, height, storage.buffer) + const success = this.opentui.symbols.textBufferViewMeasureForDimensions(view, width, height, storage.ffiView) if (!success) return null const result = MeasureResultStruct.unpackInto(storage.view, storage.result) return { lineCount: result.lineCount, widthColsMax: result.widthColsMax } @@ -5686,7 +5695,7 @@ class FFIRenderLib implements RenderLib { public editBufferGetCursorPosition(buffer: Pointer): LogicalCursor { const storage = this.ffiStructStorage.logicalCursor - this.opentui.symbols.editBufferGetCursorPosition(buffer, storage.buffer) + this.opentui.symbols.editBufferGetCursorPosition(buffer, storage.ffiView) const cursor = LogicalCursorStruct.unpackInto(storage.view, storage.result) return { row: cursor.row, col: cursor.col, offset: cursor.offset } } @@ -5745,28 +5754,28 @@ class FFIRenderLib implements RenderLib { public editBufferGetNextWordBoundary(buffer: Pointer): LogicalCursor { const storage = this.ffiStructStorage.logicalCursor - this.opentui.symbols.editBufferGetNextWordBoundary(buffer, storage.buffer) + this.opentui.symbols.editBufferGetNextWordBoundary(buffer, storage.ffiView) const cursor = LogicalCursorStruct.unpackInto(storage.view, storage.result) return { row: cursor.row, col: cursor.col, offset: cursor.offset } } public editBufferGetPrevWordBoundary(buffer: Pointer): LogicalCursor { const storage = this.ffiStructStorage.logicalCursor - this.opentui.symbols.editBufferGetPrevWordBoundary(buffer, storage.buffer) + this.opentui.symbols.editBufferGetPrevWordBoundary(buffer, storage.ffiView) const cursor = LogicalCursorStruct.unpackInto(storage.view, storage.result) return { row: cursor.row, col: cursor.col, offset: cursor.offset } } public editBufferGetEOL(buffer: Pointer): LogicalCursor { const storage = this.ffiStructStorage.logicalCursor - this.opentui.symbols.editBufferGetEOL(buffer, storage.buffer) + this.opentui.symbols.editBufferGetEOL(buffer, storage.ffiView) const cursor = LogicalCursorStruct.unpackInto(storage.view, storage.result) return { row: cursor.row, col: cursor.col, offset: cursor.offset } } public editBufferOffsetToPosition(buffer: Pointer, offset: number): LogicalCursor | null { const storage = this.ffiStructStorage.logicalCursor - const success = this.opentui.symbols.editBufferOffsetToPosition(buffer, offset, storage.buffer) + const success = this.opentui.symbols.editBufferOffsetToPosition(buffer, offset, storage.ffiView) if (!success) return null const cursor = LogicalCursorStruct.unpackInto(storage.view, storage.result) return { row: cursor.row, col: cursor.col, offset: cursor.offset } @@ -5966,7 +5975,7 @@ class FFIRenderLib implements RenderLib { public editorViewGetVisualCursor(view: Pointer): VisualCursor { const storage = this.ffiStructStorage.visualCursor - this.opentui.symbols.editorViewGetVisualCursor(view, storage.buffer) + this.opentui.symbols.editorViewGetVisualCursor(view, storage.ffiView) const cursor = VisualCursorStruct.unpackInto(storage.view, storage.result) return { ...cursor } } @@ -5989,35 +5998,35 @@ class FFIRenderLib implements RenderLib { public editorViewGetNextWordBoundary(view: Pointer): VisualCursor { const storage = this.ffiStructStorage.visualCursor - this.opentui.symbols.editorViewGetNextWordBoundary(view, storage.buffer) + this.opentui.symbols.editorViewGetNextWordBoundary(view, storage.ffiView) const cursor = VisualCursorStruct.unpackInto(storage.view, storage.result) return { ...cursor } } public editorViewGetPrevWordBoundary(view: Pointer): VisualCursor { const storage = this.ffiStructStorage.visualCursor - this.opentui.symbols.editorViewGetPrevWordBoundary(view, storage.buffer) + this.opentui.symbols.editorViewGetPrevWordBoundary(view, storage.ffiView) const cursor = VisualCursorStruct.unpackInto(storage.view, storage.result) return { ...cursor } } public editorViewGetEOL(view: Pointer): VisualCursor { const storage = this.ffiStructStorage.visualCursor - this.opentui.symbols.editorViewGetEOL(view, storage.buffer) + this.opentui.symbols.editorViewGetEOL(view, storage.ffiView) const cursor = VisualCursorStruct.unpackInto(storage.view, storage.result) return { ...cursor } } public editorViewGetVisualSOL(view: Pointer): VisualCursor { const storage = this.ffiStructStorage.visualCursor - this.opentui.symbols.editorViewGetVisualSOL(view, storage.buffer) + this.opentui.symbols.editorViewGetVisualSOL(view, storage.ffiView) const cursor = VisualCursorStruct.unpackInto(storage.view, storage.result) return { ...cursor } } public editorViewGetVisualEOL(view: Pointer): VisualCursor { const storage = this.ffiStructStorage.visualCursor - this.opentui.symbols.editorViewGetVisualEOL(view, storage.buffer) + this.opentui.symbols.editorViewGetVisualEOL(view, storage.ffiView) const cursor = VisualCursorStruct.unpackInto(storage.view, storage.result) return { ...cursor } } @@ -6371,7 +6380,7 @@ class FFIRenderLib implements RenderLib { public audioGetStreamStats(engine: AudioEngineHandle, streamId: number): NativeAudioStreamStats | null { const storage = this.ffiStructStorage.audioStreamStats - const status = this.opentui.symbols.audioGetStreamStats(engine, streamId, storage.buffer) + const status = this.opentui.symbols.audioGetStreamStats(engine, streamId, storage.ffiView) if (status !== 0) return null const stats = AudioStreamStatsStruct.unpackInto(storage.view, storage.result) as NativeAudioStreamStats return { ...stats } @@ -6383,7 +6392,7 @@ class FFIRenderLib implements RenderLib { reason: NativeAudioStreamCloseReason, ): { status: number; stats: NativeAudioStreamStats | null } { const storage = this.ffiStructStorage.audioStreamStats - const status = this.opentui.symbols.audioCloseStream(engine, streamId, reason, storage.buffer) + const status = this.opentui.symbols.audioCloseStream(engine, streamId, reason, storage.ffiView) if (status !== 0) return { status, stats: null } const stats = AudioStreamStatsStruct.unpackInto(storage.view, storage.result) as NativeAudioStreamStats return { status, stats: { ...stats } } diff --git a/packages/examples/package.json b/packages/examples/package.json index e3139ae11..ae29b8218 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -31,7 +31,7 @@ "web-tree-sitter": "0.25.10" }, "devDependencies": { - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "@types/three": "0.177.0", "typescript": "^5" diff --git a/packages/keymap/package.json b/packages/keymap/package.json index c8c89a7e2..b519c2831 100644 --- a/packages/keymap/package.json +++ b/packages/keymap/package.json @@ -90,7 +90,7 @@ "devDependencies": { "@opentui/react": "workspace:*", "@opentui/solid": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "@types/react": "^19.2.0", "react": ">=19.2.0", diff --git a/packages/qrcode/package.json b/packages/qrcode/package.json index 310766ea8..c21108868 100644 --- a/packages/qrcode/package.json +++ b/packages/qrcode/package.json @@ -49,7 +49,7 @@ "devDependencies": { "@opentui/react": "workspace:*", "@opentui/solid": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "typescript": "^5" }, diff --git a/packages/react/package.json b/packages/react/package.json index 4d7ebe62d..5fb8909a2 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -47,7 +47,7 @@ }, "devDependencies": { "@opentui/keymap": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "@types/react": "^19.2.0", "@types/react-reconciler": "^0.33.0", diff --git a/packages/solid/package.json b/packages/solid/package.json index 18d37d280..1a41983fc 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -56,7 +56,7 @@ "devDependencies": { "@opentui/keymap": "workspace:*", "@types/babel__core": "7.20.5", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "solid-js": "1.9.12", "typescript": "^5" diff --git a/packages/ssh/README.md b/packages/ssh/README.md index 790fc49e7..6f325af59 100644 --- a/packages/ssh/README.md +++ b/packages/ssh/README.md @@ -40,8 +40,10 @@ npm install @opentui/ssh `@opentui/core` is a peer dependency. Supported runtimes are Bun ≥ 1.3.0 and Node.js ≥ 26.4.0. CI runs the SSH integration suite with Bun on macOS, Linux, -and Windows, and installs, imports, starts, and closes the packed ESM package -on Node.js. +and Windows. CI also installs, imports, starts, and closes the packed ESM +package on Node.js. + +Use Bun ≥ 1.4.0 on native Windows arm64. ## The shape: `createServer(config).serve(handler)` diff --git a/packages/ssh/package.json b/packages/ssh/package.json index 6ef03d472..15ec116dd 100644 --- a/packages/ssh/package.json +++ b/packages/ssh/package.json @@ -35,7 +35,7 @@ "@opentui/core": "workspace:*", "@opentui/react": "workspace:*", "@opentui/solid": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "@types/react": "^19.2.0", "@types/ssh2": "^1.15.0", diff --git a/packages/three/package.json b/packages/three/package.json index cb8ea9422..b36a61139 100644 --- a/packages/three/package.json +++ b/packages/three/package.json @@ -37,7 +37,7 @@ "planck": "^1.4.2" }, "devDependencies": { - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@types/node": "^24.0.0", "@types/three": "0.177.0", "commander": "^13.1.0", diff --git a/packages/three/scripts/build.ts b/packages/three/scripts/build.ts index e65ffba78..33282341e 100644 --- a/packages/three/scripts/build.ts +++ b/packages/three/scripts/build.ts @@ -134,6 +134,7 @@ writeFileSync( homepage: packageJson.homepage, repository: packageJson.repository, bugs: packageJson.bugs, + engines: packageJson.engines, exports, dependencies: processedDependencies, optionalDependencies: packageJson.optionalDependencies, diff --git a/packages/web/package.json b/packages/web/package.json index 16df4919d..472e926c6 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@opentui/qrcode": "workspace:*", - "@types/bun": "1.3.14", + "@types/bun": "1.4.1", "@xterm/addon-unicode11": "^0.8.0", "@xterm/headless": "^5.5.0", "typescript": "^5" diff --git a/packages/web/src/content/docs/core-concepts/audio.mdx b/packages/web/src/content/docs/core-concepts/audio.mdx index a0e5c9712..f5604d950 100644 --- a/packages/web/src/content/docs/core-concepts/audio.mdx +++ b/packages/web/src/content/docs/core-concepts/audio.mdx @@ -77,7 +77,7 @@ A failed automatic start destroys the partial native engine. Successful automati Audio uses the same native runtime as the renderer. Read [Runtime and platform support](/docs/getting-started/runtime-support) for setup and distribution details. -The local requirements are Bun 1.3.0 or later, or Node.js 26.4.0 or later with experimental FFI. Native packages support macOS, Linux, and Windows on x64 and arm64. +The local requirements are Bun 1.3.0 or later, or Node.js 26.4.0 or later with experimental FFI. Native packages support macOS, Linux, and Windows on x64 and arm64. Use Bun 1.4.0 or later on native Windows arm64. Linux selects glibc by default. Set the documented libc selector when the target needs musl. Audio has no browser target. diff --git a/packages/web/src/content/docs/getting-started/runtime-support.mdx b/packages/web/src/content/docs/getting-started/runtime-support.mdx index fbff37913..7127a6431 100644 --- a/packages/web/src/content/docs/getting-started/runtime-support.mdx +++ b/packages/web/src/content/docs/getting-started/runtime-support.mdx @@ -19,6 +19,8 @@ Check the runtime, native artifact, permission, asset, and package requirements The Node.js acceptance scripts accept Node.js 26.4 or later. +Use Bun 1.4.0 or later on native Windows arm64 because Bun 1.3 does not support FFI on that target. + Use ESM for Node.js applications. The Core root has an asynchronous ESM graph. A CommonJS `require("@opentui/core")` call fails with `ERR_REQUIRE_ASYNC_MODULE`. diff --git a/packages/web/src/content/docs/reference/api-index.mdx b/packages/web/src/content/docs/reference/api-index.mdx index 0a6d978c7..816180923 100644 --- a/packages/web/src/content/docs/reference/api-index.mdx +++ b/packages/web/src/content/docs/reference/api-index.mdx @@ -380,7 +380,8 @@ The package root works in Bun and Node.js. Each framework path re-exports the fu ## `@opentui/ssh` -The package supports Bun 1.3.0 and Node.js 26.4.0 or later. It has one public entry point. +The package supports Bun 1.3.0 or later and Node.js 26.4.0 or later. Use Bun 1.4.0 or later on native Windows arm64. +It has one public entry point. | Symbols | Class | Canonical documentation | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -------------------------- | diff --git a/packages/web/src/content/docs/reference/ssh.mdx b/packages/web/src/content/docs/reference/ssh.mdx index e0510f73e..4e572bc93 100644 --- a/packages/web/src/content/docs/reference/ssh.mdx +++ b/packages/web/src/content/docs/reference/ssh.mdx @@ -21,7 +21,8 @@ SSH is an access transport. It is not an application packaging format. See bun add @opentui/ssh @opentui/core ``` -`@opentui/core` is a peer dependency. The SSH package declares Bun >=1.3.0 and Node.js >=26.4.0. The SSH Node CI lane +`@opentui/core` is a peer dependency. The SSH package declares Bun >=1.3.0 and Node.js >=26.4.0. Use Bun >=1.4.0 on +native Windows arm64. The SSH Node CI lane runs on Linux x64. Its packed Node consumer test uses a Core stub, so that test does not start the native renderer. See [Runtime and platform support](/docs/getting-started/runtime-support) before you run the native renderer with Node.js. diff --git a/packages/web/src/content/docs/reference/three.mdx b/packages/web/src/content/docs/reference/three.mdx index 770ebe3aa..fa9299c58 100644 --- a/packages/web/src/content/docs/reference/three.mdx +++ b/packages/web/src/content/docs/reference/three.mdx @@ -12,10 +12,12 @@ skill: ## Requirements -`@opentui/three` requires Bun >=1.3.0. It does not support Node.js. The package pins Three.js 0.177.0. Its manifest lists -`bun-webgpu` 0.1.7, `@dimforge/rapier2d-simd-compat`, and `planck` as optional dependencies, but the root statically -imports all three through its renderer and physics exports. They must all resolve even when an application does not -use physics. A normal install includes them. An install with optional dependencies omitted makes the root import fail. +`@opentui/three` requires Bun >=1.3.0. It does not support Node.js. + +The package pins Three.js 0.177.0. Its manifest lists `bun-webgpu` 0.1.7, `@dimforge/rapier2d-simd-compat`, and `planck` +as optional dependencies. The package root imports all three through its renderer and physics exports. All three +dependencies must resolve even when an application does not use physics. A normal install includes them. The root +module does not load when the installation omits optional dependencies. ```bash bun add @opentui/three diff --git a/packages/web/src/content/packages/opentui-ssh.mdx b/packages/web/src/content/packages/opentui-ssh.mdx index ee4599049..0f1454c09 100644 --- a/packages/web/src/content/packages/opentui-ssh.mdx +++ b/packages/web/src/content/packages/opentui-ssh.mdx @@ -24,7 +24,7 @@ categories: `@opentui/ssh` maps each accepted SSH shell to a `CliRenderer`. The renderer dimensions track the client's terminal. -The package requires Bun `>=1.3.0` or Node.js `>=26.4.0`. +The package requires Bun `>=1.3.0` or Node.js `>=26.4.0`. Use Bun `>=1.4.0` on native Windows arm64. Open authentication accepts SSH `none` without credentials. Use it only for loopback development. Do not expose open authentication to a public or untrusted network.