import type { Subprocess } from "bun"; import { expect, test } from "bun:test"; import { bunEnv, bunExe, tempDirWithFiles } from "harness"; async function getServerUrl(process: Subprocess) { // Read the port number from stdout const decoder = new TextDecoder(); let serverUrl = ""; let text = ""; for await (const chunk of process.stdout) { const textChunk = decoder.decode(chunk, { stream: true }); text += textChunk; console.log(textChunk); if (text.includes("http://")) { serverUrl = text.trim(); serverUrl = serverUrl.slice(serverUrl.indexOf("http://")); serverUrl = serverUrl.slice(0, serverUrl.indexOf("\n")); if (URL.canParse(serverUrl)) { break; } serverUrl = serverUrl.slice(0, serverUrl.indexOf("/n")); serverUrl = serverUrl.slice(0, serverUrl.lastIndexOf("/")); serverUrl = serverUrl.trim(); if (URL.canParse(serverUrl)) { break; } } } if (!serverUrl) { throw new Error("Could not find server URL in stdout"); } return serverUrl; } test.concurrent("bun ./index.html", async () => { const dir = tempDirWithFiles("html-entry-test", { "index.html": /*html*/ ` HTML Entry Test

Hello from Bun!

`, "styles.css": /*css*/ ` .container { max-width: 800px; margin: 2rem auto; text-align: center; font-family: system-ui, sans-serif; } button { padding: 0.5rem 1rem; font-size: 1.25rem; border-radius: 0.25rem; border: 2px solid #000; background: #fff; cursor: pointer; transition: all 0.2s; } button:hover { background: #000; color: #fff; } `, "app.js": /*js*/ ` const button = document.getElementById('counter'); let count = 0; button.addEventListener('click', () => { count++; button.textContent = \`Click me: \${count}\`; }); `, }); // Start the server by running bun with the HTML file await using process = Bun.spawn({ cmd: [bunExe(), "index.html", "--port=0"], env: { ...bunEnv, NODE_ENV: "production", }, cwd: dir, stdout: "pipe", }); const serverUrl = await getServerUrl(process); try { // Make a request to the server using the detected URL const response = await fetch(serverUrl); expect(response.status).toBe(200); expect(response.headers.get("content-type")).toContain("text/html"); const html = await response.text(); // Verify the HTML content expect(html).toContain("HTML Entry Test"); expect(html).toContain('
'); // The bundler should have processed the CSS and JS files and injected them expect(html).toMatch(//); expect(html).toMatch(/

Welcome Home

About
`, "about.html": /*html*/ ` About Page

About Us

Home

This is the about page

`, "styles.css": /*css*/ ` .container { max-width: 800px; margin: 2rem auto; text-align: center; font-family: system-ui, sans-serif; } a { display: block; margin: 1rem 0; color: blue; } `, "home.js": /*js*/ ` const button = document.getElementById('counter'); let count = 0; button.addEventListener('click', () => { count++; button.textContent = \`Click me: \${count}\`; }); `, "about.js": /*js*/ ` const message = document.getElementById('message'); message.textContent += " - Updated via JS"; console.log(process.env.BUN_PUBLIC_FOO); console.log(typeof process.env.BUN_PRIVATE_FOO !== "undefined"); `, "bunfig.toml": /*toml*/ ` [serve.static] env = "BUN_PUBLIC_*" `, }); console.log({ dir }); // Start the server by running bun with multiple HTML files await using process = Bun.spawn({ cmd: [bunExe(), "index.html", "about.html", "--port=0"], env: { ...bunEnv, NODE_ENV: "production", BUN_PUBLIC_FOO: "bar", BUN_PRIVATE_FOO: "baz", }, cwd: dir, stdout: "pipe", }); const serverUrl = await getServerUrl(process); if (!serverUrl) { throw new Error("Could not find server URL in stdout"); } try { // Test the home page const homeResponse = await fetch(serverUrl); expect(homeResponse.status).toBe(200); expect(homeResponse.headers.get("content-type")).toContain("text/html"); const homeHtml = await homeResponse.text(); expect(homeHtml).toContain("Home Page"); expect(homeHtml).toContain('About'); expect(homeHtml).toMatch(/

Welcome Home

`, "about.html": /*html*/ ` About Page

About Us

This is the about page

`, "contact.html": /*html*/ ` Contact Page

Contact Us

`, "shared.css": /*css*/ ` nav { padding: 1rem; background: #f0f0f0; text-align: center; } nav a { margin: 0 1rem; color: blue; } .container { max-width: 800px; margin: 2rem auto; text-align: center; font-family: system-ui, sans-serif; } form { display: flex; flex-direction: column; gap: 1rem; max-width: 300px; margin: 0 auto; } input, button { padding: 0.5rem; font-size: 1rem; } `, "home.js": /*js*/ ` const button = document.getElementById('counter'); let count = 0; button.addEventListener('click', () => { count++; button.textContent = \`Click me: \${count}\`; }); `, "about.js": /*js*/ ` const message = document.getElementById('message'); message.textContent += " - Updated via JS"; `, "contact.js": /*js*/ ` const form = document.getElementById('contact-form'); form.addEventListener('submit', (e) => { e.preventDefault(); const input = form.querySelector('input'); alert(\`Thanks for your message, \${input.value}!\`); input.value = ''; }); `, // Add a non-HTML file to verify it's not picked up "README.md": "# Test Project\nThis file should be ignored by the glob.", }); // Start the server using glob pattern await using process = Bun.spawn({ cmd: [bunExe(), "*.html", "--port=0"], env: { ...bunEnv, NODE_ENV: "production", }, cwd: dir, stdout: "pipe", }); console.log({ cwd: dir }); const serverUrl = await getServerUrl(process); try { // Test all three pages are served const pages = ["", "about", "contact"]; const titles = ["Home Page", "About Page", "Contact Page"]; for (const [i, route] of pages.entries()) { const response = await fetch(new URL(route, serverUrl).href); expect(response.status).toBe(200); expect(response.headers.get("content-type")).toContain("text/html"); const html = await response.text(); expect(html).toContain(`${titles[i]}`); expect(html).toMatch(/