From 0955ffef8b7f87d4857a1dbb96667ed5ba908992 Mon Sep 17 00:00:00 2001 From: lentil32 Date: Mon, 1 Jun 2026 02:38:12 +0900 Subject: [PATCH] fix(landing): prerender with workerd --- apps/landing/astro.config.ts | 3 +- apps/landing/wrangler.toml | 1 + .../astro-agent-markdown/src/content.test.ts | 9 +++++ packages/astro-agent-markdown/src/content.ts | 7 +++- .../src/dev-middleware.test.ts | 40 +++++++++++++++++++ .../src/html-sidecars.test.ts | 23 +++++++++++ .../astro-agent-markdown/src/html-sidecars.ts | 33 +++++++++++++++ 7 files changed, 113 insertions(+), 3 deletions(-) diff --git a/apps/landing/astro.config.ts b/apps/landing/astro.config.ts index 6671b981..20c6e51a 100644 --- a/apps/landing/astro.config.ts +++ b/apps/landing/astro.config.ts @@ -59,8 +59,7 @@ function createBundleReportPlugin() { export default defineConfig({ adapter: cloudflare({ imageService: { build: "compile", runtime: "cloudflare-binding" }, - // Keep Shiki-backed Astro code rendering out of workerd's prerender path. - prerenderEnvironment: "node", + prerenderEnvironment: "workerd", }), build: { // Keep page CSS out of a separate render-blocking request for first-load LCP. diff --git a/apps/landing/wrangler.toml b/apps/landing/wrangler.toml index 62f4d573..d1990571 100644 --- a/apps/landing/wrangler.toml +++ b/apps/landing/wrangler.toml @@ -5,6 +5,7 @@ name = "onequery-landing" main = "./src/worker.ts" compatibility_date = "2026-04-14" +compatibility_flags = ["nodejs_compat"] logpush = true preview_urls = true routes = [{ pattern = "onequery.dev", custom_domain = true }] diff --git a/packages/astro-agent-markdown/src/content.test.ts b/packages/astro-agent-markdown/src/content.test.ts index c1a49c6e..b84512c3 100644 --- a/packages/astro-agent-markdown/src/content.test.ts +++ b/packages/astro-agent-markdown/src/content.test.ts @@ -75,6 +75,15 @@ title: Debugging production ).toBe("debug-production-agent-runs-with-onequery"); }); + it("does not treat a collection index route as a content entry", () => { + expect( + getContentEntryIdForMarkdownPath({ + markdownPath: "/blog/index.md", + routePrefix: "/blog", + }) + ).toBeUndefined(); + }); + it("finds a content entry by negotiated page pathname", async () => { await expect( getContentMarkdownForPath({ diff --git a/packages/astro-agent-markdown/src/content.ts b/packages/astro-agent-markdown/src/content.ts index 7321c40c..48b43599 100644 --- a/packages/astro-agent-markdown/src/content.ts +++ b/packages/astro-agent-markdown/src/content.ts @@ -97,7 +97,12 @@ export function getContentEntryIdForMarkdownPath(input: { return undefined; } - return input.markdownPath.slice(routeBase.length, -markdownSuffix.length); + const entryId = input.markdownPath.slice( + routeBase.length, + -markdownSuffix.length + ); + + return entryId.length > 0 ? entryId : undefined; } export async function getContentMarkdownForPath(input: { diff --git a/packages/astro-agent-markdown/src/dev-middleware.test.ts b/packages/astro-agent-markdown/src/dev-middleware.test.ts index 684ba6bc..727dbf02 100644 --- a/packages/astro-agent-markdown/src/dev-middleware.test.ts +++ b/packages/astro-agent-markdown/src/dev-middleware.test.ts @@ -87,6 +87,46 @@ Context, not keys. `); }); + it("falls back to HTML conversion for a content collection index route", async () => { + const getMarkdown = vi.fn(async () => "should not read content entry"); + const onRequest = createDevMarkdownMiddleware({ + contentRoutes: [ + { + getMarkdown, + routePrefix: "/blog", + }, + ], + }); + const next = vi.fn( + async () => + new Response("

Blog

Latest updates.

", { + headers: { "Content-Type": "text/html; charset=utf-8" }, + }) + ); + + const response = await onRequest( + createContext( + new Request("http://localhost:4546/blog/", { + headers: { Accept: "text/markdown" }, + }) + ), + next as unknown as MiddlewareNext + ); + + expect(getMarkdown).not.toHaveBeenCalled(); + expect(next).toHaveBeenCalledOnce(); + expect(response).toBeInstanceOf(Response); + + const markdownResponse = response as Response; + expect(markdownResponse.headers.get("Content-Type")).toBe( + MARKDOWN_CONTENT_TYPE + ); + expect(await markdownResponse.text()).toBe(`# Blog + +Latest updates. +`); + }); + it("renders HTML-derived HEAD requests with GET so token counts are available", async () => { const onRequest = createDevMarkdownMiddleware(); const next = vi.fn( diff --git a/packages/astro-agent-markdown/src/html-sidecars.test.ts b/packages/astro-agent-markdown/src/html-sidecars.test.ts index 20f55398..c48fee70 100644 --- a/packages/astro-agent-markdown/src/html-sidecars.test.ts +++ b/packages/astro-agent-markdown/src/html-sidecars.test.ts @@ -67,6 +67,29 @@ describe("HTML Markdown sidecars", () => { expect(count).toBe(2); }); + it("exports Markdown sidecars for HTML pages missing from the assets map", async () => { + const outputDir = await fs.mkdtemp(path.join(os.tmpdir(), "html-md-")); + + await fs.mkdir(path.join(outputDir, "docs", "guide"), { + recursive: true, + }); + await fs.writeFile( + path.join(outputDir, "docs", "guide", "index.html"), + "

Guide

Run bounded queries.

" + ); + + const count = await exportHtmlMarkdownSidecars({ + assets: new Map(), + dir: pathToFileURL(`${outputDir}/`), + logger: createLogger(), + }); + + await expect( + fs.readFile(path.join(outputDir, "docs", "guide", "index.md"), "utf8") + ).resolves.toContain("# Guide"); + expect(count).toBe(1); + }); + it("does not overwrite content collection Markdown sidecars", async () => { const outputDir = await fs.mkdtemp(path.join(os.tmpdir(), "html-md-")); const blogDir = path.join(outputDir, "blog/post"); diff --git a/packages/astro-agent-markdown/src/html-sidecars.ts b/packages/astro-agent-markdown/src/html-sidecars.ts index 28e4ccec..1334378f 100644 --- a/packages/astro-agent-markdown/src/html-sidecars.ts +++ b/packages/astro-agent-markdown/src/html-sidecars.ts @@ -66,6 +66,33 @@ async function exportHtmlFile(input: { return true; } +async function collectHtmlFiles(directory: string) { + const htmlPaths: string[] = []; + + async function visit(currentDirectory: string) { + const entries = await fs.readdir(currentDirectory, { withFileTypes: true }); + + await Promise.all( + entries.map(async (entry) => { + const entryPath = path.join(currentDirectory, entry.name); + + if (entry.isDirectory()) { + await visit(entryPath); + return; + } + + if (entry.isFile() && entry.name.endsWith(HTML_EXTENSION)) { + htmlPaths.push(entryPath); + } + }) + ); + } + + await visit(directory); + + return htmlPaths; +} + export async function exportHtmlMarkdownSidecars( options: ExportHtmlMarkdownSidecarsOptions ) { @@ -81,6 +108,12 @@ export async function exportHtmlMarkdownSidecars( } } + // Workerd prerender builds can omit static HTML pages from the hook's assets + // map, so scan the emitted client directory as the source of truth. + for (const htmlPath of await collectHtmlFiles(outputDir)) { + htmlAssetPaths.add(htmlPath); + } + for (const htmlPath of htmlAssetPaths) { const routePath = getHtmlRoutePath(path.relative(outputDir, htmlPath));