Compare commits

...

7 Commits

Author SHA1 Message Date
Claude Bot
96ee763c93 fix(cli): add node_modules/.bin to PATH for lifecycle scripts
The `prepack`, `postpack`, `prepublishOnly`, `publish`, `postpublish`,
`preversion`, `version`, and `postversion` scripts run via
`runPackageScriptForeground` were missing `node_modules/.bin` in their
PATH, preventing them from finding locally installed binaries.

This was inconsistent with:
- `bun run <script>` which correctly adds node_modules/.bin to PATH
- Install lifecycle hooks (preinstall, postinstall) which also have it

The fix adds node_modules/.bin directories to PATH in
`runPackageScriptForeground`, walking up from the cwd (same approach
used in PackageManagerLifecycle.zig for install scripts).

Fixes #17493

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 07:15:07 +00:00
robobun
bfe40e8760 fix(cmake): use BUILDKITE_BUILD_NUMBER to avoid 302 redirect (#26409)
## What does this PR do?

Fixes CMake "No jobs found" error during the build-bun step in CI by
using `BUILDKITE_BUILD_NUMBER` instead of `BUILDKITE_BUILD_ID` (UUID)
for the Buildkite API URL.

### Problem

When `BUN_LINK_ONLY=ON`, `SetupBuildkite.cmake` fetches build info from
the Buildkite API to download artifacts from earlier build steps
(build-cpp, build-zig).

The `BUILDKITE_BUILD_ID` environment variable contains a UUID (e.g.,
`019bee3e-da45-4e9f-b4d8-4bdb5aeac0ac`). When this UUID is used in the
URL, Buildkite returns a **302 redirect** to the numeric build number
URL (e.g., `/builds/35708`).

CMake's `file(DOWNLOAD)` command **does not follow HTTP redirects**, so
the downloaded file is empty. Parsing the empty JSON yields 0 jobs,
triggering the fatal error:

```
CMake Error at cmake/tools/SetupBuildkite.cmake:67 (message):
  No jobs found:
  https://buildkite.com/bun/bun/builds/019bee3e-da45-4e9f-b4d8-4bdb5aeac0ac
```

### Solution

Prefer `BUILDKITE_BUILD_NUMBER` (numeric, e.g., `35708`) when available,
which doesn't redirect. This environment variable is automatically set
by Buildkite.

## How did you verify your code works?

- Verified UUID URL returns 302: `curl -sS -w '%{http_code}'
"https://buildkite.com/bun/bun/builds/019bee3e-da45-4e9f-b4d8-4bdb5aeac0ac"`
→ `302`
- Verified numeric URL returns 200 with JSON: `curl -sS -H "Accept:
application/json" "https://buildkite.com/bun/bun/builds/35708"` → valid
JSON with jobs array

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 23:54:33 -08:00
github-actions[bot]
bcaae48a95 deps: update lolhtml to v2.7.1 (#26430)
## What does this PR do?

Updates lolhtml to version v2.7.1

Compare:
d64457d9ff...e9e16dca48

Auto-updated by [this
workflow](https://github.com/oven-sh/bun/actions/workflows/update-lolhtml.yml)

Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2026-01-24 23:54:09 -08:00
robobun
6b3403a2b4 ci: fix update workflows creating duplicate PRs (#26433)
## Summary
- Fixed all `update-*.yml` workflows that were creating duplicate PRs
every week

## Problem
The update workflows (libarchive, zstd, cares, etc.) were using `${{
github.run_number }}` in the branch name, e.g.:
```yaml
branch: deps/update-libarchive-${{ github.run_number }}
```

This caused a new unique branch to be created on every workflow run, so
the `peter-evans/create-pull-request` action couldn't detect existing
PRs and would create duplicates.

**Evidence:** There are currently 8+ open duplicate PRs for libarchive
alone:
- #26432 deps: update libarchive to v3.8.5 (deps/update-libarchive-56)
- #26209 deps: update libarchive to v3.8.5 (deps/update-libarchive-55)
- #25955 deps: update libarchive to v3.8.5 (deps/update-libarchive-54)
- etc.

## Solution
Changed all workflows to use static branch names, e.g.:
```yaml
branch: deps/update-libarchive
```

This allows the action to:
1. Detect if an existing branch/PR already exists
2. Update the existing PR with new changes instead of creating a new one
3. Properly use `delete-branch: true` when the PR is merged

## Files Changed
- `.github/workflows/update-cares.yml`
- `.github/workflows/update-hdrhistogram.yml`
- `.github/workflows/update-highway.yml`
- `.github/workflows/update-libarchive.yml`
- `.github/workflows/update-libdeflate.yml`
- `.github/workflows/update-lolhtml.yml`
- `.github/workflows/update-lshpack.yml`
- `.github/workflows/update-root-certs.yml`
- `.github/workflows/update-sqlite3.yml`
- `.github/workflows/update-vendor.yml`
- `.github/workflows/update-zstd.yml`

## Test plan
- [x] Verified the change is syntactically correct
- [ ] Wait for next scheduled run of any workflow to verify it updates
existing PR instead of creating a new one

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 23:53:50 -08:00
robobun
70fe76209b fix(readline): use symbol key for _refreshLine in tab completer (#26412) 2026-01-24 15:37:29 -08:00
Jarred Sumner
ab3df344b8 Delete slop test 2026-01-23 23:22:32 -08:00
robobun
4680e89a91 fix(bundler): add missing semicolons in minified bun module imports (#26372)
## Summary
- Fix missing semicolons in minified output when using both default and
named imports from `"bun"` module
- The issue occurred in `printInternalBunImport` when transitioning
between star_name, default_name, and items sections without flushing
pending semicolons

## Test plan
- Added regression tests in `test/regression/issue/26371.test.ts`
covering:
  - Default + named imports (`import bun, { embeddedFiles } from "bun"`)
- Namespace + named imports (`import * as bun from "bun"; import {
embeddedFiles } from "bun"`)
  - Namespace + default + named imports combination
- Verified test fails with `USE_SYSTEM_BUN=1` (reproduces bug)
- Verified test passes with `bun bd test` (fix works)

Fixes #26371

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 23:09:01 -08:00
20 changed files with 298 additions and 104 deletions

View File

@@ -88,7 +88,7 @@ jobs:
commit-message: "deps: update c-ares to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update c-ares to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-cares-${{ github.run_number }}
branch: deps/update-cares
body: |
## What does this PR do?

View File

@@ -91,7 +91,7 @@ jobs:
commit-message: "deps: update hdrhistogram to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update hdrhistogram to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-hdrhistogram-${{ github.run_number }}
branch: deps/update-hdrhistogram
body: |
## What does this PR do?

View File

@@ -107,7 +107,7 @@ jobs:
commit-message: "deps: update highway to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update highway to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-highway-${{ github.run_number }}
branch: deps/update-highway
body: |
## What does this PR do?

View File

@@ -88,7 +88,7 @@ jobs:
commit-message: "deps: update libarchive to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update libarchive to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-libarchive-${{ github.run_number }}
branch: deps/update-libarchive
body: |
## What does this PR do?

View File

@@ -88,7 +88,7 @@ jobs:
commit-message: "deps: update libdeflate to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update libdeflate to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-libdeflate-${{ github.run_number }}
branch: deps/update-libdeflate
body: |
## What does this PR do?

View File

@@ -100,7 +100,7 @@ jobs:
commit-message: "deps: update lolhtml to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update lolhtml to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-lolhtml-${{ github.run_number }}
branch: deps/update-lolhtml
body: |
## What does this PR do?

View File

@@ -105,7 +105,7 @@ jobs:
commit-message: "deps: update lshpack to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update lshpack to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-lshpack-${{ github.run_number }}
branch: deps/update-lshpack
body: |
## What does this PR do?

View File

@@ -74,7 +74,7 @@ jobs:
```
${{ env.changed_files }}
```
branch: certs/update-root-certs-${{ github.run_number }}
branch: certs/update-root-certs
base: main
delete-branch: true
labels:

View File

@@ -83,7 +83,7 @@ jobs:
commit-message: "deps: update sqlite to ${{ steps.check-version.outputs.latest }}"
title: "deps: update sqlite to ${{ steps.check-version.outputs.latest }}"
delete-branch: true
branch: deps/update-sqlite-${{ steps.check-version.outputs.latest }}
branch: deps/update-sqlite
body: |
## What does this PR do?

View File

@@ -68,7 +68,7 @@ jobs:
commit-message: "deps: update ${{ matrix.package }} to ${{ steps.check-version.outputs.latest }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update ${{ matrix.package }} to ${{ steps.check-version.outputs.latest }}"
delete-branch: true
branch: deps/update-${{ matrix.package }}-${{ github.run_number }}
branch: deps/update-${{ matrix.package }}
body: |
## What does this PR do?

View File

@@ -88,7 +88,7 @@ jobs:
commit-message: "deps: update zstd to ${{ steps.check-version.outputs.tag }} (${{ steps.check-version.outputs.latest }})"
title: "deps: update zstd to ${{ steps.check-version.outputs.tag }}"
delete-branch: true
branch: deps/update-zstd-${{ github.run_number }}
branch: deps/update-zstd
body: |
## What does this PR do?

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
cloudflare/lol-html
COMMIT
d64457d9ff0143deef025d5df7e8586092b9afb7
e9e16dca48dd4a8ffbc77642bc4be60407585f11
)
set(LOLHTML_CWD ${VENDOR_PATH}/lolhtml/c-api)

View File

@@ -6,7 +6,8 @@ endif()
optionx(BUILDKITE_ORGANIZATION_SLUG STRING "The organization slug to use on Buildkite" DEFAULT "bun")
optionx(BUILDKITE_PIPELINE_SLUG STRING "The pipeline slug to use on Buildkite" DEFAULT "bun")
optionx(BUILDKITE_BUILD_ID STRING "The build ID to use on Buildkite")
optionx(BUILDKITE_BUILD_ID STRING "The build ID (UUID) to use on Buildkite")
optionx(BUILDKITE_BUILD_NUMBER STRING "The build number to use on Buildkite")
optionx(BUILDKITE_GROUP_ID STRING "The group ID to use on Buildkite")
if(ENABLE_BASELINE)
@@ -32,7 +33,13 @@ if(NOT BUILDKITE_BUILD_ID)
return()
endif()
setx(BUILDKITE_BUILD_URL https://buildkite.com/${BUILDKITE_ORGANIZATION_SLUG}/${BUILDKITE_PIPELINE_SLUG}/builds/${BUILDKITE_BUILD_ID})
# Use BUILDKITE_BUILD_NUMBER for the URL if available, as the UUID format causes a 302 redirect
# that CMake's file(DOWNLOAD) doesn't follow, resulting in empty response.
if(BUILDKITE_BUILD_NUMBER)
setx(BUILDKITE_BUILD_URL https://buildkite.com/${BUILDKITE_ORGANIZATION_SLUG}/${BUILDKITE_PIPELINE_SLUG}/builds/${BUILDKITE_BUILD_NUMBER})
else()
setx(BUILDKITE_BUILD_URL https://buildkite.com/${BUILDKITE_ORGANIZATION_SLUG}/${BUILDKITE_PIPELINE_SLUG}/builds/${BUILDKITE_BUILD_ID})
endif()
setx(BUILDKITE_BUILD_PATH ${BUILDKITE_BUILDS_PATH}/builds/${BUILDKITE_BUILD_ID})
file(

View File

@@ -216,6 +216,23 @@ pub const RunCommand = struct {
silent: bool,
use_system_shell: bool,
) !void {
// Add node_modules/.bin directories to PATH (walking up from cwd)
const original_path = env.get("PATH") orelse "";
var PATH: bun.EnvPath(.{}) = try .initCapacity(allocator, original_path.len + 1 + "node_modules/.bin".len + cwd.len + 1);
defer PATH.deinit();
var parent: ?string = cwd;
while (parent) |dir| {
var builder = PATH.pathComponentBuilder();
builder.append(dir);
builder.append("node_modules/.bin");
try builder.apply();
parent = std.fs.path.dirname(dir);
}
try PATH.append(original_path);
try env.map.put("PATH", PATH.slice());
const shell_bin = findShell(env.get("PATH") orelse "", cwd) orelse return error.MissingShell;
env.map.put("npm_lifecycle_event", name) catch unreachable;
env.map.put("npm_lifecycle_script", original_script) catch unreachable;

View File

@@ -1534,7 +1534,7 @@ var _Interface = class Interface extends InterfaceConstructor {
prefix +
StringPrototypeSlice.$call(this.line, this.cursor, this.line.length);
this.cursor = this.cursor - completeOn.length + prefix.length;
this._refreshLine();
this[kRefreshLine]();
return;
}

View File

@@ -964,6 +964,7 @@ fn NewPrinter(
}
if (import.default_name) |default| {
p.printSemicolonIfNeeded();
p.print("var ");
p.printSymbol(default.ref.?);
if (comptime Statement == void) {
@@ -984,6 +985,7 @@ fn NewPrinter(
}
if (import.items.len > 0) {
p.printSemicolonIfNeeded();
p.printWhitespacer(ws("var {"));
if (!import.is_single_line) {

View File

@@ -1109,15 +1109,15 @@ describe("bundler", () => {
"/entry.js": /* js */ `
// Test all equality operators with typeof undefined
console.log(typeof x !== 'undefined');
console.log(typeof x != 'undefined');
console.log(typeof x != 'undefined');
console.log('undefined' !== typeof x);
console.log('undefined' != typeof x);
console.log(typeof x === 'undefined');
console.log(typeof x == 'undefined');
console.log('undefined' === typeof x);
console.log('undefined' == typeof x);
// These should not be optimized
console.log(typeof x === 'string');
console.log(x === 'undefined');
@@ -1135,4 +1135,61 @@ describe("bundler", () => {
);
},
});
// https://github.com/oven-sh/bun/issues/26371
// Minified bundler output missing semicolon between statements when
// using both default and named imports from "bun" module
itBundled("minify/BunImportSemicolonInsertion", {
files: {
"/entry.js": /* js */ `
import bun, { embeddedFiles } from "bun"
console.log(typeof embeddedFiles)
console.log(typeof bun.argv)
`,
},
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
target: "bun",
run: {
stdout: "object\nobject",
},
});
itBundled("minify/BunImportNamespaceAndNamed", {
files: {
"/entry.js": /* js */ `
import * as bun from "bun"
import { embeddedFiles } from "bun"
console.log(typeof embeddedFiles)
console.log(typeof bun.argv)
`,
},
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
target: "bun",
run: {
stdout: "object\nobject",
},
});
itBundled("minify/BunImportDefaultNamespaceAndNamed", {
files: {
"/entry.js": /* js */ `
import bun, * as bunNs from "bun"
import { embeddedFiles } from "bun"
console.log(typeof embeddedFiles)
console.log(typeof bun.argv)
console.log(typeof bunNs.argv)
`,
},
minifySyntax: true,
minifyWhitespace: true,
minifyIdentifiers: true,
target: "bun",
run: {
stdout: "object\nobject\nobject",
},
});
});

View File

@@ -0,0 +1,157 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
test("prepack script has node_modules/.bin in PATH", async () => {
// Create a test package that verifies rimraf is accessible from prepack script
using dir = tempDir("pack-bin-path", {
"package.json": JSON.stringify(
{
name: "test-bin-path",
version: "1.0.0",
scripts: {
// Use which to find rimraf, or check if it's in PATH
prepack: "node check-path.js",
},
devDependencies: {
rimraf: "^3.0.2",
},
},
null,
2,
),
"check-path.js": `
const path = require('path');
const fs = require('fs');
// Check if node_modules/.bin is in PATH
const pathDirs = process.env.PATH.split(path.delimiter);
const binDir = path.join(process.cwd(), 'node_modules', '.bin');
const hasBinInPath = pathDirs.some(dir => {
// Normalize paths for comparison
const normalizedDir = path.normalize(dir);
const normalizedBinDir = path.normalize(binDir);
return normalizedDir === normalizedBinDir || normalizedDir.endsWith(path.join('node_modules', '.bin'));
});
if (!hasBinInPath) {
console.error('ERROR: node_modules/.bin is NOT in PATH');
console.error('PATH directories:', pathDirs);
process.exit(1);
}
// Also verify rimraf executable exists in node_modules/.bin
const rimrafPath = path.join(binDir, process.platform === 'win32' ? 'rimraf.cmd' : 'rimraf');
if (!fs.existsSync(rimrafPath)) {
// Try the bunx shim path
const bunxShimPath = path.join(binDir, 'rimraf');
if (!fs.existsSync(bunxShimPath)) {
console.error('ERROR: rimraf not found in node_modules/.bin');
process.exit(1);
}
}
console.log('SUCCESS: node_modules/.bin is in PATH');
`,
});
// First install dependencies
await using installProc = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [, , installExitCode] = await Promise.all([
installProc.stdout.text(),
installProc.stderr.text(),
installProc.exited,
]);
expect(installExitCode).toBe(0);
// Now run bun pm pack and verify prepack script can find binaries
await using packProc = Bun.spawn({
cmd: [bunExe(), "pm", "pack"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
packProc.stdout.text(),
packProc.stderr.text(),
packProc.exited,
]);
// The check-path.js script should succeed, meaning node_modules/.bin was in PATH
expect(stdout + stderr).toContain("SUCCESS: node_modules/.bin is in PATH");
expect(stdout + stderr).not.toContain("ERROR:");
expect(exitCode).toBe(0);
});
test("bun run script has node_modules/.bin in PATH (control test)", async () => {
// This verifies that `bun run` works correctly (as a control)
using dir = tempDir("run-bin-path", {
"package.json": JSON.stringify(
{
name: "test-run-bin-path",
version: "1.0.0",
scripts: {
"check-path": "node check-path.js",
},
devDependencies: {
rimraf: "^3.0.2",
},
},
null,
2,
),
"check-path.js": `
const path = require('path');
// Check if node_modules/.bin is in PATH
const pathDirs = process.env.PATH.split(path.delimiter);
const hasBinInPath = pathDirs.some(dir => dir.endsWith(path.join('node_modules', '.bin')));
if (!hasBinInPath) {
console.error('ERROR: node_modules/.bin is NOT in PATH');
process.exit(1);
}
console.log('SUCCESS: node_modules/.bin is in PATH');
`,
});
// First install dependencies
await using installProc = Bun.spawn({
cmd: [bunExe(), "install"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [, , installExitCode] = await Promise.all([
installProc.stdout.text(),
installProc.stderr.text(),
installProc.exited,
]);
expect(installExitCode).toBe(0);
// Run the check-path script via bun run
await using runProc = Bun.spawn({
cmd: [bunExe(), "run", "check-path"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([runProc.stdout.text(), runProc.stderr.text(), runProc.exited]);
expect(stdout + stderr).toContain("SUCCESS: node_modules/.bin is in PATH");
expect(stdout + stderr).not.toContain("ERROR:");
expect(exitCode).toBe(0);
});

View File

@@ -1,86 +0,0 @@
import { expect, test } from "bun:test";
// https://github.com/oven-sh/bun/issues/26394
// Race condition in Bun.serve() where requests can arrive before routes are fully registered,
// causing the default "Welcome to Bun!" response instead of the configured handler's response.
test("concurrent Bun.serve instances should not return Welcome to Bun", async () => {
const serverCount = 60;
const servers: ReturnType<typeof Bun.serve>[] = [];
try {
// Create many servers concurrently
for (let i = 0; i < serverCount; i++) {
servers.push(
Bun.serve({
port: 0,
fetch: () => new Response("OK"),
}),
);
}
// Make concurrent requests to all servers
const responses = await Promise.all(
servers.map(async server => {
const res = await fetch(`http://127.0.0.1:${server.port}/`);
return res.text();
}),
);
// Verify no "Welcome to Bun!" responses - check for both debug mode message and production mode
for (let i = 0; i < responses.length; i++) {
expect(responses[i]).not.toContain("Welcome to Bun");
expect(responses[i]).not.toBe(""); // Production mode returns empty for renderMissing
expect(responses[i]).toBe("OK");
}
} finally {
// Clean up - guaranteed to run even if assertions fail
for (const server of servers) {
server.stop();
}
}
});
test("Bun.serve should be ready to handle requests immediately after returning", async () => {
// Test a single server with immediate fetch - this tests if the server is ready synchronously
using server = Bun.serve({
port: 0,
fetch: () => new Response("handler response"),
});
// Immediately fetch - if there's a race condition, this might return "Welcome to Bun!"
const response = await fetch(`http://127.0.0.1:${server.port}/`);
const text = await response.text();
expect(text).toBe("handler response");
});
test("multiple sequential Bun.serve instances with immediate requests", async () => {
// Create servers sequentially and immediately request from each
const results: string[] = [];
const servers: ReturnType<typeof Bun.serve>[] = [];
try {
for (let i = 0; i < 20; i++) {
const server = Bun.serve({
port: 0,
fetch: () => new Response(`server-${i}`),
});
servers.push(server);
// Immediately fetch from the server
const response = await fetch(`http://127.0.0.1:${server.port}/`);
results.push(await response.text());
}
// Verify all responses match expected
for (let i = 0; i < results.length; i++) {
expect(results[i]).toBe(`server-${i}`);
}
} finally {
// Clean up - guaranteed to run even if assertions fail
for (const server of servers) {
server.stop();
}
}
});

View File

@@ -0,0 +1,40 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
// https://github.com/oven-sh/bun/issues/26411
// Tab completion with node:readline/promises threw
// "TypeError: this._refreshLine is not a function"
test("tab completion works with node:readline/promises", async () => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
import readline from "node:readline/promises";
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: true,
completer: (line) => [["FOO", "FOOBAR"], line]
});
rl.line = "foo";
rl.cursor = 3;
setTimeout(() => {
rl.close();
console.log("OK");
process.exit(0);
}, 100);
rl.write("", { name: "tab" });
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).not.toContain("this._refreshLine is not a function");
expect(stdout).toContain("OK");
expect(exitCode).toBe(0);
});