From 6fd84fe13cfa888b2e89bd941d0ace2522ae59c5 Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Mon, 24 Nov 2025 13:49:52 -0800 Subject: [PATCH 1/4] feat: ci test --- .github/workflows/docs-build-pr.yml | 6 +- package.json | 3 +- test/dev-server.test.ts | 192 ++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 test/dev-server.test.ts diff --git a/.github/workflows/docs-build-pr.yml b/.github/workflows/docs-build-pr.yml index 4a80405..63b3dd9 100644 --- a/.github/workflows/docs-build-pr.yml +++ b/.github/workflows/docs-build-pr.yml @@ -1,4 +1,4 @@ -name: PR Build +name: PR Build & Test on: pull_request: @@ -7,6 +7,7 @@ on: - "src/**" - "content/**" - "scripts/**" + - "test/**" - "package.json" - "next.config.ts" - ".github/workflows/docs-build-pr.yml" @@ -34,3 +35,6 @@ jobs: - name: Build run: bun run build + + - name: Test + run: bun test diff --git a/package.json b/package.json index 68baa96..113e688 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "dev:next": "next dev --turbo -p 8293", "clear:cache": "tsx scripts/clear-cache.ts", "start": "next start", - "download:references": "bun scripts/download-references.ts" + "download:references": "bun scripts/download-references.ts", + "test:dev-server": "bun test test/dev-server.test.ts" }, "dependencies": { "@radix-ui/react-dialog": "^1.1.11", diff --git a/test/dev-server.test.ts b/test/dev-server.test.ts new file mode 100644 index 0000000..a56d57e --- /dev/null +++ b/test/dev-server.test.ts @@ -0,0 +1,192 @@ +import { test, expect, afterAll } from "bun:test"; +import { spawn, execSync, ChildProcess } from "child_process"; +import { existsSync, readFileSync } from "fs"; +import { join } from "path"; + +const PORT = 8293; +const BASE_URL = `http://localhost:${PORT}`; +const HOME_PAGE_URL = `${BASE_URL}/docs/home`; +const TIMEOUT_MS = 30000; // 30 seconds (shorter timeout) +const POLL_INTERVAL_MS = 500; // 500ms + +// Track the dev server process for cleanup +let devServerProcess: ChildProcess | null = null; + +// Track if the dev server has failed +let serverError: string | null = null; +let serverExited = false; + +/** + * Kill any process using a specific port (macOS/Linux) + */ +function killProcessOnPort(port: number): void { + try { + execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null || true`, { + stdio: "ignore", + }); + } catch (error) { + // Ignore errors + } +} + +/** + * Kill a process group + */ +function killProcessGroup(pid: number, signal: NodeJS.Signals = "SIGTERM"): void { + try { + process.kill(-pid, signal); + } catch (error) { + // Process group may already be dead + } +} + +// Cleanup after all tests - this runs synchronously before exit +afterAll(() => { + console.log("Cleanup: Stopping dev server..."); + + if (devServerProcess && devServerProcess.pid) { + killProcessGroup(devServerProcess.pid, "SIGKILL"); + } + + // Kill any remaining processes on the port + killProcessOnPort(PORT); + + // Give OS time to release the port + execSync("sleep 1", { stdio: "ignore" }); +}); + +/** + * Check if a port is already in use by attempting to connect to it + */ +async function isPortInUse(port: number): Promise { + try { + await fetch(`http://localhost:${port}`, { + signal: AbortSignal.timeout(1000), + }); + return true; + } catch (error) { + return false; + } +} + +/** + * Wait for the server to be ready by polling the endpoint. + * Fails immediately if the dev server process exits or emits an error. + */ +async function waitForServerReady( + url: string, + timeoutMs: number, + pollIntervalMs: number +): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + // Check if the server process has failed + if (serverError) { + throw new Error(`Dev server failed to start:\n${serverError}`); + } + if (serverExited) { + throw new Error("Dev server process exited unexpectedly"); + } + + try { + const response = await fetch(url, { + signal: AbortSignal.timeout(2000), + }); + if (response.ok) { + return; + } + } catch (error) { + // Server not ready yet, continue polling + } + await new Promise((resolve) => setTimeout(resolve, pollIntervalMs)); + } + + throw new Error(`Server did not become ready within ${timeoutMs}ms`); +} + +test("dev server starts and serves home page", async () => { + const stderrChunks: Buffer[] = []; + + // Reset state + serverError = null; + serverExited = false; + + // Step 1: Check if port is already in use + const portInUse = await isPortInUse(PORT); + if (portInUse) { + throw new Error( + `Port ${PORT} is already in use. Please stop any running dev server before running this test.` + ); + } + + // Step 2: Start dev server + console.log("Starting dev server..."); + devServerProcess = spawn("bun", ["run", "dev"], { + cwd: process.cwd(), + stdio: ["ignore", "pipe", "pipe"], + shell: false, + detached: true, + }); + + // Monitor for process exit + devServerProcess.on("exit", (code) => { + if (code !== 0 && code !== null) { + serverExited = true; + const stderr = Buffer.concat(stderrChunks).toString(); + serverError = `Process exited with code ${code}\n${stderr}`; + } + }); + + devServerProcess.on("error", (err) => { + serverError = `Process error: ${err.message}`; + }); + + // Capture stderr and detect errors + devServerProcess.stderr?.on("data", (data: Buffer) => { + stderrChunks.push(data); + const output = data.toString(); + + // Detect fatal errors that should fail the test immediately + if (output.includes("Module not found") || + output.includes("error:") || + output.includes("Error:") || + output.includes("exited with code 1")) { + serverError = Buffer.concat(stderrChunks).toString(); + } + }); + + try { + // Step 3: Wait for server to be ready (will fail fast if error detected) + console.log("Waiting for server to be ready..."); + await waitForServerReady(HOME_PAGE_URL, TIMEOUT_MS, POLL_INTERVAL_MS); + console.log("Server is ready!"); + + // Step 4: Test home page HTTP response + console.log("Testing home page HTTP response..."); + const response = await fetch(HOME_PAGE_URL); + expect(response.status).toBe(200); + + const html = await response.text(); + expect(html).toContain("Superwall"); + expect(html).toContain("Welcome"); + + // Step 5: Test generated markdown file + console.log("Testing generated markdown file..."); + const markdownPath = join(process.cwd(), "public", "home.md"); + expect(existsSync(markdownPath)).toBe(true); + + const markdownContent = readFileSync(markdownPath, "utf-8"); + expect(markdownContent.length).toBeGreaterThan(0); + expect(markdownContent).toContain("Superwall"); + + console.log("All tests passed!"); + } catch (error) { + // Log captured stderr for debugging if not already in error message + if (stderrChunks.length > 0 && !serverError) { + const stderr = Buffer.concat(stderrChunks).toString(); + if (stderr) console.error("Dev server stderr:", stderr); + } + throw error; + } +}, TIMEOUT_MS + 10000); From 3090528ab6620f97e4337eba055bb63bda336543 Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Mon, 24 Nov 2025 14:24:04 -0800 Subject: [PATCH 2/4] fix(test): adjust timeout for CI environments and improve process exit handling --- test/dev-server.test.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/dev-server.test.ts b/test/dev-server.test.ts index a56d57e..6be16a7 100644 --- a/test/dev-server.test.ts +++ b/test/dev-server.test.ts @@ -6,7 +6,9 @@ import { join } from "path"; const PORT = 8293; const BASE_URL = `http://localhost:${PORT}`; const HOME_PAGE_URL = `${BASE_URL}/docs/home`; -const TIMEOUT_MS = 30000; // 30 seconds (shorter timeout) +// Increase timeout for CI environments +const IS_CI = process.env.CI === "true"; +const TIMEOUT_MS = IS_CI ? 120000 : 60000; // 2 minutes in CI, 60s locally const POLL_INTERVAL_MS = 500; // 500ms // Track the dev server process for cleanup @@ -129,13 +131,16 @@ test("dev server starts and serves home page", async () => { detached: true, }); + // Consume stdout to prevent blocking, but we don't need to store it all + devServerProcess.stdout?.on("data", () => { + // No-op to drain buffer + }); + // Monitor for process exit devServerProcess.on("exit", (code) => { - if (code !== 0 && code !== null) { - serverExited = true; - const stderr = Buffer.concat(stderrChunks).toString(); - serverError = `Process exited with code ${code}\n${stderr}`; - } + serverExited = true; + const stderr = Buffer.concat(stderrChunks).toString(); + serverError = `Process exited unexpectedly with code ${code}\n${stderr}`; }); devServerProcess.on("error", (err) => { From ef5da25f4bca0f5a543b5578778fbaef0e8553cc Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Wed, 26 Nov 2025 13:51:15 -0800 Subject: [PATCH 3/4] feat(test): next start, cf preview --- .github/workflows/docs-build-pr.yml | 4 +- bun.lock | 5 + package.json | 1 + scripts/download-references.ts | 15 ++- test/cf-preview.test.ts | 51 +++++++ test/dev-server.test.ts | 197 ---------------------------- test/next-start.test.ts | 46 +++++++ test/tsconfig.json | 7 + 8 files changed, 126 insertions(+), 200 deletions(-) create mode 100644 test/cf-preview.test.ts delete mode 100644 test/dev-server.test.ts create mode 100644 test/next-start.test.ts create mode 100644 test/tsconfig.json diff --git a/.github/workflows/docs-build-pr.yml b/.github/workflows/docs-build-pr.yml index 63b3dd9..cd5001b 100644 --- a/.github/workflows/docs-build-pr.yml +++ b/.github/workflows/docs-build-pr.yml @@ -33,8 +33,8 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - - name: Build - run: bun run build + - name: Build (Next + Cloudflare) + run: bun run build:cf - name: Test run: bun test diff --git a/bun.lock b/bun.lock index 16df42d..2307ff6 100644 --- a/bun.lock +++ b/bun.lock @@ -35,6 +35,7 @@ "@next/mdx": "^16.0.1", "@opennextjs/cloudflare": "^1.11.0", "@tailwindcss/postcss": "^4.1.5", + "@types/bun": "^1.3.3", "@types/fs-extra": "^11.0.4", "@types/mdx": "^2.0.13", "@types/node": "22.14.1", @@ -617,6 +618,8 @@ "@tsconfig/node18": ["@tsconfig/node18@1.0.3", "", {}, "sha512-RbwvSJQsuN9TB04AQbGULYfOGE/RnSFk/FLQ5b0NmDf5Kx2q/lABZbHQPKCO1vZ6Fiwkplu+yb9pGdLy1iGseQ=="], + "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -711,6 +714,8 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], diff --git a/package.json b/package.json index 113e688..5996905 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@next/mdx": "^16.0.1", "@opennextjs/cloudflare": "^1.11.0", "@tailwindcss/postcss": "^4.1.5", + "@types/bun": "^1.3.3", "@types/fs-extra": "^11.0.4", "@types/mdx": "^2.0.13", "@types/node": "22.14.1", diff --git a/scripts/download-references.ts b/scripts/download-references.ts index 5631031..fe62d20 100644 --- a/scripts/download-references.ts +++ b/scripts/download-references.ts @@ -1,5 +1,5 @@ import { $ } from 'bun'; -import { existsSync, mkdirSync } from 'fs'; +import { existsSync, mkdirSync, unlinkSync } from 'fs'; import { join } from 'path'; const repositories = [ @@ -54,6 +54,19 @@ for (const repo of repositories) { console.error(`✗ Failed to clone ${repo.name}:`, error); } } + + // Remove test file from react-native repository + if (repo.name === 'react-native') { + const testFilePath = join(repoPath, 'src', '__tests__', 'index.test.tsx'); + if (existsSync(testFilePath)) { + try { + unlinkSync(testFilePath); + console.log(`✓ Removed ${testFilePath}`); + } catch (error) { + console.error(`✗ Failed to remove ${testFilePath}:`, error); + } + } + } } console.log('\nAll repositories have been processed!'); \ No newline at end of file diff --git a/test/cf-preview.test.ts b/test/cf-preview.test.ts new file mode 100644 index 0000000..5283031 --- /dev/null +++ b/test/cf-preview.test.ts @@ -0,0 +1,51 @@ +// test/cf-preview.test.ts +import { test, expect } from "bun:test"; +import { spawn } from "child_process"; + +const PORT = 8790; +const BASE_URL = `http://127.0.0.1:${PORT}`; + +function waitForServer(url: string, timeoutMs = 60000) { + const start = Date.now(); + return new Promise((resolve, reject) => { + const tick = async () => { + try { + const res = await fetch(url, { signal: AbortSignal.timeout(2000) }); + if (res.ok) return resolve(); + } catch { + // ignore + } + + if (Date.now() - start > timeoutMs) { + reject(new Error(`CF preview server didn't become ready at ${url}`)); + } else { + setTimeout(tick, 500); + } + }; + tick(); + }); +} + +test("Cloudflare preview runs and serves /docs/home", async () => { + // Assumes `bun run build:cf` has already run in CI + const server = spawn( + "bunx", + ["opennextjs-cloudflare", "preview", "--port", String(PORT)], + { + stdio: "inherit", + } + ); + + try { + await waitForServer(`${BASE_URL}/docs/home`, 60000); + const res = await fetch(`${BASE_URL}/docs/home`); + expect(res.status).toBe(200); + + const html = await res.text(); + expect(html).toContain("Superwall"); + expect(html).toContain("Welcome"); + } finally { + // SIGINT is usually enough to bring down wrangler/preview + server.kill("SIGINT"); + } +}); diff --git a/test/dev-server.test.ts b/test/dev-server.test.ts deleted file mode 100644 index 6be16a7..0000000 --- a/test/dev-server.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { test, expect, afterAll } from "bun:test"; -import { spawn, execSync, ChildProcess } from "child_process"; -import { existsSync, readFileSync } from "fs"; -import { join } from "path"; - -const PORT = 8293; -const BASE_URL = `http://localhost:${PORT}`; -const HOME_PAGE_URL = `${BASE_URL}/docs/home`; -// Increase timeout for CI environments -const IS_CI = process.env.CI === "true"; -const TIMEOUT_MS = IS_CI ? 120000 : 60000; // 2 minutes in CI, 60s locally -const POLL_INTERVAL_MS = 500; // 500ms - -// Track the dev server process for cleanup -let devServerProcess: ChildProcess | null = null; - -// Track if the dev server has failed -let serverError: string | null = null; -let serverExited = false; - -/** - * Kill any process using a specific port (macOS/Linux) - */ -function killProcessOnPort(port: number): void { - try { - execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null || true`, { - stdio: "ignore", - }); - } catch (error) { - // Ignore errors - } -} - -/** - * Kill a process group - */ -function killProcessGroup(pid: number, signal: NodeJS.Signals = "SIGTERM"): void { - try { - process.kill(-pid, signal); - } catch (error) { - // Process group may already be dead - } -} - -// Cleanup after all tests - this runs synchronously before exit -afterAll(() => { - console.log("Cleanup: Stopping dev server..."); - - if (devServerProcess && devServerProcess.pid) { - killProcessGroup(devServerProcess.pid, "SIGKILL"); - } - - // Kill any remaining processes on the port - killProcessOnPort(PORT); - - // Give OS time to release the port - execSync("sleep 1", { stdio: "ignore" }); -}); - -/** - * Check if a port is already in use by attempting to connect to it - */ -async function isPortInUse(port: number): Promise { - try { - await fetch(`http://localhost:${port}`, { - signal: AbortSignal.timeout(1000), - }); - return true; - } catch (error) { - return false; - } -} - -/** - * Wait for the server to be ready by polling the endpoint. - * Fails immediately if the dev server process exits or emits an error. - */ -async function waitForServerReady( - url: string, - timeoutMs: number, - pollIntervalMs: number -): Promise { - const startTime = Date.now(); - - while (Date.now() - startTime < timeoutMs) { - // Check if the server process has failed - if (serverError) { - throw new Error(`Dev server failed to start:\n${serverError}`); - } - if (serverExited) { - throw new Error("Dev server process exited unexpectedly"); - } - - try { - const response = await fetch(url, { - signal: AbortSignal.timeout(2000), - }); - if (response.ok) { - return; - } - } catch (error) { - // Server not ready yet, continue polling - } - await new Promise((resolve) => setTimeout(resolve, pollIntervalMs)); - } - - throw new Error(`Server did not become ready within ${timeoutMs}ms`); -} - -test("dev server starts and serves home page", async () => { - const stderrChunks: Buffer[] = []; - - // Reset state - serverError = null; - serverExited = false; - - // Step 1: Check if port is already in use - const portInUse = await isPortInUse(PORT); - if (portInUse) { - throw new Error( - `Port ${PORT} is already in use. Please stop any running dev server before running this test.` - ); - } - - // Step 2: Start dev server - console.log("Starting dev server..."); - devServerProcess = spawn("bun", ["run", "dev"], { - cwd: process.cwd(), - stdio: ["ignore", "pipe", "pipe"], - shell: false, - detached: true, - }); - - // Consume stdout to prevent blocking, but we don't need to store it all - devServerProcess.stdout?.on("data", () => { - // No-op to drain buffer - }); - - // Monitor for process exit - devServerProcess.on("exit", (code) => { - serverExited = true; - const stderr = Buffer.concat(stderrChunks).toString(); - serverError = `Process exited unexpectedly with code ${code}\n${stderr}`; - }); - - devServerProcess.on("error", (err) => { - serverError = `Process error: ${err.message}`; - }); - - // Capture stderr and detect errors - devServerProcess.stderr?.on("data", (data: Buffer) => { - stderrChunks.push(data); - const output = data.toString(); - - // Detect fatal errors that should fail the test immediately - if (output.includes("Module not found") || - output.includes("error:") || - output.includes("Error:") || - output.includes("exited with code 1")) { - serverError = Buffer.concat(stderrChunks).toString(); - } - }); - - try { - // Step 3: Wait for server to be ready (will fail fast if error detected) - console.log("Waiting for server to be ready..."); - await waitForServerReady(HOME_PAGE_URL, TIMEOUT_MS, POLL_INTERVAL_MS); - console.log("Server is ready!"); - - // Step 4: Test home page HTTP response - console.log("Testing home page HTTP response..."); - const response = await fetch(HOME_PAGE_URL); - expect(response.status).toBe(200); - - const html = await response.text(); - expect(html).toContain("Superwall"); - expect(html).toContain("Welcome"); - - // Step 5: Test generated markdown file - console.log("Testing generated markdown file..."); - const markdownPath = join(process.cwd(), "public", "home.md"); - expect(existsSync(markdownPath)).toBe(true); - - const markdownContent = readFileSync(markdownPath, "utf-8"); - expect(markdownContent.length).toBeGreaterThan(0); - expect(markdownContent).toContain("Superwall"); - - console.log("All tests passed!"); - } catch (error) { - // Log captured stderr for debugging if not already in error message - if (stderrChunks.length > 0 && !serverError) { - const stderr = Buffer.concat(stderrChunks).toString(); - if (stderr) console.error("Dev server stderr:", stderr); - } - throw error; - } -}, TIMEOUT_MS + 10000); diff --git a/test/next-start.test.ts b/test/next-start.test.ts new file mode 100644 index 0000000..d42db0b --- /dev/null +++ b/test/next-start.test.ts @@ -0,0 +1,46 @@ +// test/next-start.test.ts +import { test, expect } from "bun:test"; +import { spawn } from "child_process"; + +const PORT = 8293; +const BASE_URL = `http://localhost:${PORT}`; + +function waitForServer(url: string, timeoutMs = 20000) { + const start = Date.now(); + return new Promise((resolve, reject) => { + const tick = async () => { + try { + const res = await fetch(url, { signal: AbortSignal.timeout(2000) }); + if (res.ok) return resolve(); + } catch { + // ignore + } + + if (Date.now() - start > timeoutMs) { + reject(new Error(`Server didn't become ready at ${url}`)); + } else { + setTimeout(tick, 300); + } + }; + tick(); + }); +} + +test("next build/start runs and serves /docs/home", async () => { + // Assumes `bun run build` has already run in CI + const server = spawn("bunx", ["next", "start", "-p", String(PORT)], { + stdio: "inherit", + }); + + try { + await waitForServer(`${BASE_URL}/docs/home`); + const res = await fetch(`${BASE_URL}/docs/home`); + expect(res.status).toBe(200); + + const html = await res.text(); + expect(html).toContain("Superwall"); + expect(html).toContain("Welcome"); + } finally { + server.kill("SIGTERM"); + } +}); diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..4e38be6 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["bun"] + }, + "include": ["**/*.ts", "**/*.tsx"] +} \ No newline at end of file From df5cea2c855e3950a936ef3bb7374f230085daf8 Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Wed, 26 Nov 2025 14:10:04 -0800 Subject: [PATCH 4/4] fix(test): generate test wrangler config --- package.json | 2 +- scripts/generate-test-wrangler.ts | 61 +++++++++++++++++++++++++++++++ test/cf-preview.test.ts | 4 ++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 scripts/generate-test-wrangler.ts diff --git a/package.json b/package.json index 5996905..23e08af 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "clear:cache": "tsx scripts/clear-cache.ts", "start": "next start", "download:references": "bun scripts/download-references.ts", - "test:dev-server": "bun test test/dev-server.test.ts" + "generate:test-wrangler": "bun scripts/generate-test-wrangler.ts" }, "dependencies": { "@radix-ui/react-dialog": "^1.1.11", diff --git a/scripts/generate-test-wrangler.ts b/scripts/generate-test-wrangler.ts new file mode 100644 index 0000000..3057f00 --- /dev/null +++ b/scripts/generate-test-wrangler.ts @@ -0,0 +1,61 @@ +#!/usr/bin/env bun +/** + * Generates a minimal wrangler.jsonc config file for testing purposes. + * This config contains no secrets and is suitable for CI/test environments. + */ + +import { existsSync, writeFileSync } from "fs"; +import { join } from "path"; + +const wranglerConfig = { + $schema: "node_modules/wrangler/config-schema.json", + account_id: "test-account-id", + main: ".open-next/worker.js", + name: "docs-test-worker", + compatibility_date: "2024-12-30", + compatibility_flags: [ + "nodejs_compat", + "global_fetch_strictly_public" + ], + assets: { + directory: ".open-next/assets", + binding: "ASSETS" + }, + services: [{ + binding: "WORKER_SELF_REFERENCE", + service: "docs-test-worker" + }], + routes: [], + env: { + staging: { + workers_dev: true, + routes: [] + } + } +}; + +/** + * Generates a test wrangler.jsonc config file if it doesn't already exist. + * @param configPath Optional path to the config file. Defaults to "wrangler.jsonc" in the current working directory. + * @returns The path to the config file. + */ +export function generateTestWranglerConfig(configPath?: string, silent = false): string { + const path = configPath || join(process.cwd(), "wrangler.jsonc"); + + if (existsSync(path)) { + return path; + } + + const configContent = JSON.stringify(wranglerConfig, null, 2); + writeFileSync(path, configContent, "utf-8"); + if (!silent) { + console.log(`✓ Generated test wrangler.jsonc at ${path}`); + } + return path; +} + +// If run as a script, generate the config +if (import.meta.main) { + generateTestWranglerConfig(); +} + diff --git a/test/cf-preview.test.ts b/test/cf-preview.test.ts index 5283031..90dcf86 100644 --- a/test/cf-preview.test.ts +++ b/test/cf-preview.test.ts @@ -1,6 +1,7 @@ // test/cf-preview.test.ts import { test, expect } from "bun:test"; import { spawn } from "child_process"; +import { generateTestWranglerConfig } from "../scripts/generate-test-wrangler"; const PORT = 8790; const BASE_URL = `http://127.0.0.1:${PORT}`; @@ -27,6 +28,9 @@ function waitForServer(url: string, timeoutMs = 60000) { } test("Cloudflare preview runs and serves /docs/home", async () => { + // Ensure wrangler.jsonc exists (auto-generate if missing) + generateTestWranglerConfig(); + // Assumes `bun run build:cf` has already run in CI const server = spawn( "bunx",