diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore index 72fa0878d..f9b7115db 100644 --- a/apps/docs/.gitignore +++ b/apps/docs/.gitignore @@ -51,4 +51,5 @@ src/web/public/**/*.md src/web/public/llms.txt src/web/public/llms-full.txt src/web/public/sitemap.xml +src/web/public/pagefind/ .firecrawl diff --git a/apps/docs/package.json b/apps/docs/package.json index 408fe7221..36166dcea 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -77,6 +77,7 @@ "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", "@vitejs/plugin-react": "^4.5.2", + "pagefind": "^1.5.2", "playwright": "^1.58.1", "tailwindcss": "^4.1.18", "typescript": "^5", diff --git a/apps/docs/scripts/generate-markdown-files.ts b/apps/docs/scripts/generate-markdown-files.ts index 680f27e9b..2b747a8bf 100644 --- a/apps/docs/scripts/generate-markdown-files.ts +++ b/apps/docs/scripts/generate-markdown-files.ts @@ -10,11 +10,13 @@ import { readdir, readFile, mkdir, writeFile, rm } from 'node:fs/promises'; import { join, dirname, relative } from 'node:path'; import matter from 'gray-matter'; +import * as pagefind from 'pagefind'; import { navData, type NavItem } from '../src/web/components/docs/nav-data.ts'; const BASE_URL = 'https://agentuity.dev'; const CONTENT_DIR = join(import.meta.dir, '../src/web/content'); const OUTPUT_DIR = join(import.meta.dir, '../src/web/public'); +const PAGEFIND_OUTPUT_DIR = join(OUTPUT_DIR, 'pagefind'); interface DocPage { title: string; @@ -24,6 +26,21 @@ interface DocPage { content: string; } +interface SearchRecord { + title: string; + description: string; + urlPath: string; + section: string; + content: string; +} + +interface NavSearchItem { + title: string; + urlPath: string; + description: string; + section: string; +} + async function getAllMdxFiles(dir: string): Promise { const files: string[] = []; @@ -901,7 +918,121 @@ async function main() { const sitemapXml = generateSitemapXml(pages); await writeFile(join(OUTPUT_DIR, 'sitemap.xml'), sitemapXml, 'utf-8'); - console.log(`Generated ${pages.length} markdown files + llms.txt + llms-full.txt + sitemap.xml`); + await generatePagefindIndex(pages); + + console.log( + `Generated ${pages.length} markdown files + llms.txt + llms-full.txt + sitemap.xml + Pagefind index` + ); +} + +function collectNavSearchItems(): NavSearchItem[] { + const items: NavSearchItem[] = []; + + const walk = (navItems: NavItem[], section: string) => { + for (const item of navItems) { + if (item.url) { + items.push({ + title: item.title, + urlPath: item.url, + description: item.description ?? '', + section, + }); + } + if (item.items) { + walk(item.items, section); + } + } + }; + + for (const section of navData) { + if (section.url) { + items.push({ + title: section.title, + urlPath: section.url, + description: '', + section: section.title, + }); + } + walk(section.items, section.title); + } + + return items; +} + +function createSearchRecords(pages: DocPage[]): SearchRecord[] { + const navItems = collectNavSearchItems(); + const navByUrl = new Map(navItems.map((item) => [item.urlPath, item])); + const records = new Map(); + + for (const page of pages) { + const navItem = navByUrl.get(page.urlPath); + const section = navItem?.section ?? 'Docs'; + records.set(page.urlPath, { + title: page.title, + description: page.description, + urlPath: page.urlPath, + section, + content: page.content, + }); + } + + for (const item of navItems) { + if (records.has(item.urlPath)) { + continue; + } + records.set(item.urlPath, { + title: item.title, + description: item.description, + urlPath: item.urlPath, + section: item.section, + content: [item.title, item.description, item.section].filter(Boolean).join('\n\n'), + }); + } + + return Array.from(records.values()).sort((a, b) => a.urlPath.localeCompare(b.urlPath)); +} + +async function generatePagefindIndex(pages: DocPage[]): Promise { + const { errors, index } = await pagefind.createIndex({ + forceLanguage: 'en', + includeCharacters: '_:@/.-', + }); + + if (errors.length > 0) { + throw new Error(`Pagefind index setup failed: ${errors.join('; ')}`); + } + if (!index) { + throw new Error('Pagefind index setup failed without an error message'); + } + + const records = createSearchRecords(pages); + for (const record of records) { + const result = await index.addCustomRecord({ + url: record.urlPath, + content: record.content, + language: 'en', + meta: { + title: record.title, + description: record.description, + section: record.section, + }, + filters: { + section: [record.section], + }, + }); + + if (result.errors.length > 0) { + throw new Error(`Pagefind failed to index ${record.urlPath}: ${result.errors.join('; ')}`); + } + } + + await rm(PAGEFIND_OUTPUT_DIR, { recursive: true, force: true }); + const writeResult = await index.writeFiles({ outputPath: PAGEFIND_OUTPUT_DIR }); + if (writeResult.errors.length > 0) { + throw new Error(`Pagefind write failed: ${writeResult.errors.join('; ')}`); + } + + await pagefind.close(); } function generateLlmsTxt(pages: DocPage[]): string { diff --git a/apps/docs/src/web/components/docs/search-dialog.tsx b/apps/docs/src/web/components/docs/search-dialog.tsx index 9e7a3c870..a4304fd02 100644 --- a/apps/docs/src/web/components/docs/search-dialog.tsx +++ b/apps/docs/src/web/components/docs/search-dialog.tsx @@ -19,7 +19,6 @@ import { import { useNavigate } from '@tanstack/react-router'; import { Command, - CommandEmpty, CommandGroup, CommandInput, CommandItem, @@ -36,6 +35,7 @@ import { cn } from '../../lib/utils'; import { navData, type NavItem } from './nav-data'; import { useAISearch } from '../../hooks/use-ai-search'; import { AISearchMessages, AISearchActions } from './ai-search-messages'; +import { searchPagefind } from '../../lib/pagefind-search'; const MODE_STORAGE_KEY = 'agentuity-search-mode'; @@ -55,6 +55,14 @@ interface SearchItem { section: string; } +type PagefindStatus = 'idle' | 'loading' | 'ready' | 'error'; + +interface PagefindSearchState { + readonly query: string; + readonly items: SearchItem[]; + readonly status: PagefindStatus; +} + function collectItems(items: NavItem[], section: string): SearchItem[] { const result: SearchItem[] = []; for (const item of items) { @@ -77,6 +85,51 @@ function getAllItems(): SearchItem[] { return items; } +function getSearchWords(value: string): string[] { + return value.toLowerCase().split(/\W+/).filter(Boolean); +} + +function matchesWordPrefixes(value: string, terms: string[]): boolean { + const words = getSearchWords(value); + return terms.every((term) => words.some((word) => word.startsWith(term))); +} + +function getNavMatches(query: string, items: SearchItem[]): SearchItem[] { + const terms = getSearchWords(query); + if (terms.length === 0) { + return []; + } + + const titleMatches: SearchItem[] = []; + const otherMatches: SearchItem[] = []; + for (const item of items) { + if (matchesWordPrefixes(item.title, terms)) { + titleMatches.push(item); + } else if ( + matchesWordPrefixes(`${item.title} ${item.description ?? ''} ${item.section}`, terms) + ) { + otherMatches.push(item); + } + } + + return [...titleMatches, ...otherMatches]; +} + +function mergeSearchItems(primaryItems: SearchItem[], secondaryItems: SearchItem[]): SearchItem[] { + const seen = new Set(); + const result: SearchItem[] = []; + + for (const item of [...primaryItems, ...secondaryItems]) { + if (seen.has(item.url)) { + continue; + } + seen.add(item.url); + result.push(item); + } + + return result; +} + const sectionIcons: Record = { 'SDK Explorer': LayoutGridIcon, 'Get Started': RocketIcon, @@ -181,12 +234,13 @@ function renderTreeItems( const indent = depth * 16; if (item.url) { + const url = item.url; const Icon = depth === 0 ? sectionIcon : FileTextIcon; nodes.push( onSelect(item.url!)} + key={url} + value={url} + onSelect={() => onSelect(url)} style={{ paddingLeft: `${12 + indent}px` }} className="py-1.5" > @@ -224,6 +278,13 @@ function KeywordSearchContent({ onSwitchMode: () => void; }) { const [search, setSearch] = React.useState(''); + const [selectedValue, setSelectedValue] = React.useState(''); + const [pagefindState, setPagefindState] = React.useState({ + query: '', + items: [], + status: 'idle', + }); + const listRef = React.useRef(null); const allItems = React.useMemo(() => getAllItems(), []); const handleSelect = (url: string) => { @@ -231,19 +292,74 @@ function KeywordSearchContent({ onOpenChange(false); }; - const groupedItems = React.useMemo(() => { + React.useEffect(() => { + const query = search.trim(); + if (!query) { + setPagefindState({ query: '', items: [], status: 'idle' }); + return; + } + + let cancelled = false; + setPagefindState({ query, items: [], status: 'loading' }); + + void searchPagefind(query) + .then((results) => { + if (cancelled) { + return; + } + setPagefindState({ query, items: results, status: 'ready' }); + }) + .catch(() => { + if (cancelled) { + return; + } + setPagefindState({ query, items: [], status: 'error' }); + }); + + return () => { + cancelled = true; + }; + }, [search]); + + const query = search.trim(); + const pagefindItems = pagefindState.query === query ? pagefindState.items : []; + const pagefindStatus: PagefindStatus = + query && pagefindState.query !== query ? 'loading' : pagefindState.status; + const navMatches = React.useMemo(() => getNavMatches(query, allItems), [query, allItems]); + const searchItems = React.useMemo( + () => mergeSearchItems(navMatches, pagefindItems), + [navMatches, pagefindItems] + ); + + React.useEffect(() => { + const firstVisibleItem = query ? searchItems[0] : allItems[0]; + setSelectedValue(firstVisibleItem?.url ?? ''); + }, [query, searchItems, allItems]); + + React.useLayoutEffect(() => { + if (query || pagefindStatus === 'idle') { + listRef.current?.scrollTo({ top: 0 }); + } + }, [query, pagefindStatus]); + + const groupedSearchItems = React.useMemo(() => { const groups: Record = {}; - for (const item of allItems) { + for (const item of searchItems) { if (!groups[item.section]) { groups[item.section] = []; } groups[item.section]!.push(item); } return groups; - }, [allItems]); + }, [searchItems]); return ( - +
- - {search.trim() ? ( + + {query ? ( <> - No results found. - {Object.entries(groupedItems).map(([section, items]) => { + {pagefindStatus === 'loading' && searchItems.length === 0 && ( +
+ Searching... +
+ )} + {pagefindStatus !== 'loading' && searchItems.length === 0 && ( +
+ {pagefindStatus === 'error' + ? 'Search is unavailable right now.' + : 'No results found.'} +
+ )} + {Object.entries(groupedSearchItems).map(([section, items]) => { const Icon = sectionIcons[section] ?? FileTextIcon; return ( {items.map((item) => ( handleSelect(item.url)} className="items-start py-2.5" > diff --git a/apps/docs/src/web/lib/pagefind-search.ts b/apps/docs/src/web/lib/pagefind-search.ts new file mode 100644 index 000000000..03b199089 --- /dev/null +++ b/apps/docs/src/web/lib/pagefind-search.ts @@ -0,0 +1,103 @@ +const PAGEFIND_RESULT_LIMIT = 12; +const PAGEFIND_MODULE_PATH = '/pagefind/pagefind.js'; + +const QUERY_ALIASES = { + db: 'database Postgres SQL Drizzle', + otel: 'OpenTelemetry observability tracing telemetry', + rag: 'retrieval augmented generation vector search embeddings', +} as const; +const MIN_ALIAS_PREFIX_LENGTH = 3; + +export interface PagefindSearchItem { + title: string; + url: string; + description: string; + section: string; +} + +interface PagefindModule { + init: () => Promise | void; + options: (options: { excerptLength?: number }) => Promise; + debouncedSearch: ( + query: string, + options?: Record, + debounceTimeout?: number + ) => Promise; +} + +interface PagefindSearch { + results: readonly PagefindResult[]; +} + +interface PagefindResult { + data: () => Promise; +} + +interface PagefindResultData { + url: string; + plain_excerpt?: string; + meta?: { + title?: string; + description?: string; + section?: string; + }; +} + +let pagefindPromise: Promise | undefined; + +function normalizeWords(value: string): string { + return value.toLowerCase().split(/\W+/).filter(Boolean).join(' '); +} + +function expandSearchQuery(input: string): string { + const trimmedInput = input.trim(); + if (!trimmedInput) { + return input; + } + + const words = normalizeWords(trimmedInput).split(' '); + const aliases = Object.entries(QUERY_ALIASES) + .filter(([alias]) => + words.some( + (word) => + word === alias || (word.length >= MIN_ALIAS_PREFIX_LENGTH && alias.startsWith(word)) + ) + ) + .map(([, terms]) => terms); + + return aliases.length > 0 ? `${trimmedInput} ${aliases.join(' ')}` : trimmedInput; +} + +async function loadPagefind(): Promise { + if (!pagefindPromise) { + const pagefindUrl = new URL(PAGEFIND_MODULE_PATH, window.location.origin).toString(); + pagefindPromise = import(/* @vite-ignore */ pagefindUrl).then(async (module) => { + const pagefind: PagefindModule = module; + await pagefind.options({ excerptLength: 18 }); + await pagefind.init(); + return pagefind; + }); + } + + return pagefindPromise; +} + +export async function searchPagefind(input: string): Promise { + const query = expandSearchQuery(input); + const pagefind = await loadPagefind(); + const search = await pagefind.debouncedSearch(query, {}, 150); + if (!search) { + return []; + } + + const results = await Promise.all( + search.results.slice(0, PAGEFIND_RESULT_LIMIT).map((result) => result.data()) + ); + + return results.map((result) => ({ + title: result.meta?.title ?? result.url, + url: result.url, + description: result.meta?.description || result.plain_excerpt || '', + section: result.meta?.section ?? 'Docs', + })); +} diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json index aeba78570..654d2fea9 100644 --- a/apps/docs/tsconfig.json +++ b/apps/docs/tsconfig.json @@ -25,5 +25,6 @@ "@middleware/*": ["./src/middleware/*"] } }, - "include": ["src/**/*", "app.ts"] + "include": ["src/**/*", "app.ts"], + "exclude": ["src/web/public/pagefind"] } diff --git a/bun.lock b/bun.lock index cd6348937..f3ac69b5f 100644 --- a/bun.lock +++ b/bun.lock @@ -105,6 +105,7 @@ "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", "@vitejs/plugin-react": "^4.5.2", + "pagefind": "^1.5.2", "playwright": "^1.58.1", "tailwindcss": "^4.1.18", "typescript": "^5", @@ -1800,6 +1801,20 @@ "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.12", "", { "os": "win32", "cpu": "x64" }, "sha512-rV21md7QWnu3r/shev7IFMh6hX8BJHwofxESAofUT4yH866oCIbcNbzp6+fxrj4oGD8uisP6WoaTCboijv9yYg=="], + "@pagefind/darwin-arm64": ["@pagefind/darwin-arm64@1.5.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ=="], + + "@pagefind/darwin-x64": ["@pagefind/darwin-x64@1.5.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw=="], + + "@pagefind/freebsd-x64": ["@pagefind/freebsd-x64@1.5.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA=="], + + "@pagefind/linux-arm64": ["@pagefind/linux-arm64@1.5.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw=="], + + "@pagefind/linux-x64": ["@pagefind/linux-x64@1.5.2", "", { "os": "linux", "cpu": "x64" }, "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA=="], + + "@pagefind/windows-arm64": ["@pagefind/windows-arm64@1.5.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g=="], + + "@pagefind/windows-x64": ["@pagefind/windows-x64@1.5.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg=="], + "@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="], "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.6", "", { "os": "android", "cpu": "arm64" }, "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A=="], @@ -3708,6 +3723,8 @@ "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + "pagefind": ["pagefind@1.5.2", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.5.2", "@pagefind/darwin-x64": "1.5.2", "@pagefind/freebsd-x64": "1.5.2", "@pagefind/linux-arm64": "1.5.2", "@pagefind/linux-x64": "1.5.2", "@pagefind/windows-arm64": "1.5.2", "@pagefind/windows-x64": "1.5.2" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q=="], + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], @@ -4398,6 +4415,8 @@ "@agentuity/auth/@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + "@agentuity/cli/@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], + "@agentuity/cli/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], "@agentuity/coder-tui/@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], @@ -4414,6 +4433,10 @@ "@agentuity/drizzle/drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], + "@agentuity/evals/@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], + + "@agentuity/evals/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + "@agentuity/frontend/@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], "@agentuity/frontend/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], @@ -4430,6 +4453,10 @@ "@agentuity/runtime/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + "@agentuity/schedule/@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], + + "@agentuity/schedule/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + "@agentuity/schema/@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], "@agentuity/schema/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], @@ -4440,6 +4467,8 @@ "@agentuity/test-utils/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + "@agentuity/workbench/@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], + "@agentuity/workbench/@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], "@agentuity/workbench/bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.14", "", { "dependencies": { "tailwindcss": "4.0.0-beta.9" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-Ge8M8DQsRDErCzH/uI8pYjx5vZWXxQvnwM/xMQMElxQqHieGbAopfYo/q/kllkPkRbFHiwhnHwTpRMAMJZCjug=="], @@ -4718,6 +4747,8 @@ "docs/@ai-sdk/openai": ["@ai-sdk/openai@2.0.102", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tYarHJhyMioGegsnhpqz1/tKoCAJJ6zBHoIQaredNkt8V3o/JXj2647NnEOJVe7WHQXGvCfzbfnP1TADFhPmcA=="], + "docs/@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], + "docs/@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], "docs/ai": ["ai@5.0.173", "", { "dependencies": { "@ai-sdk/gateway": "2.0.77", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SnzPzJ5Y9rMdMEBHBhleMkfnSzEWwezk87MswKxAH/3iiX00yOjmd6JEZM2+whBpJU4xMPTz8iBVw165jDq16g=="], @@ -5016,6 +5047,8 @@ "docs/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-60GYsRj5wIJQRcq5YwYJq4KhwLeStceXEJiZdecP1miiH+6FMmrnc7lZDOJoQ6m9lrudEb+uI4LEwddLz5+rPQ=="], + "docs/@types/bun/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + "docs/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], "docs/@vitejs/plugin-react/react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], diff --git a/packages/cli/src/cmd/build/vite/vite-asset-server-config.ts b/packages/cli/src/cmd/build/vite/vite-asset-server-config.ts index 6b52d8f57..1f22d8892 100644 --- a/packages/cli/src/cmd/build/vite/vite-asset-server-config.ts +++ b/packages/cli/src/cmd/build/vite/vite-asset-server-config.ts @@ -126,7 +126,7 @@ function spaFallbackPlugin(rootDir: string, routePaths: string[], workbenchPath? const isDocumentRequest = secFetchDest === 'document' || accept.includes('text/html'); // Skip file requests (have an extension) - if (pathname !== '/' && /\.[a-zA-Z0-9]+$/.test(pathname)) return next(); + if (pathname !== '/' && /\.[\w-]+$/.test(pathname)) return next(); // For non-document requests, only allow root path fallback. // (e.g. don't turn module/script fetches into HTML accidentally)