From 2fdfb9885908ee370216c22b016580b4cde94ec9 Mon Sep 17 00:00:00 2001 From: PunGrumpy <108584943+PunGrumpy@users.noreply.github.com> Date: Thu, 22 Oct 2026 10:03:26 +0700 Subject: [PATCH 1/8] test(web): add a rank check for the docs search Bumping fumadocs from 16.15 to 16.17 changed where pages land in /api/search, and nothing in the repo could show it. The script asks one or more running builds of apps/web for a fixed set of queries and prints the rank of the page that documents each one, so two builds can sit side by side in one table. On the parent of the bump (77a4430) against main (a6f431b): | query | page | 77a4430 | a6f431b | | --------- | ------------------------ | --------- | --------- | | filter | /docs/core/filters | #1 of 94 | #32 of 60 | | upsert | /docs/core/store | #1 of 34 | #30 of 33 | | eq | /docs/core/filters | #1 of 36 | #10 of 33 | | namespace | /docs/guides/migration | #1 of 82 | #10 of 60 | | qdrant | /docs/providers/qdrant | #16 of 43 | #1 of 57 | --- scripts/search-ranks.ts | 108 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 scripts/search-ranks.ts diff --git a/scripts/search-ranks.ts b/scripts/search-ranks.ts new file mode 100644 index 0000000..afcddac --- /dev/null +++ b/scripts/search-ranks.ts @@ -0,0 +1,108 @@ +// Rank check for the docs search. Fetches /api/search from one or more running +// builds of apps/web and reports where the page that documents each query lands. +// +// bun scripts/search-ranks.ts main=http://localhost:3000 +// bun scripts/search-ranks.ts before=http://localhost:4061 after=http://localhost:4062 +// bun scripts/search-ranks.ts --top 8 after=http://localhost:3000 +// +// Start a build with `bun run build && bun run start` inside apps/web. +import { parseArgs } from "node:util"; + +interface Probe { + readonly query: string; + readonly page: string; +} + +interface Target { + readonly label: string; + readonly base: string; +} + +interface SearchResult { + readonly url: string; +} + +const PROBES: readonly Probe[] = [ + { page: "/docs/core/filters", query: "filter" }, + { page: "/docs/core/store", query: "upsert" }, + { page: "/docs/core/filters", query: "eq" }, + { page: "/docs/guides/migration", query: "namespace" }, + { page: "/docs/providers/upstash", query: "namespace" }, + { page: "/docs/providers/qdrant", query: "qdrant" }, +]; + +const pagePath = (url: string): string => url.split("#")[0] ?? url; + +const search = async (base: string, query: string): Promise => { + const url = `${base}/api/search?query=${encodeURIComponent(query)}`; + const response = await fetch(url); + if (!response.ok) { + throw new Error(`${url} returned ${response.status}`); + } + // SAFETY: /api/search returns a JSON array of results that each carry a `url`. + return (await response.json()) as SearchResult[]; +}; + +const rankOf = (results: readonly SearchResult[], page: string): string => { + const index = results.findIndex((result) => pagePath(result.url) === page); + return index === -1 ? "-" : `#${index + 1}`; +}; + +const parseTarget = (arg: string): Target => { + const separator = arg.indexOf("="); + const label = arg.slice(0, separator); + const base = arg.slice(separator + 1).replace(/\/$/u, ""); + if (separator < 1 || !base) { + throw new Error(`Expected