Compare commits

...

5 Commits

Author SHA1 Message Date
Meghan Denny
33b94f8bb4 Merge branch 'main' into RiskyMH/default-trusted/add-bcrypt 2025-07-03 15:11:58 -08:00
Meghan Denny
2e59e845fa test: refactor node-napi.test.ts for more observability (#20781)
Co-authored-by: nektro <5464072+nektro@users.noreply.github.com>
2025-07-03 14:37:11 -07:00
Meghan Denny
00df6cb4ee Bump 2025-07-03 11:59:00 -07:00
Jarred Sumner
0d4089ea7c Fixes #20753 (#20789)
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2025-07-03 01:06:22 -07:00
Michael H
0bf7451b8b add bcrypt to default-trusted-dependencies.txt
It has `install` script and is semi-popular

https://www.npmjs.com/package/bcrypto
2025-02-10 15:54:24 +11:00
6 changed files with 184 additions and 66 deletions

2
LATEST
View File

@@ -1 +1 @@
1.2.17
1.2.18

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "bun",
"version": "1.2.18",
"version": "1.2.19",
"workspaces": [
"./packages/bun-types",
"./packages/@types/bun"

View File

@@ -113,6 +113,7 @@ azure-functions-core-tools
azure-streamanalytics-cicd
backport
bcrypt
bcrypto
better-sqlite3
bigint-buffer
blake-hash

View File

@@ -272,12 +272,13 @@ function execFile(file, args, options, callback) {
// merge chunks
let stdout;
let stderr;
if (child.stdout?.readableEncoding) {
if (encoding || child.stdout?.readableEncoding) {
stdout = ArrayPrototypeJoin.$call(_stdout, "");
} else {
stdout = BufferConcat(_stdout);
}
if (child.stderr?.readableEncoding) {
if (encoding || child.stderr?.readableEncoding) {
stderr = ArrayPrototypeJoin.$call(_stderr, "");
} else {
stderr = BufferConcat(_stderr);

View File

@@ -1,7 +1,6 @@
import { Glob, spawn, spawnSync } from "bun";
import { beforeAll, describe, expect, it } from "bun:test";
import { describe, expect, it } from "bun:test";
import { bunEnv, bunExe, isBroken, isCI, isIntelMacOS, isMusl, isWindows } from "harness";
import os from "node:os";
import { dirname, join } from "path";
const jsNativeApiRoot = join(__dirname, "node-napi-tests", "test", "js-native-api");
@@ -88,68 +87,51 @@ for (const t of failingNodeApiTests) {
}
}
beforeAll(async () => {
const directories = jsNativeApiTests
.filter(t => !failingJsNativeApiTests.includes(t))
.map(t => join(jsNativeApiRoot, t))
.concat(nodeApiTests.filter(t => !failingNodeApiTests.includes(t)).map(t => join(nodeApiRoot, t)))
.map(t => dirname(t));
const uniqueDirectories = Array.from(new Set(directories));
const directories = jsNativeApiTests
.filter(t => !failingJsNativeApiTests.includes(t))
.map(t => join(jsNativeApiRoot, t))
.concat(nodeApiTests.filter(t => !failingNodeApiTests.includes(t)).map(t => join(nodeApiRoot, t)))
.map(t => dirname(t));
const uniqueDirectories = Array.from(new Set(directories));
async function buildOne(dir: string) {
const child = spawn({
cmd: [bunExe(), "x", "node-gyp@11", "rebuild", "--debug", "-j", "max"],
cwd: dir,
stderr: "pipe",
stdout: "ignore",
stdin: "inherit",
env: {
...bunEnv,
npm_config_target: "v24.3.0",
CXXFLAGS: (bunEnv.CXXFLAGS ?? "") + (process.platform == "win32" ? " -std=c++20" : " -std=gnu++20"),
// on linux CI, node-gyp will default to g++ and the version installed there is very old,
// so we make it use clang instead
...(process.platform == "linux" && isCI
? { "CC": "/usr/lib/llvm-19/bin/clang", CXX: "/usr/lib/llvm-19/bin/clang++" }
: {}),
},
describe("build", () => {
for (const dir of uniqueDirectories) {
it(`${dir.slice(import.meta.dir.length + 1)}`, async () => {
const child = spawn({
cmd: [bunExe(), "x", "node-gyp@11", "rebuild", "--debug", "-j", "max"],
cwd: dir,
stderr: "pipe",
stdout: "ignore",
stdin: "inherit",
env: {
...bunEnv,
npm_config_target: "v24.3.0",
CXXFLAGS: (bunEnv.CXXFLAGS ?? "") + (process.platform == "win32" ? " -std=c++20" : " -std=gnu++20"),
// on linux CI, node-gyp will default to g++ and the version installed there is very old,
// so we make it use clang instead
...(process.platform == "linux" && isCI
? { "CC": "/usr/lib/llvm-19/bin/clang", CXX: "/usr/lib/llvm-19/bin/clang++" }
: {}),
},
});
await child.exited;
if (child.exitCode !== 0) {
const stderr = await new Response(child.stderr).text();
console.error(`node-gyp rebuild in ${dir} failed:\n${stderr}`);
console.error("bailing out!");
process.exit(1);
}
});
await child.exited;
if (child.exitCode !== 0) {
const stderr = await new Response(child.stderr).text();
console.error(`node-gyp rebuild in ${dir} failed:\n${stderr}`);
console.error("bailing out!");
process.exit(1);
}
}
});
async function worker() {
while (uniqueDirectories.length > 0) {
const dir = uniqueDirectories.pop();
await buildOne(dir!);
}
}
const parallelism = Math.min(8, os.cpus().length, 1 /* TODO(@heimskr): remove */);
const jobs: Promise<void>[] = [];
for (let i = 0; i < parallelism; i++) {
jobs.push(worker());
}
await Promise.all(jobs);
}, 600000);
describe.each([
["js-native-api", jsNativeApiTests, jsNativeApiRoot, failingJsNativeApiTests],
["node-api", nodeApiTests, nodeApiRoot, failingNodeApiTests],
])("%s tests", (_name, tests, root, failing) => {
describe.each(tests)("%s", test => {
it.skipIf(failing.includes(test))(
"passes",
() => {
describe("js-native-api tests", () => {
for (const test of jsNativeApiTests) {
describe.skipIf(failingJsNativeApiTests.includes(test))(`${test}`, () => {
it("passes", () => {
const result = spawnSync({
cmd: [bunExe(), "run", test],
cwd: root,
cwd: jsNativeApiRoot,
stderr: "inherit",
stdout: "ignore",
stdin: "inherit",
@@ -157,8 +139,26 @@ describe.each([
});
expect(result.success).toBeTrue();
expect(result.exitCode).toBe(0);
},
60000, // timeout
);
});
}, 60_000);
});
}
});
describe("node-api tests", () => {
for (const test of nodeApiTests) {
describe.skipIf(failingNodeApiTests.includes(test))(`${test}`, () => {
it("passes", () => {
const result = spawnSync({
cmd: [bunExe(), "run", test],
cwd: nodeApiRoot,
stderr: "inherit",
stdout: "ignore",
stdin: "inherit",
env: bunEnv,
});
expect(result.success).toBeTrue();
expect(result.exitCode).toBe(0);
}, 60_000);
});
}
});

View File

@@ -0,0 +1,116 @@
import { describe, expect, test } from "bun:test";
import { isWindows } from "harness";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
describe.skipIf(isWindows /* accessing posix-specific paths */)("stdout should always be a string", () => {
test("execFile returns string stdout/stderr even when process fails to spawn", done => {
// Test case that would cause the issue: non-existent command
execFile("/does/not/exist", [], (err, stdout, stderr) => {
expect(err).toBeTruthy();
expect(err.code).toBe("ENOENT");
// These should never be undefined - they should be strings by default
expect(stdout).toBeDefined();
expect(stderr).toBeDefined();
expect(typeof stdout).toBe("string");
expect(typeof stderr).toBe("string");
expect(stdout).toBe("");
expect(stderr).toBe("");
// This is what claude-code was trying to do that failed
expect(() => stdout.trim()).not.toThrow();
expect(() => stderr.trim()).not.toThrow();
done();
});
});
test("execFile returns string stdout/stderr for permission denied errors", done => {
// Another edge case: file exists but not executable
execFile("/etc/passwd", [], (err, stdout, stderr) => {
expect(err).toBeTruthy();
expect(err.code).toBe("EACCES");
expect(stdout).toBeDefined();
expect(stderr).toBeDefined();
expect(typeof stdout).toBe("string");
expect(typeof stderr).toBe("string");
expect(stdout).toBe("");
expect(stderr).toBe("");
done();
});
});
test("execFile returns Buffer stdout/stderr when encoding is 'buffer'", done => {
execFile("/does/not/exist", [], { encoding: "buffer" }, (err, stdout, stderr) => {
expect(err).toBeTruthy();
expect(err.code).toBe("ENOENT");
expect(stdout).toBeDefined();
expect(stderr).toBeDefined();
expect(Buffer.isBuffer(stdout)).toBe(true);
expect(Buffer.isBuffer(stderr)).toBe(true);
expect(stdout.length).toBe(0);
expect(stderr.length).toBe(0);
done();
});
});
test("execFile promisified version includes stdout/stderr in error object", async () => {
try {
await execFileAsync("/does/not/exist", []);
expect.unreachable("Should have thrown");
} catch (err) {
expect(err.code).toBe("ENOENT");
// Promisified version attaches stdout/stderr to the error object
expect(err.stdout).toBeDefined();
expect(err.stderr).toBeDefined();
expect(typeof err.stdout).toBe("string");
expect(typeof err.stderr).toBe("string");
expect(err.stdout).toBe("");
expect(err.stderr).toBe("");
}
});
test("execFile returns stdout/stderr for process that exits with error code", done => {
execFile(
process.execPath,
["-e", "console.log('output'); console.error('error'); process.exit(1)"],
(err, stdout, stderr) => {
expect(err).toBeTruthy();
expect(err.code).toBe(1);
expect(stdout).toBeDefined();
expect(stderr).toBeDefined();
expect(typeof stdout).toBe("string");
expect(typeof stderr).toBe("string");
expect(stdout).toBe("output\n");
expect(stderr).toBe("error\n");
done();
},
);
});
test("execFile handles fast-exiting processes correctly", done => {
// Process that exits immediately
execFile("true", [], (err, stdout, stderr) => {
expect(err).toBeNull();
expect(stdout).toBeDefined();
expect(stderr).toBeDefined();
expect(typeof stdout).toBe("string");
expect(typeof stderr).toBe("string");
expect(stdout).toBe("");
expect(stderr).toBe("");
done();
});
});
});