test: harden dist-bundle regression guard + Node smoke test
Existing test only checked `globalThis.Bun` top-level destructures and `__require` calls. Add two new test cases: 1. Raw Bun runtime API scanner: scans dist/index.js for any `Bun.<anyMethod>(` or `Bun.<anyMethod>.` call outside shim-safe patterns (runtime.Bun, globalThis.Bun, typeof Bun, and the "Bun is not defined" error-message string). Uses a negative lookbehind so shim indirection (`runtime.Bun.foo`) passes. 2. Node smoke test: imports dist/index.js under `node --input-type=module` and asserts stderr contains no `ReferenceError` and no `Bun is not defined`. The existing case 3 only checked exit code, which masked lazy-evaluation crashes that fire after import. Reading exports forces lazy module-level evaluation paths to run.
This commit is contained in:
@@ -1,9 +1,58 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { existsSync } from "node:fs"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
const DIST_INDEX = "dist/index.js"
|
||||
const GLOBAL_BUN_DESTRUCTURE = /^\s*(?:var|let|const)\s*\{[^}]*\}\s*=\s*globalThis\.Bun/gm
|
||||
const TOP_LEVEL_REQUIRE_CALL = "__require("
|
||||
const RAW_BUN_API_CALL = /(?<![.$\w])Bun\.[a-zA-Z_$][a-zA-Z_$0-9]*\s*[.(]/g
|
||||
const NODE_EXPORT_SMOKE_SCRIPT = [
|
||||
"const mod = await import('./dist/index.js');",
|
||||
"const keys = Object.keys(mod).join(',');",
|
||||
"console.log('SMOKE_OK:' + keys);",
|
||||
].join("\n")
|
||||
|
||||
function hasRawBunApiCall(line: string): boolean {
|
||||
RAW_BUN_API_CALL.lastIndex = 0
|
||||
return RAW_BUN_API_CALL.test(line)
|
||||
}
|
||||
|
||||
function isInsideStringLiteral(line: string, position: number): boolean {
|
||||
let quote: "'" | '"' | "`" | null = null
|
||||
let escaped = false
|
||||
|
||||
for (let index = 0; index < position; index += 1) {
|
||||
const char = line.charAt(index)
|
||||
|
||||
if (escaped) {
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
|
||||
if (quote !== null && char === "\\") {
|
||||
escaped = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (char === "'" || char === '"' || char === "`") {
|
||||
if (quote === char) {
|
||||
quote = null
|
||||
} else if (quote === null) {
|
||||
quote = char
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return quote !== null
|
||||
}
|
||||
|
||||
function formatOffendingLine(lineNumber: number, line: string): string {
|
||||
const content = line.trim()
|
||||
const truncated = content.length > 120 ? `${content.slice(0, 117)}...` : content
|
||||
|
||||
return `${lineNumber}: ${truncated}`
|
||||
}
|
||||
|
||||
describe("dist bundle Bun globals", () => {
|
||||
test.skipIf(!existsSync(DIST_INDEX))("#given dist bundle #when scanned #then no globalThis.Bun destructures remain", async () => {
|
||||
@@ -62,4 +111,66 @@ describe("dist bundle Bun globals", () => {
|
||||
stderr: "",
|
||||
})
|
||||
})
|
||||
|
||||
test.skipIf(!existsSync(DIST_INDEX))("#given dist bundle #when scanned for raw Bun runtime APIs #then no unshimmed Bun API calls remain", async () => {
|
||||
expect(hasRawBunApiCall("Bun.file('dist/index.js')")).toBe(true)
|
||||
expect(hasRawBunApiCall("runtime.Bun.file('dist/index.js')")).toBe(false)
|
||||
expect(hasRawBunApiCall(".Bun.file('dist/index.js')")).toBe(false)
|
||||
expect(hasRawBunApiCall("$Bun.file('dist/index.js')")).toBe(false)
|
||||
expect(hasRawBunApiCall("Bun.spawnSync.options")).toBe(true)
|
||||
expect(hasRawBunApiCall("Bun.readableStreamToText(stream)")).toBe(true)
|
||||
|
||||
const dist = await Bun.file(DIST_INDEX).text()
|
||||
const offending: string[] = []
|
||||
let insideJSDoc = false
|
||||
|
||||
for (const [index, line] of dist.split("\n").entries()) {
|
||||
const trimmed = line.trimStart()
|
||||
|
||||
if (insideJSDoc || trimmed.startsWith("/**")) {
|
||||
insideJSDoc = !trimmed.includes("*/")
|
||||
continue
|
||||
}
|
||||
|
||||
if (line.includes("runtime.Bun") || line.includes("globalThis.Bun") || line.includes("typeof Bun")) {
|
||||
continue
|
||||
}
|
||||
|
||||
RAW_BUN_API_CALL.lastIndex = 0
|
||||
const rawMatch = [...line.matchAll(RAW_BUN_API_CALL)].find(
|
||||
(match) => match.index !== undefined && !isInsideStringLiteral(line, match.index),
|
||||
)
|
||||
|
||||
if (rawMatch) {
|
||||
offending.push(formatOffendingLine(index + 1, line))
|
||||
}
|
||||
}
|
||||
|
||||
expect(
|
||||
offending,
|
||||
`Expected zero raw Bun API calls in dist/index.js but found ${offending.length}:\n${offending.join("\n")}`,
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
test.skipIf(!existsSync(DIST_INDEX))("#given dist bundle #when imported and inspected under node --input-type=module #then stderr has no Bun reference errors", async () => {
|
||||
const node = Bun.which("node")
|
||||
if (!node) return
|
||||
|
||||
const proc = Bun.spawn({
|
||||
cmd: [node, "--input-type=module", "-e", NODE_EXPORT_SMOKE_SCRIPT],
|
||||
cwd: process.cwd(),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
})
|
||||
|
||||
const stdout = await new Response(proc.stdout).text()
|
||||
const stderr = await new Response(proc.stderr).text()
|
||||
const exitCode = await proc.exited
|
||||
const stderrLower = stderr.toLowerCase()
|
||||
|
||||
expect(exitCode, stderr.trim()).toBe(0)
|
||||
expect(stdout).toContain("SMOKE_OK:")
|
||||
expect(stderrLower).not.toContain("referenceerror")
|
||||
expect(stderr).not.toContain("Bun is not defined")
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user