diff --git a/README.md b/README.md index f91e0a4..2a71094 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,8 @@ This eliminates the need for polling—perfect for long-running processes like b | Variable | Default | Description | | ---------------------- | ---------- | -------------------------------------------------- | | `PTY_MAX_BUFFER_LINES` | `50000` | Maximum lines to keep in output buffer per session | -| `PTY_WEB_HOSTNAME` | `::1` | Hostname for the web server to bind to | +| `PTY_WEB_HOSTNAME` | `::1` | Hostname for the web server to bind to (IPv6 loopback by default) | +| `PTY_WEB_PORT` | `0` (random) | Port for the web server (0 = random port) | ### Permissions diff --git a/src/web/server/server.ts b/src/web/server/server.ts index 4391751..c8e94ed 100644 --- a/src/web/server/server.ts +++ b/src/web/server/server.ts @@ -41,7 +41,7 @@ export class PTYServer implements Disposable { private startWebServer(): Server { return Bun.serve({ - port: 0, + port: process.env.PTY_WEB_PORT ? parseInt(process.env.PTY_WEB_PORT, 10) : 0, hostname: process.env.PTY_WEB_HOSTNAME ?? '::1', routes: { diff --git a/test/e2e/fixtures.ts b/test/e2e/fixtures.ts index b3dffbe..a9467ec 100644 --- a/test/e2e/fixtures.ts +++ b/test/e2e/fixtures.ts @@ -90,12 +90,14 @@ export const test = base.extend({ const serverURL = serverURLText.trim() // Parse URL to extract port number - const urlMatch = serverURL.match(/http:\/\/\[::1\]:(\d+)/) + const urlMatch = serverURL.match(/http:\/\/(?:127\.0\.0\.1|\[::1\]):(\d+)/) if (!urlMatch || !urlMatch[1]) { throw new Error(`Invalid port file format: ${serverURL}`) } const port = parseInt(urlMatch[1], 10) - const baseURL = `http://[::1]:${port}` + const baseURL = serverURL.includes('[::1]') + ? `http://[::1]:${port}` + : `http://127.0.0.1:${port}` await waitForServer(baseURL, 15000) diff --git a/test/npm-pack-integration.test.ts b/test/npm-pack-integration.test.ts index 4482506..72ce0b2 100644 --- a/test/npm-pack-integration.test.ts +++ b/test/npm-pack-integration.test.ts @@ -32,6 +32,7 @@ describe('npm pack integration', () => { let tempDir: string let packFile: string | null = null let serverProcess: ReturnType | null = null + let baseURL = '' afterEach(async () => { // Cleanup server process @@ -137,12 +138,13 @@ describe('npm pack integration', () => { const port = await waitWithRetry() expect(port).not.toBe(0) + baseURL = `http://[::1]:${port}` // Wait for server to be ready let retries = 20 // 10 seconds while (retries > 0) { try { - const response = await fetch(`http://localhost:${port}/api/sessions`) + const response = await fetch(`${baseURL}/api/sessions`) if (response.ok) break } catch (error) { if (!(error instanceof DOMException) || error.name !== 'AbortError') { @@ -155,12 +157,12 @@ describe('npm pack integration', () => { expect(retries).toBeGreaterThan(0) // Server should be ready // 5) Fetch assets - const assetResponse = await fetch(`http://localhost:${port}/assets/${assetName}`) + const assetResponse = await fetch(`${baseURL}/assets/${assetName}`) expect(assetResponse.status).toBe(200) // Could add more specific checks here, like content-type or specific assets // 6) Fetch index.html and verify it's the built version - const indexResponse = await fetch(`http://localhost:${port}/`) + const indexResponse = await fetch(`${baseURL}/`) expect(indexResponse.status).toBe(200) const indexContent = await indexResponse.text() expect(indexContent).not.toContain('main.tsx') // Fails if raw HTML is served