/** * This file is loaded in every test file in the repository. * * Avoid adding external dependencies here so that we can still run some tests * without always needing to run `bun install` in development. */ import { gc as bunGC, sleepSync, spawnSync, unsafe, which, write } from "bun"; import { heapStats } from "bun:jsc"; import { beforeAll, describe, expect } from "bun:test"; import { ChildProcess, execSync, fork } from "child_process"; import { readdir, readFile, readlink, rm, writeFile } from "fs/promises"; import fs, { closeSync, openSync, rmSync } from "node:fs"; import os from "node:os"; import { dirname, isAbsolute, join } from "path"; import * as numeric from "_util/numeric.ts"; export const BREAKING_CHANGES_BUN_1_2 = false; export const isMacOS = process.platform === "darwin"; export const isLinux = process.platform === "linux"; export const isPosix = isMacOS || isLinux; export const isWindows = process.platform === "win32"; export const isIntelMacOS = isMacOS && process.arch === "x64"; export const isArm64 = process.arch === "arm64"; export const isDebug = Bun.version.includes("debug"); export const isCI = process.env.CI !== undefined; export const libcFamily: "glibc" | "musl" = process.platform !== "linux" ? "glibc" : // process.report.getReport() has incorrect type definitions. (process.report.getReport() as { header: { glibcVersionRuntime: boolean } }).header.glibcVersionRuntime ? "glibc" : "musl"; export const isMusl = isLinux && libcFamily === "musl"; export const isGlibc = isLinux && libcFamily === "glibc"; export const isBuildKite = process.env.BUILDKITE === "true"; export const isVerbose = process.env.DEBUG === "1"; // Use these to mark a test as flaky or broken. // This will help us keep track of these tests. // // test.todoIf(isFlaky && isMacOS)("this test is flaky"); export const isFlaky = isCI; export const isBroken = isCI; export const isASAN = basename(process.execPath).includes("bun-asan"); export const bunEnv: NodeJS.Dict = { ...process.env, GITHUB_ACTIONS: "false", BUN_DEBUG_QUIET_LOGS: "1", NO_COLOR: "1", FORCE_COLOR: undefined, TZ: "Etc/UTC", CI: "1", BUN_RUNTIME_TRANSPILER_CACHE_PATH: "0", BUN_FEATURE_FLAG_INTERNAL_FOR_TESTING: "1", BUN_GARBAGE_COLLECTOR_LEVEL: process.env.BUN_GARBAGE_COLLECTOR_LEVEL || "0", BUN_FEATURE_FLAG_EXPERIMENTAL_BAKE: "1", BUN_DEBUG_linkerctx: "0", WANTS_LOUD: "0", AGENT: "false", }; const ciEnv = { ...bunEnv }; if (isASAN) { bunEnv.ASAN_OPTIONS ??= "allow_user_segv_handler=1:disable_coredump=0"; } if (isWindows) { bunEnv.SHELLOPTS = "igncr"; // Ignore carriage return } for (let key in bunEnv) { if (bunEnv[key] === undefined) { delete ciEnv[key]; delete bunEnv[key]; } if (key.startsWith("BUN_DEBUG_") && key !== "BUN_DEBUG_QUIET_LOGS") { delete ciEnv[key]; delete bunEnv[key]; } if (key.startsWith("BUILDKITE")) { delete bunEnv[key]; delete process.env[key]; } } delete bunEnv.BUN_INSPECT_CONNECT_TO; delete bunEnv.NODE_ENV; if (isDebug) { // This makes debug build memory leak tests more reliable. // The code for dumping out the debug build transpiled source code has leaks. bunEnv.BUN_DEBUG_NO_DUMP = "1"; } export function bunExe() { if (isWindows) return process.execPath.replaceAll("\\", "/"); return process.execPath; } export function nodeExe(): string | null { return which("node") || null; } export function shellExe(): string { return isWindows ? "pwsh" : "bash"; } export function gc(force = true) { bunGC(force); } /** * The garbage collector is not 100% deterministic * * We want to assert that SOME of the objects are collected * But we cannot reliably assert that ALL of them are collected * * Therefore, we check that the count is less than or equal to the expected count * * @param type * @param count * @param maxWait * @returns */ export async function expectMaxObjectTypeCount( expect: typeof import("bun:test").expect, type: string, count: number, maxWait = 1000, ) { var { heapStats } = require("bun:jsc"); gc(); if (heapStats().objectTypeCounts[type] <= count) return; gc(true); for (const wait = 20; maxWait > 0; maxWait -= wait) { if (heapStats().objectTypeCounts[type] <= count) break; await Bun.sleep(wait); gc(); } expect(heapStats().objectTypeCounts[type] || 0).toBeLessThanOrEqual(count); } // we must ensure that finalizers are run // so that the reference-counting logic is exercised export function gcTick(trace = false) { trace && console.trace(""); // console.trace("hello"); gc(); return Bun.sleep(0); } export function withoutAggressiveGC(block: () => unknown) { if (!unsafe.gcAggressionLevel) return block(); const origGC = unsafe.gcAggressionLevel(); unsafe.gcAggressionLevel(0); try { return block(); } finally { unsafe.gcAggressionLevel(origGC); } } export function hideFromStackTrace(block: CallableFunction) { Object.defineProperty(block, "name", { value: "::bunternal::", configurable: true, enumerable: true, writable: true, }); } export type DirectoryTree = { [name: string]: | string | Buffer | DirectoryTree | ((opts: { root: string }) => Bun.MaybePromise); }; export async function makeTree(base: string, tree: DirectoryTree) { const isDirectoryTree = (value: string | DirectoryTree | Buffer): value is DirectoryTree => typeof value === "object" && value && typeof value?.byteLength === "undefined"; for (const [name, raw_contents] of Object.entries(tree)) { const contents = typeof raw_contents === "function" ? await raw_contents({ root: base }) : raw_contents; const joined = join(base, name); if (name.includes("/")) { const dir = dirname(name); if (dir !== name && dir !== ".") { fs.mkdirSync(join(base, dir), { recursive: true }); } } if (isDirectoryTree(contents)) { fs.mkdirSync(joined); makeTree(joined, contents); continue; } fs.writeFileSync(joined, contents); } } export function makeTreeSyncFromDirectoryTree(base: string, tree: DirectoryTree) { const isDirectoryTree = (value: string | DirectoryTree | Buffer): value is DirectoryTree => typeof value === "object" && value && typeof value?.byteLength === "undefined"; for (const [name, raw_contents] of Object.entries(tree)) { const contents = (typeof raw_contents === "function" ? raw_contents({ root: base }) : raw_contents) as string; const joined = join(base, name); if (name.includes("/")) { const dir = dirname(name); if (dir !== name && dir !== ".") { fs.mkdirSync(join(base, dir), { recursive: true }); } } if (isDirectoryTree(contents)) { fs.mkdirSync(joined); makeTreeSync(joined, contents); continue; } fs.writeFileSync(joined, contents); } } export function makeTreeSync(base: string, filesOrAbsolutePathToCopyFolderFrom: DirectoryTree | string) { if (typeof filesOrAbsolutePathToCopyFolderFrom === "string") { fs.cpSync(filesOrAbsolutePathToCopyFolderFrom, base, { recursive: true }); return; } return makeTreeSyncFromDirectoryTree(base, filesOrAbsolutePathToCopyFolderFrom); } /** * Recursively create files within a new temporary directory. * * @param basename prefix of the new temporary directory * @param filesOrAbsolutePathToCopyFolderFrom Directory tree or absolute path to a folder to copy. If passing an object each key is a folder or file, and each value is the contents of the file. Use objects for directories. * @returns an absolute path to the new temporary directory * * @example * ```ts * const dir = tempDirWithFiles("my-test", { * "index.js": `import foo from "./src/foo";`, * "src": { * "foo.js": `export default "foo";`, * }, * }); * ``` */ export function tempDirWithFiles( basename: string, filesOrAbsolutePathToCopyFolderFrom: DirectoryTree | string, ): string { const base = fs.mkdtempSync(join(fs.realpathSync.native(os.tmpdir()), basename + "_")); makeTreeSync(base, filesOrAbsolutePathToCopyFolderFrom); return base; } class DisposableString extends String { [Symbol.dispose]() { fs.rmSync(this + "", { recursive: true, force: true }); } [Symbol.asyncDispose]() { return fs.promises.rm(this + "", { recursive: true, force: true }); } } export function tempDir( basename: string, filesOrAbsolutePathToCopyFolderFrom: DirectoryTree | string, ): string & DisposableString & AsyncDisposable { const base = tempDirWithFiles(basename, filesOrAbsolutePathToCopyFolderFrom); return new DisposableString(base) as string & DisposableString & AsyncDisposable; } export function tempDirWithFilesAnon(filesOrAbsolutePathToCopyFolderFrom: DirectoryTree | string): string { const base = tmpdirSync(); makeTreeSync(base, filesOrAbsolutePathToCopyFolderFrom); return base; } export function bunRun(file: string, env?: Record | NodeJS.ProcessEnv, dump = false) { var path = require("path"); const result = Bun.spawnSync([bunExe(), file], { cwd: path.dirname(file), env: { ...bunEnv, NODE_ENV: undefined, ...env, }, stdin: "ignore", stdout: !dump ? "pipe" : "inherit", stderr: !dump ? "pipe" : "inherit", }); if (!result.success) { if (dump) { throw new Error( "exited with code " + result.exitCode + (result.signalCode ? `signal: ${result.signalCode}` : ""), ); } throw new Error(String(result.stderr) + "\n" + String(result.stdout)); } return { stdout: String(result.stdout ?? "").trim(), stderr: String(result.stderr ?? "").trim(), }; } export function bunTest(file: string, env?: Record) { var path = require("path"); const result = Bun.spawnSync([bunExe(), "test", path.basename(file)], { cwd: path.dirname(file), env: { ...bunEnv, NODE_ENV: undefined, ...env, }, }); if (!result.success) throw new Error(result.stderr.toString("utf8")); return { stdout: result.stdout.toString("utf8").trim(), stderr: result.stderr.toString("utf8").trim(), }; } export function bunRunAsScript( dir: string, script: string, env?: Record, execArgv?: string[], ) { const result = Bun.spawnSync([bunExe(), ...(execArgv ?? []), `run`, `${script}`], { cwd: dir, env: { ...bunEnv, NODE_ENV: undefined, ...env, }, }); if (!result.success) throw new Error(result.stderr.toString("utf8")); return { stdout: result.stdout.toString("utf8").trim(), stderr: result.stderr.toString("utf8").trim(), }; } export function randomLoneSurrogate() { const n = numeric.random.between(0, 2, { domain: "integral" }); if (n === 0) return randomLoneHighSurrogate(); return randomLoneLowSurrogate(); } export function randomInvalidSurrogatePair() { const low = randomLoneLowSurrogate(); const high = randomLoneHighSurrogate(); return `${low}${high}`; } // Generates a random lone high surrogate (from the range D800-DBFF) export function randomLoneHighSurrogate() { return String.fromCharCode(numeric.random.between(0xd800, 0xdbff, { domain: "integral" })); } // Generates a random lone high surrogate (from the range DC00-DFFF) export function randomLoneLowSurrogate() { return String.fromCharCode(numeric.random.between(0xdc00, 0xdfff, { domain: "integral" })); } export function runWithError(cb: () => unknown): Error | undefined { try { cb(); } catch (e) { return e as Error; } return undefined; } export async function runWithErrorPromise(cb: () => unknown): Promise { try { await cb(); } catch (e) { return e as Error; } return undefined; } export function fakeNodeRun(dir: string, file: string | string[], env?: Record) { var path = require("path"); const result = Bun.spawnSync([bunExe(), "--bun", "node", ...(Array.isArray(file) ? file : [file])], { cwd: dir ?? path.dirname(file), env: { ...bunEnv, NODE_ENV: undefined, ...env, }, }); if (!result.success) throw new Error(result.stderr.toString("utf8")); return { stdout: result.stdout.toString("utf8").trim(), stderr: result.stderr.toString("utf8").trim(), }; } export function randomPort(): number { return 1024 + Math.floor(Math.random() * (65535 - 1024)); } const binaryTypes = { "buffer": Buffer, "arraybuffer": ArrayBuffer, "uint8array": Uint8Array, "uint16array": Uint16Array, "uint32array": Uint32Array, "int8array": Int8Array, "int16array": Int16Array, "int32array": Int32Array, "float16array": globalThis.Float16Array, "float32array": Float32Array, "float64array": Float64Array, } as const; if (expect.extend) expect.extend({ toHaveTestTimedOutAfter(actual: any, expected: number) { if (typeof actual !== "string") { return { pass: false, message: () => `Expected ${actual} to be a string`, }; } const preStartI = actual.indexOf("timed out after "); if (preStartI === -1) { return { pass: false, message: () => `Expected ${actual} to contain "timed out after "`, }; } const startI = preStartI + "timed out after ".length; const endI = actual.indexOf("ms", startI); if (endI === -1) { return { pass: false, message: () => `Expected ${actual} to contain "ms" after "timed out after "`, }; } const int = parseInt(actual.slice(startI, endI)); if (!Number.isSafeInteger(int)) { return { pass: false, message: () => `Expected ${int} to be a safe integer`, }; } return { pass: int >= expected, message: () => `Expected ${int} to be >= ${expected}`, }; }, toBeBinaryType(actual: any, expected: keyof typeof binaryTypes) { switch (expected) { case "buffer": return { pass: Buffer.isBuffer(actual), message: () => `Expected ${actual} to be buffer`, }; case "arraybuffer": return { pass: actual instanceof ArrayBuffer, message: () => `Expected ${actual} to be ArrayBuffer`, }; default: { const ctor = binaryTypes[expected]; if (!ctor) { return { pass: false, message: () => `Expected ${expected} to be a binary type`, }; } return { pass: actual instanceof ctor, message: () => `Expected ${actual} to be ${expected}`, }; } } }, toRun(cmds: string[], optionalStdout?: string, expectedCode: number = 0) { const result = Bun.spawnSync({ cmd: [bunExe(), ...cmds], env: bunEnv, stdio: ["inherit", "pipe", "inherit"], }); if (result.exitCode !== expectedCode) { return { pass: false, message: () => `Command ${cmds.join(" ")} failed:` + "\n" + result.stdout.toString("utf-8"), }; } if (optionalStdout != null) { return { pass: result.stdout.toString("utf-8") === optionalStdout, message: () => `Expected ${cmds.join(" ")} to output ${optionalStdout} but got ${result.stdout.toString("utf-8")}`, }; } return { pass: true, message: () => `Expected ${cmds.join(" ")} to fail`, }; }, toThrowWithCode(fn: CallableFunction, cls: CallableFunction, code: string) { try { fn(); return { pass: false, message: () => `Received function did not throw`, }; } catch (e) { // expect(e).toBeInstanceOf(cls); if (!(e instanceof cls)) { return { pass: false, message: () => `Expected error to be instanceof ${cls.name}; got ${e.__proto__.constructor.name}`, }; } // expect(e).toHaveProperty("code"); if (!("code" in e)) { return { pass: false, message: () => `Expected error to have property 'code'; got ${e}`, }; } // expect(e.code).toEqual(code); if (e.code !== code) { return { pass: false, message: () => `Expected error to have code '${code}'; got ${e.code}`, }; } return { pass: true, }; } }, async toThrowWithCodeAsync(fn: CallableFunction, cls: CallableFunction, code: string) { try { await fn(); return { pass: false, message: () => `Received function did not throw`, }; } catch (e) { // expect(e).toBeInstanceOf(cls); if (!(e instanceof cls)) { return { pass: false, message: () => `Expected error to be instanceof ${cls.name}; got ${e.__proto__.constructor.name}`, }; } // expect(e).toHaveProperty("code"); if (!("code" in e)) { return { pass: false, message: () => `Expected error to have property 'code'; got ${e}`, }; } // expect(e.code).toEqual(code); if (e.code !== code) { return { pass: false, message: () => `Expected error to have code '${code}'; got ${e.code}`, }; } return { pass: true, }; } }, toBeLatin1String(actual: unknown) { if ((actual as string).isLatin1()) { return { pass: true, message: () => `Expected ${actual} to be a Latin1 string`, }; } return { pass: false, message: () => `Expected ${actual} to be a Latin1 string`, }; }, toBeUTF16String(actual: unknown) { if ((actual as string).isUTF16()) { return { pass: true, message: () => `Expected ${actual} to be a UTF16 string`, }; } return { pass: false, message: () => `Expected ${actual} to be a UTF16 string`, }; }, }); export function ospath(path: string) { if (isWindows) { return path.replace(/\//g, "\\"); } return path; } /** * Iterates through each tree in the lockfile, checking for each package * on disk. Also requires each package dependency. Not tested well for * non-npm packages (links, folders, git dependencies, etc.) */ export async function toMatchNodeModulesAt(lockfile: any, root: string) { function shouldSkip(pkg: any, dep: any): boolean { // Band-aid as toMatchNodeModulesAt will sometimes ask this function // if a package depends on itself if (pkg?.name === dep?.name) return true; return ( !pkg || !pkg.resolution || dep.behavior.optional || (dep.behavior.dev && pkg.id !== 0) || (pkg.arch && pkg.arch !== process.arch) ); } for (const { path, dependencies } of lockfile.trees) { for (const { package_id, id } of Object.values(dependencies) as any[]) { const treeDep = lockfile.dependencies[id]; const treePkg = lockfile.packages[package_id]; if (shouldSkip(treePkg, treeDep)) continue; const treeDepPath = join(root, path, treeDep.name); switch (treePkg.resolution.tag) { case "npm": const onDisk = await Bun.file(join(treeDepPath, "package.json")).json(); if (!Bun.deepMatch({ name: treePkg.name, version: treePkg.resolution.value }, onDisk)) { return { pass: false, message: () => ` Expected at ${join(path, treeDep.name)}: ${JSON.stringify({ name: treePkg.name, version: treePkg.resolution.value })} Received ${JSON.stringify({ name: onDisk.name, version: onDisk.version })}`, }; } // Ok, we've confirmed the package exists and has the correct version. Now go through // each of its transitive dependencies and confirm the same. for (const depId of treePkg.dependencies) { const dep = lockfile.dependencies[depId]; const pkg = lockfile.packages[dep.package_id]; if (shouldSkip(pkg, dep)) continue; try { const resolved = await Bun.file(Bun.resolveSync(join(dep.name, "package.json"), treeDepPath)).json(); switch (pkg.resolution.tag) { case "npm": const name = dep.is_alias ? dep.npm.name : dep.name; if (!Bun.deepMatch({ name, version: pkg.resolution.value }, resolved)) { if (dep.literal === "*") { // allow any version, just needs to be resolvable continue; } if (dep.behavior.peer && dep.npm) { // allow peer dependencies to not match exactly, but still satisfy if (Bun.semver.satisfies(pkg.resolution.value, dep.npm.version)) continue; } return { pass: false, message: () => `Expected ${dep.name} to have version ${pkg.resolution.value} in ${treeDepPath}, but got ${resolved.version}`, }; } break; } } catch (e) { return { pass: false, message: () => `Expected ${dep.name} to be resolvable in ${treeDepPath}`, }; } } break; default: if (!fs.existsSync(treeDepPath)) { return { pass: false, message: () => `Expected ${treePkg.resolution.tag} "${treeDepPath}" to exist`, }; } for (const depId of treePkg.dependencies) { const dep = lockfile.dependencies[depId]; const pkg = lockfile.packages[dep.package_id]; if (shouldSkip(pkg, dep)) continue; try { const resolved = await Bun.file(Bun.resolveSync(join(dep.name, "package.json"), treeDepPath)).json(); switch (pkg.resolution.tag) { case "npm": const name = dep.is_alias ? dep.npm.name : dep.name; if (!Bun.deepMatch({ name, version: pkg.resolution.value }, resolved)) { if (dep.literal === "*") { // allow any version, just needs to be resolvable continue; } // workspaces don't need a version if (treePkg.resolution.tag === "workspace" && !resolved.version) continue; if (dep.behavior.peer && dep.npm) { // allow peer dependencies to not match exactly, but still satisfy if (Bun.semver.satisfies(pkg.resolution.value, dep.npm.version)) continue; } return { pass: false, message: () => `Expected ${dep.name} to have version ${pkg.resolution.value} in ${treeDepPath}, but got ${resolved.version}`, }; } break; } } catch (e) { return { pass: false, message: () => `Expected ${dep.name} to be resolvable in ${treeDepPath}`, }; } } break; } } } return { pass: true, }; } export async function toHaveBins(actual: string[], expectedBins: string[]) { const message = () => `Expected ${actual} to be package bins ${expectedBins}`; if (isWindows) { for (var i = 0; i < actual.length; i += 2) { if (!actual[i].includes(expectedBins[i / 2]) || !actual[i + 1].includes(expectedBins[i / 2])) { return { pass: false, message }; } } return { pass: true, message }; } return { pass: actual.every((bin, i) => bin === expectedBins[i]), message }; } export async function toBeValidBin(actual: string, expectedLinkPath: string) { const message = () => `Expected ${actual} to be a link to ${expectedLinkPath}`; if (isWindows) { const contents = await readFile(actual + ".bunx", "utf16le"); const expected = expectedLinkPath.slice(3); return { pass: contents.includes(expected), message }; } return { pass: (await readlink(actual)) === expectedLinkPath, message }; } export async function toBeWorkspaceLink(actual: string, expectedLinkPath: string) { const message = () => `Expected ${actual} to be a link to ${expectedLinkPath}`; if (isWindows) { if (isAbsolute(actual)) { // junctions on windows will have an absolute path return { pass: actual.includes(expectedLinkPath.split("..").at(-1)!), message, }; } return { pass: actual === expectedLinkPath, message, }; } const pass = actual === expectedLinkPath; return { pass, message }; } export function getMaxFD(): number { if (isMacOS || isLinux) { let max = -1; // https://github.com/python/cpython/commit/e21a7a976a7e3368dc1eba0895e15c47cb06c810 for (let entry of fs.readdirSync(isMacOS ? "/dev/fd" : "/proc/self/fd")) { const fd = parseInt(entry.trim(), 10); if (Number.isSafeInteger(fd) && fd >= 0) { max = Math.max(max, fd); } } if (max >= 0) { return max; } } const maxFD = openSync("/dev/null", "r"); closeSync(maxFD); return maxFD; } // This is extremely frowned upon but I think it's easier to deal with than // remembering to do this manually everywhere declare global { interface Buffer { /** * **INTERNAL USE ONLY, NOT An API IN BUN** */ toUnixString(): string; } interface String { /** * **INTERNAL USE ONLY, NOT An API IN BUN** */ isLatin1(): boolean; /** * **INTERNAL USE ONLY, NOT An API IN BUN** */ isUTF16(): boolean; } } Buffer.prototype.toUnixString = function () { return this.toString("utf-8").replaceAll("\r\n", "\n"); }; export function dockerExe(): string | null { return which("docker") || which("podman") || null; } export function isDockerEnabled(): boolean { const dockerCLI = dockerExe(); if (!dockerCLI) { if (isCI && isLinux) { throw new Error("A functional `docker` is required in CI for some tests."); } return false; } // TODO: investigate why Docker tests are not working on Linux arm64 if (isLinux && process.arch === "arm64") { return false; } try { const info = execSync(`"${dockerCLI}" info`, { stdio: ["ignore", "pipe", "inherit"] }); return info.toString().indexOf("Server Version:") !== -1; } catch { if (isCI && isLinux) { throw new Error("A functional `docker` is required in CI for some tests."); } return false; } } export async function waitForPort(port: number, timeout: number = 60_000): Promise { let deadline = Date.now() + Math.max(1, timeout); let error: unknown; while (Date.now() < deadline) { error = await new Promise(resolve => { Bun.connect({ hostname: "localhost", port, socket: { data: socket => { resolve(undefined); socket.end(); }, end: () => resolve(new Error("Socket closed")), error: (_, cause) => resolve(new Error("Socket error", { cause })), connectError: (_, cause) => resolve(new Error("Socket connect error", { cause })), }, }); }); if (error) { await Bun.sleep(1000); } else { return; } } throw error; } export async function describeWithContainer( label: string, { image, env = {}, args = [], archs, concurrent = false, }: { image: string; env?: Record; args?: string[]; archs?: NodeJS.Architecture[]; concurrent?: boolean; }, fn: (container: { port: number; host: string; ready: Promise }) => void, ) { // Skip if Docker is not available if (!isDockerEnabled()) { describe.todo(label); return; } (concurrent && Bun.version !== "1.2.22" ? describe.concurrent : describe)(label, () => { // Check if this is one of our docker-compose services const services: Record = { "postgres_plain": 5432, "postgres_tls": 5432, "postgres_auth": 5432, "mysql_plain": 3306, "mysql_native_password": 3306, "mysql_tls": 3306, "mysql:8": 3306, // Map mysql:8 to mysql_plain "mysql:9": 3306, // Map mysql:9 to mysql_native_password "redis_plain": 6379, "redis_unified": 6379, "minio": 9000, "autobahn": 9002, }; const servicePort = services[image]; if (servicePort) { // Map mysql:8 and mysql:9 based on environment variables let actualService = image; if (image === "mysql:8" || image === "mysql:9") { if (env.MYSQL_ROOT_PASSWORD === "bun") { actualService = "mysql_native_password"; // Has password "bun" } else if (env.MYSQL_ALLOW_EMPTY_PASSWORD === "yes") { actualService = "mysql_plain"; // No password } else { actualService = "mysql_plain"; // Default to no password } } // Create a container descriptor with stable references and a ready promise let readyResolver: () => void; let readyRejecter: (error: any) => void; const readyPromise = new Promise((resolve, reject) => { readyResolver = resolve; readyRejecter = reject; }); // Internal state that will be updated when container is ready let _host = "127.0.0.1"; let _port = 0; // Container descriptor with live getters and ready promise const containerDescriptor = { get host() { return _host; }, get port() { return _port; }, ready: readyPromise, }; // Start the service before any tests beforeAll(async () => { try { const dockerHelper = await import("./docker/index.ts"); const info = await dockerHelper.ensure(actualService as any); _host = info.host; _port = info.ports[servicePort]; console.log(`Container ready via docker-compose: ${image} at ${_host}:${_port}`); readyResolver!(); } catch (error) { readyRejecter!(error); throw error; } }); fn(containerDescriptor); return; } // No fallback - if the image isn't in docker-compose, it should fail throw new Error( `Image "${image}" is not configured in docker-compose.yml. All test containers must use docker-compose.`, ); }); } export function osSlashes(path: string) { return isWindows ? path.replace(/\//g, "\\") : path; } import * as child_process from "node:child_process"; import { basename } from "node:path"; class WriteBlockedError extends Error { constructor(time) { super("Write blocked for " + (time | 0) + "ms"); this.name = "WriteBlockedError"; } } function failTestsOnBlockingWriteCall() { const prop = Object.getOwnPropertyDescriptor(child_process.ChildProcess.prototype, "stdin"); const didAttachSymbol = Symbol("kDidAttach"); if (prop) { Object.defineProperty(child_process.ChildProcess.prototype, "stdin", { ...prop, get() { const actual = prop.get.call(this); if (actual?.write && !actual.__proto__[didAttachSymbol]) { actual.__proto__[didAttachSymbol] = true; attachWriteMeasurement(actual); } return actual; }, }); } function attachWriteMeasurement(stream) { const prop = Object.getOwnPropertyDescriptor(stream.__proto__, "write"); if (prop) { Object.defineProperty(stream.__proto__, "write", { ...prop, value(chunk, encoding, cb) { const start = performance.now(); const rc = prop.value.apply(this, arguments); const end = performance.now(); if (end - start > 8) { const err = new WriteBlockedError(end - start); throw err; } return rc; }, }); } } } failTestsOnBlockingWriteCall(); export function dumpStats() { const stats = heapStats(); const { objectTypeCounts, protectedObjectTypeCounts } = stats; console.log({ objects: Object.fromEntries(Object.entries(objectTypeCounts).sort()), protected: Object.fromEntries(Object.entries(protectedObjectTypeCounts).sort()), }); } export function fillRepeating(dstBuffer: NodeJS.TypedArray, start: number, end: number) { let len = dstBuffer.length, // important: use indices length, not byte-length sLen = end - start, p = sLen; // set initial position = source sequence length // step 2: copy existing data doubling segment length per iteration while (p < len) { if (p + sLen > len) sLen = len - p; // if not power of 2, truncate last segment dstBuffer.copyWithin(p, start, sLen); // internal copy p += sLen; // add current length to offset sLen <<= 1; // double length for next segment } } function makeFlatPropertyMap(opts: object) { // return all properties of opts as paths for nested objects with dot notation // like { a: { b: 1 } } => { "a.b": 1 } // combining names of nested objects with dot notation // infinitely deep const ret: any = {}; function recurse(obj: object, path = "") { for (const [key, value] of Object.entries(obj)) { if (value === undefined) continue; if (value && typeof value === "object") { recurse(value, path ? `${path}.${key}` : key); } else { ret[path ? `${path}.${key}` : key] = value; } } } recurse(opts); return ret; } export function toTOMLString(opts: object) { // return a TOML string of the given options const props = makeFlatPropertyMap(opts); let ret = ""; for (const [key, value] of Object.entries(props)) { if (value === undefined) continue; ret += `${key} = ${JSON.stringify(value)}` + "\n"; } return ret; } const shebang_posix = (program: string) => `#!/usr/bin/env ${program} `; const shebang_windows = (program: string) => `0) { var decoder = new (require("string_decoder").StringDecoder)("utf8"); var str = ""; for await (const chunk of iter) { str += decoder.write(chunk); let i = str.indexOf("\n"); while (i >= 0) { yield str.slice(0, i); str = str.slice(i + 1); i = str.indexOf("\n"); } } str += decoder.end(); { let i = str.indexOf("\n"); while (i >= 0) { yield str.slice(0, i); str = str.slice(i + 1); i = str.indexOf("\n"); } } if (str.length > 0) { yield str; } } export function joinP(...paths: string[]) { return join(...paths).replaceAll("\\", "/"); } /** * TODO: see if this is the default behavior of node child_process APIs if so, * we need to do case-insensitive stuff within our Bun.spawn implementation * * Windows has case-insensitive environment variables, so sometimes an * object like { Path: "...", PATH: "..." } will be passed. Bun lets * the first one win, but we really want the LAST one to win. * * This is mostly needed if you want to override env vars, such like: * env: { * ...bunEnv, * PATH: "my path override here", * } * becomes * env: mergeWindowEnvs([ * bunEnv, * { * PATH: "my path override here", * }, * ]) */ export function mergeWindowEnvs(envs: Record[]) { const keys: Record = {}; const flat: Record = {}; for (const env of envs) { for (const key in env) { if (!env[key]) continue; const normalized = (keys[key.toUpperCase()] ??= key); flat[normalized] = env[key]; } } return flat; } export function tmpdirSync(pattern: string = "bun.test."): string { return fs.mkdtempSync(join(fs.realpathSync.native(os.tmpdir()), pattern)); } export async function runBunInstall( env: NodeJS.Dict, cwd: string, options: { allowWarnings?: boolean; allowErrors?: boolean; expectedExitCode?: number | null; savesLockfile?: boolean; production?: boolean; frozenLockfile?: boolean; saveTextLockfile?: boolean; packages?: string[]; verbose?: boolean; } = {}, ) { const production = options?.production ?? false; const args = [bunExe(), "install"]; if (options?.packages) { args.push(...options.packages); } if (production) { args.push("--production"); } if (options?.frozenLockfile) { args.push("--frozen-lockfile"); } if (options?.saveTextLockfile) { args.push("--save-text-lockfile"); } if (options?.verbose) { args.push("--verbose"); } const { stdout, stderr, exited } = Bun.spawn({ cmd: args, cwd, stdout: "pipe", stdin: "ignore", stderr: "pipe", env, }); expect(stdout).toBeDefined(); expect(stderr).toBeDefined(); let err: string = stderrForInstall(await stderr.text()); expect(err).not.toContain("panic:"); if (!options?.allowErrors) { expect(err).not.toContain("error:"); } if (!options?.allowWarnings) { expect(err).not.toContain("warn:"); } if ((options?.savesLockfile ?? true) && !production && !options?.frozenLockfile) { expect(err).toContain("Saved lockfile"); } let out: string = await stdout.text(); expect(await exited).toBe(options?.expectedExitCode ?? 0); return { out, err, exited }; } // stderr with `slow filesystem` warning removed export function stderrForInstall(err: string) { return err.replace(/warn: Slow filesystem.*/g, ""); } export async function runBunUpdate( env: NodeJS.ProcessEnv, cwd: string, args?: string[], ): Promise<{ out: string[]; err: string; exitCode: number }> { const { stdout, stderr, exited } = Bun.spawn({ cmd: [bunExe(), "update", ...(args ?? [])], cwd, stdout: "pipe", stdin: "ignore", stderr: "pipe", env, }); let err = await stderr.text(); let out = await stdout.text(); let exitCode = await exited; if (exitCode !== 0) { console.log("stdout:", out); console.log("stderr:", err); expect().fail("bun update failed"); } return { out: out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/), err, exitCode }; } export async function pack(cwd: string, env: NodeJS.Dict, ...args: string[]) { const { stdout, stderr, exited } = Bun.spawn({ cmd: [bunExe(), "pm", "pack", ...args], cwd, stdout: "pipe", stderr: "pipe", stdin: "ignore", env, }); const err = await stderr.text(); expect(err).not.toContain("error:"); expect(err).not.toContain("warning:"); expect(err).not.toContain("failed"); expect(err).not.toContain("panic:"); const out = await stdout.text(); const exitCode = await exited; expect(exitCode).toBe(0); return { out, err }; } // If you need to modify, clone it export const expiredTls = Object.freeze({ cert: "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAKLdQVPy90jjMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\nBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\naWRnaXRzIFB0eSBMdGQwHhcNMTkwMjAzMTQ0OTM1WhcNMjAwMjAzMTQ0OTM1WjBF\nMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\nZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEA7i7IIEdICTiSTVx+ma6xHxOtcbd6wGW3nkxlCkJ1UuV8NmY5ovMsGnGD\nhJJtUQ2j5ig5BcJUf3tezqCNW4tKnSOgSISfEAKvpn2BPvaFq3yx2Yjz0ruvcGKp\nDMZBXmB/AAtGyN/UFXzkrcfppmLHJTaBYGG6KnmU43gPkSDy4iw46CJFUOupc51A\nFIz7RsE7mbT1plCM8e75gfqaZSn2k+Wmy+8n1HGyYHhVISRVvPqkS7gVLSVEdTea\nUtKP1Vx/818/HDWk3oIvDVWI9CFH73elNxBkMH5zArSNIBTehdnehyAevjY4RaC/\nkK8rslO3e4EtJ9SnA4swOjCiqAIQEwIDAQABo1AwTjAdBgNVHQ4EFgQUv5rc9Smm\n9c4YnNf3hR49t4rH4yswHwYDVR0jBBgwFoAUv5rc9Smm9c4YnNf3hR49t4rH4ysw\nDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATcL9CAAXg0u//eYUAlQa\nL+l8yKHS1rsq1sdmx7pvsmfZ2g8ONQGfSF3TkzkI2OOnCBokeqAYuyT8awfdNUtE\nEHOihv4ZzhK2YZVuy0fHX2d4cCFeQpdxno7aN6B37qtsLIRZxkD8PU60Dfu9ea5F\nDDynnD0TUabna6a0iGn77yD8GPhjaJMOz3gMYjQFqsKL252isDVHEDbpVxIzxPmN\nw1+WK8zRNdunAcHikeoKCuAPvlZ83gDQHp07dYdbuZvHwGj0nfxBLc9qt90XsBtC\n4IYR7c/bcLMmKXYf0qoQ4OzngsnPI5M+v9QEHvYWaKVwFY4CTcSNJEwfXw+BAeO5\nOA==\n-----END CERTIFICATE-----", key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDuLsggR0gJOJJN\nXH6ZrrEfE61xt3rAZbeeTGUKQnVS5Xw2Zjmi8ywacYOEkm1RDaPmKDkFwlR/e17O\noI1bi0qdI6BIhJ8QAq+mfYE+9oWrfLHZiPPSu69wYqkMxkFeYH8AC0bI39QVfOSt\nx+mmYsclNoFgYboqeZTjeA+RIPLiLDjoIkVQ66lznUAUjPtGwTuZtPWmUIzx7vmB\n+pplKfaT5abL7yfUcbJgeFUhJFW8+qRLuBUtJUR1N5pS0o/VXH/zXz8cNaTegi8N\nVYj0IUfvd6U3EGQwfnMCtI0gFN6F2d6HIB6+NjhFoL+QryuyU7d7gS0n1KcDizA6\nMKKoAhATAgMBAAECggEAd5g/3o1MK20fcP7PhsVDpHIR9faGCVNJto9vcI5cMMqP\n6xS7PgnSDFkRC6EmiLtLn8Z0k2K3YOeGfEP7lorDZVG9KoyE/doLbpK4MfBAwBG1\nj6AHpbmd5tVzQrnNmuDjBBelbDmPWVbD0EqAFI6mphXPMqD/hFJWIz1mu52Kt2s6\n++MkdqLO0ORDNhKmzu6SADQEcJ9Suhcmv8nccMmwCsIQAUrfg3qOyqU4//8QB8ZM\njosO3gMUesihVeuF5XpptFjrAliPgw9uIG0aQkhVbf/17qy0XRi8dkqXj3efxEDp\n1LSqZjBFiqJlFchbz19clwavMF/FhxHpKIhhmkkRSQKBgQD9blaWSg/2AGNhRfpX\nYq+6yKUkUD4jL7pmX1BVca6dXqILWtHl2afWeUorgv2QaK1/MJDH9Gz9Gu58hJb3\nymdeAISwPyHp8euyLIfiXSAi+ibKXkxkl1KQSweBM2oucnLsNne6Iv6QmXPpXtro\nnTMoGQDS7HVRy1on5NQLMPbUBQKBgQDwmN+um8F3CW6ZV1ZljJm7BFAgNyJ7m/5Q\nYUcOO5rFbNsHexStrx/h8jYnpdpIVlxACjh1xIyJ3lOCSAWfBWCS6KpgeO1Y484k\nEYhGjoUsKNQia8UWVt+uWnwjVSDhQjy5/pSH9xyFrUfDg8JnSlhsy0oC0C/PBjxn\nhxmADSLnNwKBgQD2A51USVMTKC9Q50BsgeU6+bmt9aNMPvHAnPf76d5q78l4IlKt\nwMs33QgOExuYirUZSgjRwknmrbUi9QckRbxwOSqVeMOwOWLm1GmYaXRf39u2CTI5\nV9gTMHJ5jnKd4gYDnaA99eiOcBhgS+9PbgKSAyuUlWwR2ciL/4uDzaVeDQKBgDym\nvRSeTRn99bSQMMZuuD5N6wkD/RxeCbEnpKrw2aZVN63eGCtkj0v9LCu4gptjseOu\n7+a4Qplqw3B/SXN5/otqPbEOKv8Shl/PT6RBv06PiFKZClkEU2T3iH27sws2EGru\nw3C3GaiVMxcVewdg1YOvh5vH8ZVlxApxIzuFlDvnAoGAN5w+gukxd5QnP/7hcLDZ\nF+vesAykJX71AuqFXB4Wh/qFY92CSm7ImexWA/L9z461+NKeJwb64Nc53z59oA10\n/3o2OcIe44kddZXQVP6KTZBd7ySVhbtOiK3/pCy+BQRsrC7d71W914DxNWadwZ+a\njtwwKjDzmPwdIXDSQarCx0U=\n-----END PRIVATE KEY-----", passphrase: "1234", }); // openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ // -keyout localhost.key \ // -out localhost.crt \ // -subj "/C=US/ST=CA/L=San Francisco/O=Oven/OU=Team Bun/CN=server-bun" \ // -addext "subjectAltName = DNS:localhost,IP:127.0.0.1,IP:::1" // notAfter=Sep 4 03:00:49 2035 GMT export const tls = Object.freeze({ cert: "-----BEGIN CERTIFICATE-----\nMIID4jCCAsqgAwIBAgIUcaRq6J/YF++Bo01Zc+HeQvCbnWMwDQYJKoZIhvcNAQEL\nBQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nbmNpc2NvMQ0wCwYDVQQKDARPdmVuMREwDwYDVQQLDAhUZWFtIEJ1bjETMBEGA1UE\nAwwKc2VydmVyLWJ1bjAeFw0yNTA5MDYwMzAwNDlaFw0zNTA5MDQwMzAwNDlaMGkx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj\nbzENMAsGA1UECgwET3ZlbjERMA8GA1UECwwIVGVhbSBCdW4xEzARBgNVBAMMCnNl\ncnZlci1idW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDlYzosgRgX\nHL6vMh1V0ERFhsvlZrtRojSw6tafr3SQBphU793/rGiYZlL/lJ9HIlLkx9JMbuTj\nNm5U2eRwHiTQIeWD4aCIESwPlkdaVYtC+IOj55bJN8xNa7h5GyJwF7PnPetAsKyE\n8DMBn1gKMhaIis7HHOUtk4/K3Y4peU44d04z0yPt6JtY5Sbvi1E7pGX6T/2c9sHs\ndIDeDctWnewpXXs8zkAla0KNWQfpDnpS53wxAfStTA4lSrA9daxC7hZopQlLxFIb\nJk+0BLbEsXtrJ54T5iguHk+2MDVAy4MOqP9XbKV7eGHk73l6+CSwmHyHBxh4ChxR\nQeT5BP0MUTn1AgMBAAGjgYEwfzAdBgNVHQ4EFgQUw7nEnh4uOdZVZUapQzdAUaVa\nAn0wHwYDVR0jBBgwFoAUw7nEnh4uOdZVZUapQzdAUaVaAn0wDwYDVR0TAQH/BAUw\nAwEB/zAsBgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAA\nAAEwDQYJKoZIhvcNAQELBQADggEBAEA8r1fvDLMSCb8bkAURpFk8chn8pl5MChzT\nYUDaLdCCBjPXJkSXNdyuwS+T/ljAGyZbW5xuDccCNKltawO4CbyEXUEZbYr3w9eq\nj8uqymJPhFf0O1rKOI2han5GBCgHwG13QwKI+4uu7390nD+TlzLOhxFfvOG7OadH\nQNMNLNyldgF4Nb8vWdz0FtQiGUIrO7iq4LFhhd1lCxe0q+FAYSEYcc74WtF/Yo8V\nJQauXuXyoP5FqLzNt/yeNQhceyIXJGKCsjr5/bASBmVlCwgRfsD3jpG37L8YCJs1\nL4WEikcY4Lzb2NF9e94IyZdQsRqd9DFBF5zP013MSUiuhiow32k=\n-----END CERTIFICATE-----\n", key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDlYzosgRgXHL6v\nMh1V0ERFhsvlZrtRojSw6tafr3SQBphU793/rGiYZlL/lJ9HIlLkx9JMbuTjNm5U\n2eRwHiTQIeWD4aCIESwPlkdaVYtC+IOj55bJN8xNa7h5GyJwF7PnPetAsKyE8DMB\nn1gKMhaIis7HHOUtk4/K3Y4peU44d04z0yPt6JtY5Sbvi1E7pGX6T/2c9sHsdIDe\nDctWnewpXXs8zkAla0KNWQfpDnpS53wxAfStTA4lSrA9daxC7hZopQlLxFIbJk+0\nBLbEsXtrJ54T5iguHk+2MDVAy4MOqP9XbKV7eGHk73l6+CSwmHyHBxh4ChxRQeT5\nBP0MUTn1AgMBAAECggEABtPvC5uVGr0DjQX2GxONsK8cOxoVec7U+C4pUMwBcXcM\nyjxwlHdujpi/IDXtjsm+A2rSPu2vGPdKDfMFanPvPxW/Ne99noc6U0VzHsR8lnP8\nwSB328nyJhzOeyZcXk9KTtgIPF7156gZsJLsZTNL+ej90i3xQWvKxCxXmrLuad5O\nz/TrgZkC6wC3fgj1d3e8bMljQ7tLxbshJMYVI5o6RFTxy84DLI+rlvPkf7XbiMPf\n2lsm4jcJKvfx+164HZJ9QVlx8ncqOHAnGvxb2xHHfqv4JAbz615t7yRvtaw4Paj5\n6kQSf0VWnsVzgxNJWvnUZym/i/Qf5nQafjChCyKOEQKBgQD9f4SkvJrp/mFKWLHd\nkDvRpSIIltfJsa5KShn1IHsQXFwc0YgyP4SKQb3Ckv+/9UFHK9EzM+WlPxZi7ZOS\nhsWhIfkI4c4ORpxUQ+hPi0K2k+HIY7eYyONqDAzw5PGkKBo3mSGMHDXYywSqexhB\nCCMHuHdMhwyHdz4PWYOK3C2VMQKBgQDnpsrHK7lM9aVb8wNhTokbK5IlTSzH/5oJ\nlAVu6G6H3tM5YQeoDXztbZClvrvKU8DU5UzwaC+8AEWQwaram29QIDpAI3nVQQ0k\ndmHHp/pCeADdRG2whaGcl418UJMMv8AUpWTRm+kVLTLqfTHBC0ji4NlCQMHCUCfd\nU8TeUi5QBQKBgQDvJNd7mboDOUmLG7VgMetc0Y4T0EnuKsMjrlhimau/OYJkZX84\n+BcPXwmnf4nqC3Lzs3B9/12L0MJLvZjUSHQ0mJoZOPxtF0vvasjEEbp0B3qe0wOn\nDQ0NRCUJNNKJbJOfE8VEKnDZ/lx+f/XXk9eINwvElDrLqUBQtr+TxjbyYQKBgAxQ\nlZ8Y9/TbajsFJDzcC/XhzxckjyjisbGoqNFIkfevJNN8EQgiD24f0Py+swUChtHK\njtiI8WCxMwGLCiYs9THxRKd8O1HW73fswy32BBvcfU9F//7OW9UTSXY+YlLfLrrq\nP/3UqAN0L6y/kxGMJAfLpEEdaC+IS1Y8yc531/ZxAoGASYiasDpePtmzXklDxk3h\njEw64QAdXK2p/xTMjSeTtcqJ7fvaEbg+Mfpxq0mdTjfbTdR9U/nzAkwS7OoZZ4Du\nueMVls0IVqcNnBtikG8wgdxN27b5JPXS+GzQ0zDSpWFfRPZiIh37BAXr0D1voluJ\nrEHkcals6p7hL98BoxjFIvA=\n-----END PRIVATE KEY-----\n", }); export const invalidTls = Object.freeze({ cert: "-----BEGIN CERTIFICATE-----\nBQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nBQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nbmNpc2NvMQ0wCwYDVQQKDARPdmVuMREwDwYDVQQLDAhUZWFtIEJ1bjETMBEGA1UE\nAwwKc2VydmVyLWJ1bjAeFw0yNTAyMDQwNDUyNTdaFw0yNzAyMDQwNDUyNTdaMGkx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj\nbzENMAsGA1UECgwET3ZlbjERMA8GA1UECwwIVGVhbSBCdW4xEzARBgNVBAMMCnNl\ncnZlci1idW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1rZqCnASs\nHPzPjs/mls+z3qTl6OsCNI+kTsA23/+ZkvtBe7EI+9LfV1Sy4MF66ZovR0UgeJUB\nlL7ExadXkfZJS0N6LEAIyEMQI0cpILv3i6sJCcRwHV7X7N55lkUdsJtQ3fSKsyn9\nPDWJGVdwtRjdod3XyevYcx5NLGZOF/4KJmR4eNkX8ycG8zvW/srPHHE95/+k/5Wo\n/RrS+OLl+bgVznxmXtnFMdbYvJ1RLyipCED2P569NWXAgCzYESX2tqLr20R8ca8Q\niTcXXijY1Wq+pVR5NhIckt+zyZlUQ5IT3DvAQn4aW30wA514k1AKDKQjtxdRzVmV\nGDOTOzAlpmeZAgMBAAGjTzBNMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQ\nAAAAAAAAAAAAAAAAAAAAATAdBgNVHQ4EFgQUkgeZUw9BZc/9mxAym4BjaVYhHoow\nDQYJKoZIhvcNAQELBQADggEBAJGQomt68rO1wuhHaG355kGaIsTsoUJgs7VAKNI2\n0/vtMKODX2Zo2BHhiI1wSH751IySqWbGYCvXl6QrsV5tD/jdIYKvyXLFmV0KgQSY\nkZ91sde4jIiiqL5h03GJSUydCl4SE1A1H8Ht41NYoyVaWVPzv5lprt654vpRPbPq\nYBQTWSFcYkmGnza/tRALUftM5U3yKOTQ8sKH/eKGC9KU0DI5pZ2XAxrIyvrJZMm1\n0WwWTrO0KlXN8N9v8tVCVm7g6mYug4HEADQ4kymyfwM6mPY1EmsGy36KOqCRUtUR\n+jmAZr9m+l+27GxR9zjxoLWHkARuWZM/hL//u90cNfNDRgQ=\n-----END CERTIFICATE-----\n", key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1rZqCnASsHPzP\njs/mls+z3qTl6OsCNI+kTsA23/+ZkvtBe7EI+9LfV1Sy4MF66ZovR0UgeJUBlL7E\nxadXkfZJS0N6LEAIyEMQI0cpILv3i6sJCcRwHV7X7N55lkUdsJtQ3fSKsyn9PDWJ\nGVdwtRjdod3XyevYcx5NLGZOF/4KJmR4eNkX8ycG8zvW/srPHHE95/+k/5Wo/RrS\n+OLl+bgVznxmXtnFMdbYvJ1RLyipCED2P569NWXAgCzYESX2tqLr20R8ca8QiTcX\nXijY1Wq+pVR5NhIckt+zyZlUQ5IT3DvAQn4aW30wA514k1AKDKQjtxdRzVmVGDOT\nOzAlpmeZAgMBAAECggEANW6F2zTckOwDlF2pmmUvV/S6pZ1/hIoF1uqMUHdHmpim\nSaeBtSUu6x2pnORKMwaCILaCx55/IFRpWMDSywf0GbFHeqaJ/Ks9QgFGG/vzHEZY\n+pMDUX/p1XJmKfc+g5Fd1IY6thIkXsR28EfiNhUk54YEE0NhGCsfNc5BlmUrAzuw\nSevCkbChsZzLoasskt5hgWOb1wT757xDrOOss3LXvwaFkMXANQHiaGWxSpmyXTVf\nmtIX4wpN2K5BQxRBV6xmRaBBp7fWJlbqvV67wwh2cxIAyvQ68VVVHTbfv44TUw62\nyCKle6hSLi/OnMr1FJv16Ez+K3lUIkYE0nTYIvQkYwKBgQD34Nwy0U8WcHcOSbU1\nMIREfvPNYfl/hH94wmLyKZWhpqOfqgrUg+19+n9TXw4DGdO7lwAzz+ud7TImbGDI\n+1cb9/FxTK5cRwICTLC+Pse6pVkKUvPdil/TfHZBJP1jeIMGMDVi0fcGv97LxrHV\nJGQwA5x1nHGHl0JrENRqm3M2NwKBgQC7oXkWb0s2C8ANI14gz+q/2EmTSJxxrzXR\nz5EQk87PmPfkY4I1T8vKFcaiJynyReLwpYTip2WYGqc7qAO9oLwmA+d/NMOBI2sg\noEn154Q9zvr3jqIgu9/AapEgEDlA+v18veoIz3bae6wu57lpGvGtCoQLBS6q2UZg\n3zFI3BJorwKBgDz4WjFFuqZSU3Z4OtIydNZEQ8Oo7a2n8ZLKfXwDLoLsciK7uJ49\nNRVfoCHpp5CrsaDaq3oTEmluBn/c+JF3AR4oBoNP0TNxY9Uc9/xThN0r/pLDhKhh\neOCUJKIxbwIgilnjUb5U1uYaG7sTzHoY0Wvd94YWTPaFBhk/sn/mbJhRAoGAA+/E\nWZsmKdEfS2dFj0ytcS75hDSOy7fQWkGPmphvS127Pbh0v+eXr/q6+yX1NFcRBtmC\nKzs133YXsiG5Sl439Fg6oCmcPHZgxgN26cjctmtESrNcZXFrpV7XAqQ0f0+Ex/w4\nD81Cghz8JNPJyRG+plHFKXIHY6BBYMDuCMhNPpMCgYEA1BVG5scNtmBE3SaRns2G\npKgWiwmzPDTqwf3R0rgkHQroZUIz616jLgKXIVMBPaq/771uq+hzJZro9sNcNL8p\n9PkLRr4V4KtUSqkjvitU68vMM1qxtO9NVwCI5u3wicVC5mMqcH8FN+sO/5/jIPBl\nO/qEOVDlCuYtURcnh/Oz1cE=\n-----END PRIVATE KEY-----\n", }); export function disableAggressiveGCScope() { const gc = Bun.unsafe.gcAggressionLevel(0); return { [Symbol.dispose]() { Bun.unsafe.gcAggressionLevel(gc); }, }; } String.prototype.isLatin1 = function () { return require("bun:internal-for-testing").jscInternals.isLatin1String(this); }; String.prototype.isUTF16 = function () { return require("bun:internal-for-testing").jscInternals.isUTF16String(this); }; interface BunHarnessTestMatchers { toBeLatin1String(): void; toBeUTF16String(): void; toHaveTestTimedOutAfter(expected: number): void; toBeBinaryType(expected: keyof typeof binaryTypes): void; toRun(optionalStdout?: string, expectedCode?: number): void; toThrowWithCode(cls: CallableFunction, code: string): void; toThrowWithCodeAsync(cls: CallableFunction, code: string): Promise; } declare module "bun:test" { interface Matchers extends BunHarnessTestMatchers {} interface AsymmetricMatchers extends BunHarnessTestMatchers {} } /** * Set `NODE_TLS_REJECT_UNAUTHORIZED` for a scope. */ export function rejectUnauthorizedScope(value: boolean) { const original_rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED; process.env.NODE_TLS_REJECT_UNAUTHORIZED = value ? "1" : "0"; return { [Symbol.dispose]() { process.env.NODE_TLS_REJECT_UNAUTHORIZED = original_rejectUnauthorized; }, }; } let networkInterfaces: any; function isIP(type: "IPv4" | "IPv6") { if (!networkInterfaces) { networkInterfaces = os.networkInterfaces(); } for (const networkInterface of Object.values(networkInterfaces)) { for (const { family } of networkInterface as any[]) { if (family === type) { return true; } } } return false; } export function isIPv6() { // FIXME: AWS instances on Linux for Buildkite are not setup with IPv6 if (isBuildKite && isLinux) { return false; } return isIP("IPv6"); } export function isIPv4() { return isIP("IPv4"); } let glibcVersion: string | undefined; export function getGlibcVersion() { if (glibcVersion || !isLinux) { return glibcVersion; } try { const { header } = process.report!.getReport() as any; const { glibcVersionRuntime: version } = header; if (typeof version === "string") { return (glibcVersion = version); } } catch (error) { console.warn("Failed to detect glibc version", error); } } export function isGlibcVersionAtLeast(version: string): boolean { const glibcVersion = getGlibcVersion(); if (!glibcVersion) { return false; } return Bun.semver.satisfies(glibcVersion, `>=${version}`); } let macOSVersion: string | undefined; export function getMacOSVersion(): string | undefined { if (macOSVersion || !isMacOS) { return macOSVersion; } try { const { stdout } = Bun.spawnSync({ cmd: ["sw_vers", "-productVersion"], }); return (macOSVersion = stdout.toString().trim()); } catch (error) { console.warn("Failed to detect macOS version:", error); } } export function isMacOSVersionAtLeast(minVersion: number): boolean { const macOSVersion = getMacOSVersion(); if (!macOSVersion) { return false; } return parseFloat(macOSVersion) >= minVersion; } export function readableStreamFromArray(array) { return new ReadableStream({ pull(controller) { for (let entry of array) { controller.enqueue(entry); } controller.close(); }, }); } let hasGuardMalloc = -1; export function forceGuardMalloc(env) { if (process.platform !== "darwin") { return; } if (hasGuardMalloc === -1) { hasGuardMalloc = Number(fs.existsSync("/usr/lib/libgmalloc.dylib")); } if (hasGuardMalloc === 1) { env.DYLD_INSERT_LIBRARIES = "/usr/lib/libgmalloc.dylib"; env.MALLOC_PROTECT_BEFORE = "1"; env.MallocScribble = "1"; env.MallocGuardEdges = "1"; env.MALLOC_FILL_SPACE = "1"; env.MALLOC_STRICT_SIZE = "1"; } else { console.warn("Guard malloc is not available on this platform for some reason."); } } export function fileDescriptorLeakChecker() { const initial = getMaxFD(); return { [Symbol.dispose]() { const current = getMaxFD(); if (current > initial) { throw new Error(`File descriptor leak detected: ${current} (current) > ${initial} (initial)`); } }, }; } /** * Gets a secret from the environment. * * In Buildkite, secrets must be retrieved using the `buildkite-agent secret get` command * and are not available as an environment variable. */ export function getSecret(name: string): string | undefined { let value = process.env[name]?.trim(); // When not running in CI, allow the secret to be missing. if (!isCI) { return value; } // In Buildkite, secrets must be retrieved using the `buildkite-agent secret get` command if (!value && isBuildKite) { const { exitCode, stdout } = spawnSync({ cmd: ["buildkite-agent", "secret", "get", name], stdout: "pipe", env: ciEnv, stderr: "inherit", }); if (exitCode === 0) { value = stdout.toString().trim(); } } // Throw an error if the secret is not found, so the test fails in CI. if (!value) { let hint; if (isBuildKite) { hint = `Create a secret with the name "${name}" in the Buildkite UI. https://buildkite.com/docs/pipelines/security/secrets/buildkite-secrets`; } else { hint = `Define an environment variable with the name "${name}".`; } throw new Error(`Secret not found: ${name}\n${hint}`); } // Set the secret in the environment so that it can be used in tests. process.env[name] = value; return value; } export function assertManifestsPopulated(absCachePath: string, registryUrl: string) { const { npm_manifest_test_helpers } = require("bun:internal-for-testing"); const { parseManifest } = npm_manifest_test_helpers; for (const file of fs.readdirSync(absCachePath)) { if (!file.endsWith(".npm")) continue; const manifest = parseManifest(join(absCachePath, file), registryUrl); expect(manifest.versions.length).toBeGreaterThan(0); } } // Make it easier to run some node tests. Object.defineProperty(globalThis, "gc", { value: Bun.gc, writable: true, enumerable: false, configurable: true, }); export function waitForFileToExist(path: string, interval_ms: number) { while (!fs.existsSync(path)) { sleepSync(interval_ms); } } export function libcPathForDlopen() { switch (process.platform) { case "linux": switch (libcFamily) { case "glibc": return "libc.so.6"; case "musl": return "/usr/lib/libc.so"; } case "darwin": return "libc.dylib"; default: throw new Error("TODO"); } } export function cwdScope(cwd: string) { const original = process.cwd(); process.chdir(cwd); return { [Symbol.dispose]() { process.chdir(original); }, }; } export function rmScope(path: string) { return { [Symbol.dispose]() { fs.rmSync(path, { recursive: true, force: true }); }, }; } export function textLockfile(version: number, pkgs: any): string { return JSON.stringify({ lockfileVersion: version, configVersion: 1, ...pkgs, }); } export class VerdaccioRegistry { port: number; process: ChildProcess | undefined; configPath: string; packagesPath: string; users: Record = {}; constructor(opts?: { configPath?: string; packagesPath?: string; verbose?: boolean }) { this.port = randomPort(); this.configPath = opts?.configPath ?? join(import.meta.dir, "cli", "install", "registry", "verdaccio.yaml"); this.packagesPath = opts?.packagesPath ?? join(import.meta.dir, "cli", "install", "registry", "packages"); } async start(silent: boolean = true) { await rm(join(dirname(this.configPath), "htpasswd"), { force: true }); this.process = fork(require.resolve("verdaccio/bin/verdaccio"), ["-c", this.configPath, "-l", `${this.port}`], { silent, // Prefer using a release build of Bun since it's faster execPath: isCI ? bunExe() : Bun.which("bun") || bunExe(), env: { ...(bunEnv as any), NODE_NO_WARNINGS: "1", }, }); this.process.stderr?.on("data", data => { console.error(`[verdaccio] stderr: ${data}`); }); const started = Promise.withResolvers(); this.process.on("error", error => { console.error(`Failed to start verdaccio: ${error}`); started.reject(error); }); this.process.on("exit", (code, signal) => { if (code !== 0) { console.error(`Verdaccio exited with code ${code} and signal ${signal}`); } else { console.log("Verdaccio exited successfully"); } }); this.process.on("message", (message: { verdaccio_started: boolean }) => { if (message.verdaccio_started) { started.resolve(); } }); await started.promise; } registryUrl() { return `http://localhost:${this.port}/`; } stop() { rmSync(join(dirname(this.configPath), "htpasswd"), { force: true }); this.process?.kill(0); } /** * returns auth token */ async generateUser(username: string, password: string): Promise { if (this.users[username]) { throw new Error(`User ${username} already exists`); } else this.users[username] = password; const url = `http://localhost:${this.port}/-/user/org.couchdb.user:${username}`; const user = { name: username, password: password, email: `${username}@example.com`, }; const response = await fetch(url, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(user), }); if (response.ok) { const data = await response.json(); return data.token; } throw new Error("Failed to create user:", response.statusText); } async authBunfig(user: string) { const authToken = await this.generateUser(user, user); return ` [install] cache = false registry = { url = "http://localhost:${this.port}/", token = "${authToken}" } `; } async createTestDir( opts: { bunfigOpts?: BunfigOpts; files?: DirectoryTree | string } = { bunfigOpts: { linker: "hoisted" }, files: {}, }, ) { await rm(join(dirname(this.configPath), "htpasswd"), { force: true }); await rm(join(this.packagesPath, "private-pkg-dont-touch"), { force: true }); const packageDir = tempDir("verdaccio-test-", opts.files ?? {}); const packageJson = join(packageDir, "package.json"); await this.writeBunfig(packageDir, opts.bunfigOpts); this.users = {}; return { packageDir: String(packageDir), packageJson }; } async writeBunfig(dir: string, opts: BunfigOpts = {}) { let bunfig = ` [install] cache = "${join(dir, ".bun-cache").replaceAll("\\", "\\\\")}" `; if ("saveTextLockfile" in opts) { bunfig += `saveTextLockfile = ${opts.saveTextLockfile} `; } if (!opts.npm) { bunfig += `registry = "${this.registryUrl()}"\n`; } if (opts.linker) { bunfig += `linker = "${opts.linker}"\n`; } if (opts.publicHoistPattern) { if (typeof opts.publicHoistPattern === "string") { bunfig += `publicHoistPattern = "${opts.publicHoistPattern}"`; } else { bunfig += `publicHoistPattern = [${opts.publicHoistPattern.map(p => `"${p}"`).join(", ")}]`; } } await write(join(dir, "bunfig.toml"), bunfig); } } type BunfigOpts = { saveTextLockfile?: boolean; npm?: boolean; linker?: "isolated" | "hoisted"; publicHoistPattern?: string | string[]; }; export async function readdirSorted(path: string): Promise { const results = await readdir(path); results.sort(); return results; } /** * Helper function for making automatically lazily-executed promises. * * The difference is that the promise has not already started to be evaluated when it is created, * only when you await it does it execute the function. * * @example * ```ts * function createMyFixture() { * return { * start: lazyPromiseLike(() => fetch("https://example.com")), * stop: lazyPromiseLike(() => fetch("https://example.com")), * } * } * * const { start, stop } = createMyFixture(); * * await start; // Calls only the start function * ``` * * @param fn A function to make lazily evaluated. * @returns A promise-like object that will evaluate the function when `then` is called. */ export function lazyPromiseLike(fn: () => Promise): PromiseLike { let p: Promise; return { then(onfulfilled, onrejected) { if (!p) { p = fn(); } return p.then(onfulfilled, onrejected); }, }; } export async function gunzipJsonRequest(req: Request) { const buf = await req.arrayBuffer(); const inflated = Bun.gunzipSync(buf); const body = JSON.parse(Buffer.from(inflated).toString("utf-8")); return body; } /** * HTML content from example.com, used as a test fixture to replace * external calls to example.com with local server responses. */ export const exampleHtml = Buffer.from( "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPHRpdGxlPkV4YW1wbGUgRG9tYWluPC90aXRsZT4KCiAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCIgLz4KICAgIDxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtdHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04IiAvPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xIiAvPgogICAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KICAgICAgYm9keSB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YwZjBmMjsKICAgICAgICBtYXJnaW46IDA7CiAgICAgICAgcGFkZGluZzogMDsKICAgICAgICBmb250LWZhbWlseToKICAgICAgICAgIC1hcHBsZS1zeXN0ZW0sIHN5c3RlbS11aSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAiU2Vnb2UgVUkiLCAiT3BlbiBTYW5zIiwgIkhlbHZldGljYSBOZXVlIiwgSGVsdmV0aWNhLCBBcmlhbCwKICAgICAgICAgIHNhbnMtc2VyaWY7CiAgICAgIH0KICAgICAgZGl2IHsKICAgICAgICB3aWR0aDogNjAwcHg7CiAgICAgICAgbWFyZ2luOiA1ZW0gYXV0bzsKICAgICAgICBwYWRkaW5nOiAyZW07CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2ZkZmRmZjsKICAgICAgICBib3JkZXItcmFkaXVzOiAwLjVlbTsKICAgICAgICBib3gtc2hhZG93OiAycHggM3B4IDdweCAycHggcmdiYSgwLCAwLCAwLCAwLjAyKTsKICAgICAgfQogICAgICBhOmxpbmssCiAgICAgIGE6dmlzaXRlZCB7CiAgICAgICAgY29sb3I6ICMzODQ4OGY7CiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lOwogICAgICB9CiAgICAgIEBtZWRpYSAobWF4LXdpZHRoOiA3MDBweCkgewogICAgICAgIGRpdiB7CiAgICAgICAgICBtYXJnaW46IDAgYXV0bzsKICAgICAgICAgIHdpZHRoOiBhdXRvOwogICAgICAgIH0KICAgICAgfQogICAgPC9zdHlsZT4KICA8L2hlYWQ+CgogIDxib2R5PgogICAgPGRpdj4KICAgICAgPGgxPkV4YW1wbGUgRG9tYWluPC9oMT4KICAgICAgPHA+CiAgICAgICAgVGhpcyBkb21haW4gaXMgZm9yIHVzZSBpbiBpbGx1c3RyYXRpdmUgZXhhbXBsZXMgaW4gZG9jdW1lbnRzLiBZb3UgbWF5IHVzZSB0aGlzIGRvbWFpbiBpbiBsaXRlcmF0dXJlIHdpdGhvdXQKICAgICAgICBwcmlvciBjb29yZGluYXRpb24gb3IgYXNraW5nIGZvciBwZXJtaXNzaW9uLgogICAgICA8L3A+CiAgICAgIDxwPjxhIGhyZWY9Imh0dHBzOi8vd3d3LmlhbmEub3JnL2RvbWFpbnMvZXhhbXBsZSI+TW9yZSBpbmZvcm1hdGlvbi4uLjwvYT48L3A+CiAgICA8L2Rpdj4KICA8L2JvZHk+CjwvaHRtbD4K", "base64", ).toString(); /** * Creates a local HTTP or HTTPS server serving example.com HTML content. * Useful for testing without external network calls. * * @param protocol - Protocol to use: "https" (default) or "http" * @returns Object with server URL, optional CA cert, server instance, and cleanup methods * * @example * ```ts * const site = exampleSite("http"); * const response = await fetch(site.url); * site.stop(); * ``` * * @example Using disposal pattern * ```ts * using site = exampleSite(); * await fetch(site.url, { tls: { ca: site.ca } }); * // server automatically stopped when scope exits * ``` */ export function exampleSite(protocol: "https" | "http" = "https") { const server = Bun.serve({ port: 0, tls: protocol === "https" ? tls : undefined, hostname: "127.0.0.1", fetch(req) { return new Response(exampleHtml, { headers: { "Content-Type": "text/html", }, }); }, }); return { url: server.url, ca: protocol === "https" ? tls.cert : undefined, server, stop() { return server.stop(); }, async [Symbol.asyncDispose]() { await server.stop(); }, }; } export function normalizeBunSnapshot(snapshot: string, optionalDir?: string) { if (optionalDir) { snapshot = snapshot .replaceAll(fs.realpathSync.native(optionalDir).replaceAll("\\", "/"), "") .replaceAll(optionalDir, ""); } // Remove timestamps from test result lines that start with (pass), (fail), (skip), or (todo) snapshot = snapshot.replace(/^((?:pass|fail|skip|todo)\) .+) \[[\d.]+\s?m?s\]$/gm, "$1"); return ( snapshot .replaceAll("\r\n", "\n") .replaceAll("\\", "/") .replaceAll(fs.realpathSync.native(process.cwd()).replaceAll("\\", "/"), "") .replaceAll(fs.realpathSync.native(os.tmpdir()).replaceAll("\\", "/"), "") .replaceAll(fs.realpathSync.native(os.homedir()).replaceAll("\\", "/"), "") // look for [\d\d ms] or [\d\d s] with optional periods .replace(/\s\[[\d.]+\s?m?s\]/gm, "") .replace(/^\[[\d.]+\s?m?s\]/gm, "") // line numbers in stack traces like at FunctionName (NN:NN) // it must specifically look at the stacktrace format .replace(/^\s+at (.*?)\(.*?:\d+(?::\d+)?\)/gm, " at $1(file:NN:NN)") // Handle version strings in error messages like "Bun v1.2.21+revision (platform arch)" // This needs to come before the other version replacements .replace(/Bun v[\d.]+(?:-[\w.]+)?(?:\+[\w]+)?(?:\s+\([^)]+\))?/g, "Bun v") .replaceAll(Bun.version_with_sha, " ()") .replaceAll(Bun.version, "") .replaceAll(Bun.revision, "") .trim() ); } export function nodeModulesPackages(nodeModulesPath: string): string { const packages: string[] = []; function scanDirectory(dir: string, relativePath: string = "") { try { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { if (entry.name === ".bun-cache") { // Skip .bun-cache directories continue; } const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name; // Check if this directory contains a package.json const packageJsonPath = join(fullPath, "package.json"); if (fs.existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); const name = packageJson.name || "unknown"; const version = packageJson.version || "unknown"; packages.push(`${newRelativePath}/${name}@${version}`); } catch { // If package.json is invalid, still include the path packages.push(`${newRelativePath}/[invalid-package.json]`); } } // Recursively scan subdirectories (including nested node_modules) scanDirectory(fullPath, newRelativePath); } } } catch (err) { // Ignore errors for directories we can't read } } scanDirectory(nodeModulesPath); // Sort the packages alphabetically packages.sort(); return packages.join("\n"); }