mirror of
https://github.com/heroui-inc/heroui.git
synced 2026-09-18 18:55:01 +08:00
fix(docs): improve heroui.com agent readiness (#6810)
* fix(docs): improve heroui.com agent readiness * fix(docs): refine agent trust metadata * fix(docs): use junior@heroui.com on trust pages --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
"prebuild": "node scripts/build-skills.mjs && node scripts/build-theme-presets.mjs",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"test": "vitest run",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix",
|
||||
"typecheck": "tsc --noEmit",
|
||||
@@ -79,6 +80,7 @@
|
||||
"postcss": "8.5.23",
|
||||
"source-map-support": "0.5.21",
|
||||
"tailwindcss": "4.3.0",
|
||||
"tar": "7.5.21"
|
||||
"tar": "7.5.21",
|
||||
"vitest": "4.1.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,20 @@ export async function GET(request: Request) {
|
||||
{
|
||||
anchor: agentApi,
|
||||
"service-desc": [
|
||||
{
|
||||
href: absoluteUrl(origin, "/openapi.json"),
|
||||
type: "application/vnd.oai.openapi+json",
|
||||
},
|
||||
{
|
||||
href: absoluteUrl(origin, "/.well-known/openapi/heroui-agent-api.json"),
|
||||
type: "application/vnd.oai.openapi+json",
|
||||
},
|
||||
],
|
||||
"service-doc": [
|
||||
{
|
||||
href: absoluteUrl(origin, "/api/agent"),
|
||||
type: "application/json",
|
||||
},
|
||||
{
|
||||
href: absoluteUrl(origin, "/docs/react/getting-started/llms-txt"),
|
||||
type: "text/html",
|
||||
@@ -43,6 +51,25 @@ export async function GET(request: Request) {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
anchor: absoluteUrl(origin, "/.well-known/mcp"),
|
||||
"service-desc": [
|
||||
{
|
||||
href: absoluteUrl(origin, "/.well-known/mcp/server-card.json"),
|
||||
type: "application/json",
|
||||
},
|
||||
],
|
||||
"service-doc": [
|
||||
{
|
||||
href: absoluteUrl(origin, "/docs/react/getting-started/mcp-server"),
|
||||
type: "text/html",
|
||||
},
|
||||
{
|
||||
href: absoluteUrl(origin, "/docs/native/getting-started/mcp-server"),
|
||||
type: "text/html",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
anchor: REACT_MCP_API_URL,
|
||||
"service-desc": [
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import {getRequestOrigin, jsonResponse} from "@/lib/agent-discovery";
|
||||
import {getMcpServerCard} from "@/lib/agent-mcp";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = false;
|
||||
|
||||
export function GET(request: Request) {
|
||||
return jsonResponse(getMcpServerCard(getRequestOrigin(request)), {
|
||||
headers: {
|
||||
Link: '</.well-known/mcp/server-card.json>; rel="service-desc"; type="application/json"',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,112 +1,9 @@
|
||||
import {
|
||||
MCP_PACKAGE_VERSION,
|
||||
NATIVE_MCP_API_URL,
|
||||
REACT_MCP_API_URL,
|
||||
absoluteUrl,
|
||||
getRequestOrigin,
|
||||
jsonResponse,
|
||||
} from "@/lib/agent-discovery";
|
||||
import {getRequestOrigin, jsonResponse} from "@/lib/agent-discovery";
|
||||
import {getMcpServerCard} from "@/lib/agent-mcp";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = false;
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const origin = getRequestOrigin(request);
|
||||
|
||||
return jsonResponse({
|
||||
$schema: "https://modelcontextprotocol.io/schemas/server-card/draft.json",
|
||||
capabilities: {
|
||||
prompts: false,
|
||||
resources: false,
|
||||
tools: true,
|
||||
},
|
||||
description:
|
||||
"HeroUI MCP servers expose read-only HeroUI React and HeroUI Native documentation, component metadata, source references, styles, and theme variables to AI coding agents.",
|
||||
endpoint: absoluteUrl(origin, "/.well-known/mcp/server-card.json"),
|
||||
links: {
|
||||
docs: [
|
||||
absoluteUrl(origin, "/docs/react/getting-started/mcp-server"),
|
||||
absoluteUrl(origin, "/docs/native/getting-started/mcp-server"),
|
||||
],
|
||||
npm: [
|
||||
"https://www.npmjs.com/package/@heroui/react-mcp",
|
||||
"https://www.npmjs.com/package/@heroui/native-mcp",
|
||||
],
|
||||
source: "https://github.com/heroui-inc/heroui-mcp",
|
||||
},
|
||||
notes:
|
||||
"The supported MCP transport today is stdio through the published npm packages. The endpoint field identifies this server card for browser and catalog discovery; it is not a Streamable HTTP MCP endpoint.",
|
||||
serverInfo: {
|
||||
name: "HeroUI MCP",
|
||||
version: MCP_PACKAGE_VERSION,
|
||||
},
|
||||
tools: [
|
||||
{
|
||||
description: "List all available HeroUI v3 React components.",
|
||||
name: "list_components",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get complete React component documentation.",
|
||||
name: "get_component_docs",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get React component TypeScript source code.",
|
||||
name: "get_component_source_code",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get React component CSS source styles.",
|
||||
name: "get_component_source_styles",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get HeroUI React theme variables.",
|
||||
name: "get_theme_variables",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "Browse full HeroUI React documentation.",
|
||||
name: "get_docs",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "List all available HeroUI Native components.",
|
||||
name: "list_components",
|
||||
package: "@heroui/native-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get complete Native component documentation.",
|
||||
name: "get_component_docs",
|
||||
package: "@heroui/native-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get HeroUI Native theme variables.",
|
||||
name: "get_theme_variables",
|
||||
package: "@heroui/native-mcp",
|
||||
},
|
||||
{
|
||||
description: "Browse full HeroUI Native documentation.",
|
||||
name: "get_docs",
|
||||
package: "@heroui/native-mcp",
|
||||
},
|
||||
],
|
||||
transports: [
|
||||
{
|
||||
args: ["-y", "@heroui/react-mcp@latest"],
|
||||
command: "npx",
|
||||
dataApi: REACT_MCP_API_URL,
|
||||
package: "@heroui/react-mcp",
|
||||
type: "stdio",
|
||||
},
|
||||
{
|
||||
args: ["-y", "@heroui/native-mcp@latest"],
|
||||
command: "npx",
|
||||
dataApi: NATIVE_MCP_API_URL,
|
||||
package: "@heroui/native-mcp",
|
||||
type: "stdio",
|
||||
},
|
||||
],
|
||||
});
|
||||
export function GET(request: Request) {
|
||||
return jsonResponse(getMcpServerCard(getRequestOrigin(request)));
|
||||
}
|
||||
|
||||
@@ -1,225 +1,22 @@
|
||||
import {
|
||||
AGENT_API_VERSION,
|
||||
NATIVE_MCP_API_URL,
|
||||
OPENAPI_HEADERS,
|
||||
REACT_MCP_API_URL,
|
||||
absoluteUrl,
|
||||
getAgentServiceBaseUrl,
|
||||
getRequestOrigin,
|
||||
} from "@/lib/agent-discovery";
|
||||
import {OPENAPI_HEADERS, absoluteUrl, getRequestOrigin, jsonResponse} from "@/lib/agent-discovery";
|
||||
import {OPENAPI_BUILDERS} from "@/lib/agent-openapi";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = false;
|
||||
|
||||
type OpenAPIDocument = Record<string, unknown>;
|
||||
|
||||
function baseDocument(
|
||||
title: string,
|
||||
version: string,
|
||||
serverUrl: string,
|
||||
description: string,
|
||||
): OpenAPIDocument {
|
||||
return {
|
||||
info: {
|
||||
description,
|
||||
title,
|
||||
version,
|
||||
},
|
||||
openapi: "3.1.0",
|
||||
servers: [{url: serverUrl}],
|
||||
};
|
||||
}
|
||||
|
||||
function agentApiDocument(origin: string): OpenAPIDocument {
|
||||
return {
|
||||
...baseDocument(
|
||||
"HeroUI Docs Agent API",
|
||||
AGENT_API_VERSION,
|
||||
getAgentServiceBaseUrl(origin),
|
||||
"Read-only endpoints that help agents search HeroUI documentation and retrieve markdown page content.",
|
||||
),
|
||||
paths: {
|
||||
"/health": {
|
||||
get: {
|
||||
operationId: "getAgentApiHealth",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Agent API health status",
|
||||
},
|
||||
},
|
||||
summary: "Check service health",
|
||||
},
|
||||
},
|
||||
"/page": {
|
||||
get: {
|
||||
operationId: "getHeroUIDocPageMarkdown",
|
||||
parameters: [
|
||||
{
|
||||
description: "HeroUI docs page URL, for example /docs/react/components/button",
|
||||
in: "query",
|
||||
name: "url",
|
||||
required: true,
|
||||
schema: {type: "string"},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
"200": {description: "Documentation page metadata and markdown"},
|
||||
"404": {description: "Documentation page not found"},
|
||||
},
|
||||
summary: "Retrieve a documentation page as markdown",
|
||||
},
|
||||
},
|
||||
"/search": {
|
||||
get: {
|
||||
operationId: "searchHeroUIDocs",
|
||||
parameters: [
|
||||
{in: "query", name: "q", required: true, schema: {type: "string"}},
|
||||
{
|
||||
in: "query",
|
||||
name: "platform",
|
||||
schema: {default: "all", enum: ["all", "react", "native"], type: "string"},
|
||||
},
|
||||
{
|
||||
in: "query",
|
||||
name: "limit",
|
||||
schema: {default: 10, maximum: 20, minimum: 1, type: "integer"},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
"200": {description: "Matching documentation pages"},
|
||||
"400": {description: "Invalid request"},
|
||||
},
|
||||
summary: "Search HeroUI documentation",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mcpApiDocument(kind: "react" | "native"): OpenAPIDocument {
|
||||
const isReact = kind === "react";
|
||||
const serverUrl = isReact ? REACT_MCP_API_URL : NATIVE_MCP_API_URL;
|
||||
const title = isReact ? "HeroUI React MCP Data API" : "HeroUI Native MCP Data API";
|
||||
const componentSourcePaths = isReact
|
||||
? {
|
||||
"/v1/components/source": {
|
||||
post: {
|
||||
operationId: "getComponentSource",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
properties: {components: {items: {type: "string"}, type: "array"}},
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {"200": {description: "Component source code"}},
|
||||
summary: "Get component TypeScript source",
|
||||
},
|
||||
},
|
||||
"/v1/components/styles": {
|
||||
post: {
|
||||
operationId: "getComponentStyles",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
properties: {components: {items: {type: "string"}, type: "array"}},
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {"200": {description: "Component CSS styles"}},
|
||||
summary: "Get component styles",
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
...baseDocument(
|
||||
title,
|
||||
"1.1.0",
|
||||
serverUrl,
|
||||
`Read-only data API used by the ${isReact ? "@heroui/react-mcp" : "@heroui/native-mcp"} package and HeroUI agent skills.`,
|
||||
),
|
||||
paths: {
|
||||
"/health": {
|
||||
get: {
|
||||
operationId: "getHealth",
|
||||
responses: {"200": {description: "Health status"}},
|
||||
summary: "Check API health",
|
||||
},
|
||||
},
|
||||
"/v1/components": {
|
||||
get: {
|
||||
operationId: "listComponents",
|
||||
responses: {"200": {description: "Available components"}},
|
||||
summary: "List available components",
|
||||
},
|
||||
},
|
||||
"/v1/components/docs": {
|
||||
post: {
|
||||
operationId: "getComponentDocs",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
properties: {components: {items: {type: "string"}, type: "array"}},
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {"200": {description: "Component documentation"}},
|
||||
summary: "Get component documentation",
|
||||
},
|
||||
},
|
||||
...componentSourcePaths,
|
||||
"/v1/docs/{path}": {
|
||||
get: {
|
||||
operationId: "getDocs",
|
||||
parameters: [{in: "path", name: "path", required: true, schema: {type: "string"}}],
|
||||
responses: {"200": {description: "Documentation content"}},
|
||||
summary: "Get documentation by path",
|
||||
},
|
||||
},
|
||||
"/v1/themes/variables": {
|
||||
get: {
|
||||
operationId: "getThemeVariables",
|
||||
parameters: [{in: "query", name: "theme", schema: {default: "default", type: "string"}}],
|
||||
responses: {"200": {description: "Theme variables"}},
|
||||
summary: "Get theme variables",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const OPENAPI_BUILDERS: Record<string, (origin: string) => OpenAPIDocument> = {
|
||||
"heroui-agent-api.json": agentApiDocument,
|
||||
"heroui-native-mcp-api.json": () => mcpApiDocument("native"),
|
||||
"heroui-react-mcp-api.json": () => mcpApiDocument("react"),
|
||||
};
|
||||
|
||||
export async function GET(request: Request, {params}: {params: Promise<{api: string}>}) {
|
||||
const {api} = await params;
|
||||
const builder = OPENAPI_BUILDERS[api];
|
||||
|
||||
if (!builder) {
|
||||
return new Response(
|
||||
JSON.stringify(
|
||||
{available: Object.keys(OPENAPI_BUILDERS), error: "OpenAPI description not found"},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
return jsonResponse(
|
||||
{
|
||||
headers: {"Content-Type": "application/json; charset=utf-8"},
|
||||
status: 404,
|
||||
code: "OPENAPI_NOT_FOUND",
|
||||
error: true,
|
||||
hint: `Use one of: ${Object.keys(OPENAPI_BUILDERS).join(", ")}.`,
|
||||
message: "The requested HeroUI OpenAPI description was not found.",
|
||||
},
|
||||
{status: 404},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import type {Metadata} from "next";
|
||||
|
||||
import {TrustPage} from "../components/trust-page";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
alternates: {canonical: "/about"},
|
||||
description:
|
||||
"Learn about HeroUI, the open-source React and React Native UI library founded by Junior Garcia and backed by Y Combinator.",
|
||||
title: {absolute: "About HeroUI"},
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<TrustPage
|
||||
description="HeroUI is an open-source UI library for building accessible, customizable web and mobile interfaces."
|
||||
title="About HeroUI"
|
||||
>
|
||||
<section>
|
||||
<h2>What HeroUI publishes</h2>
|
||||
<p>
|
||||
HeroUI provides components for React applications on the web and for React Native
|
||||
applications on mobile. The project focuses on accessible behavior, customizable
|
||||
composition, typed developer APIs, and polished defaults that teams can adapt to their own
|
||||
products. The public documentation at <a href="/">heroui.com</a> includes installation
|
||||
guides, component references, examples, theming resources, release notes, and migration
|
||||
guidance.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Open source and developer tools</h2>
|
||||
<p>
|
||||
HeroUI source code is available in the{" "}
|
||||
<a href="https://github.com/heroui-inc/heroui">official GitHub repository</a>. Official
|
||||
packages include <code>@heroui/react</code>, <code>@heroui/react-mcp</code>, and{" "}
|
||||
<code>@heroui/native-mcp</code>. The official <code>heroui-cli</code> is documented in the{" "}
|
||||
<a href="/docs/react/getting-started/cli">HeroUI CLI guide</a>. Developers and coding
|
||||
agents can inspect the same public source and documentation rather than relying on an
|
||||
undocumented integration.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Company and founder</h2>
|
||||
<p>
|
||||
HeroUI is a Y Combinator S24 company founded by Junior Garcia. The library remains openly
|
||||
documented so developers can evaluate how it works, review its source, and use its
|
||||
published packages under the terms provided in the repository. For ways to reach the team,
|
||||
visit the <a href="/contact">HeroUI contact page</a>.
|
||||
</p>
|
||||
</section>
|
||||
</TrustPage>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type {ReactNode} from "react";
|
||||
|
||||
import {Footer} from "@/components/footer";
|
||||
import {getDictionary} from "@/lib/dictionaries";
|
||||
|
||||
export async function TrustPage({
|
||||
children,
|
||||
description,
|
||||
title,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
description: string;
|
||||
title: string;
|
||||
}) {
|
||||
const dict = await getDictionary("en");
|
||||
|
||||
return (
|
||||
<main className="flex min-h-[calc(100vh-4rem)] flex-col">
|
||||
<article className="mx-auto w-full max-w-3xl flex-1 px-6 py-16 sm:py-24">
|
||||
<header className="mb-10 border-b border-border pb-8">
|
||||
<p className="mb-3 text-sm font-medium text-muted">HeroUI</p>
|
||||
<h1 className="text-4xl font-bold tracking-tight text-foreground">{title}</h1>
|
||||
<p className="mt-4 text-lg leading-8 text-muted">{description}</p>
|
||||
</header>
|
||||
<div className="space-y-8 text-base leading-7 text-muted [&_a]:font-medium [&_a]:text-foreground [&_a]:underline [&_a]:underline-offset-4 [&_h2]:text-2xl [&_h2]:font-semibold [&_h2]:tracking-tight [&_h2]:text-foreground [&_p]:mt-3">
|
||||
{children}
|
||||
</div>
|
||||
</article>
|
||||
<Footer dict={dict.footer} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import type {Metadata} from "next";
|
||||
|
||||
import {TrustPage} from "../components/trust-page";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
alternates: {canonical: "/contact"},
|
||||
description:
|
||||
"Contact HeroUI for sales, partnerships, general questions, and privacy questions using verified public email addresses.",
|
||||
title: {absolute: "Contact HeroUI"},
|
||||
};
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<TrustPage
|
||||
description="Use these verified channels for questions about HeroUI, its open-source libraries, and its developer resources."
|
||||
title="Contact HeroUI"
|
||||
>
|
||||
<section>
|
||||
<h2>Email contacts</h2>
|
||||
<p>
|
||||
For sales and partnership questions, email{" "}
|
||||
<a href="mailto:sales@heroui.com">sales@heroui.com</a>. For general or privacy-related
|
||||
questions, email <a href="mailto:junior@heroui.com">junior@heroui.com</a>. HeroUI does not
|
||||
list a public street address or phone number in this repository, so this page does not
|
||||
present one. These email addresses are the direct, verifiable contact options currently
|
||||
published by the project.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Technical questions</h2>
|
||||
<p>
|
||||
Before writing, check the <a href="/docs/react/getting-started">React documentation</a>,{" "}
|
||||
<a href="/docs/native/getting-started">React Native documentation</a>, and the{" "}
|
||||
<a href="https://github.com/heroui-inc/heroui">HeroUI GitHub repository</a>. They contain
|
||||
installation guides, component APIs, examples, migration resources, and issue tracking.
|
||||
When asking a technical question, include the platform, package version, a small
|
||||
reproduction, and what you already tried. That context makes the request easier to
|
||||
understand without requiring access to a private application.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>What not to send</h2>
|
||||
<p>
|
||||
This public documentation site does not provide a contact form or secure support portal.
|
||||
Do not email passwords, authentication tokens, payment card numbers, government
|
||||
identification, or other sensitive personal information. For more context about this
|
||||
documentation site and email communication, read the{" "}
|
||||
<a href="/privacy">HeroUI privacy page</a>.
|
||||
</p>
|
||||
</section>
|
||||
</TrustPage>
|
||||
);
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export default async function HomePage({params}: {params: Promise<{lang: string}
|
||||
<div className="mx-auto flex max-w-2xl flex-col items-center justify-center">
|
||||
<ReleaseBadges badges={home.releaseBadges} />
|
||||
<h1 className="mt-2 text-3xl font-bold tracking-tight text-foreground sm:text-4xl lg:mt-4 lg:text-5xl">
|
||||
{home.titleMain} <div className="text-muted/70">{home.titleMuted}</div>
|
||||
{home.titleMain} <span className="block text-muted/70">{home.titleMuted}</span>
|
||||
</h1>
|
||||
<p className="text-balance text-muted md:text-lg">{home.description}</p>
|
||||
<div className="mt-4 flex gap-3">
|
||||
@@ -102,6 +102,30 @@ export default async function HomePage({params}: {params: Promise<{lang: string}
|
||||
{home.githubStarsSuffix}
|
||||
</span>
|
||||
</a>
|
||||
<section
|
||||
aria-labelledby="heroui-overview"
|
||||
className="sr-only"
|
||||
lang={lang === "cn" ? "en" : undefined}
|
||||
>
|
||||
<h2 id="heroui-overview">Build accessible products with HeroUI</h2>
|
||||
<p>
|
||||
HeroUI is an open-source component library for React web and React Native
|
||||
applications. The web library combines React Aria Components with Tailwind CSS v4 to
|
||||
provide accessible behavior, typed APIs, customizable composition, and polished
|
||||
defaults. Teams can inspect the source, use the free packages, and adapt each
|
||||
component to their own design system.
|
||||
</p>
|
||||
<h3>Documentation for developers and coding agents</h3>
|
||||
<p>
|
||||
The HeroUI documentation includes installation guides, component APIs, examples,
|
||||
theming references, migration instructions, and an official CLI guide. Coding agents
|
||||
can use the generated llms.txt indexes, request documentation as markdown, search the
|
||||
public Docs Agent API, read its OpenAPI specification, or connect the existing HeroUI
|
||||
React and HeroUI Native MCP packages. These machine-readable resources expose the same
|
||||
public documentation without requiring a paid plan or weakening authentication for
|
||||
private services.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
<DemoShowcase />
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import type {Metadata} from "next";
|
||||
|
||||
import {TrustPage} from "../components/trust-page";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
alternates: {canonical: "/privacy"},
|
||||
description:
|
||||
"Plain-language privacy information for the public HeroUI documentation website and HeroUI contact emails.",
|
||||
title: {absolute: "HeroUI Privacy"},
|
||||
};
|
||||
|
||||
export default function PrivacyPage() {
|
||||
return (
|
||||
<TrustPage
|
||||
description="Plain-language context for the public HeroUI documentation website and the contact addresses listed here."
|
||||
title="HeroUI Privacy"
|
||||
>
|
||||
<section>
|
||||
<h2>Scope of this page</h2>
|
||||
<p>
|
||||
This page applies to the public HeroUI documentation website at heroui.com, including the
|
||||
About, Contact, and Privacy pages, and to messages people choose to send to the contact
|
||||
addresses published on this site. HeroUI primarily uses this website to publish
|
||||
documentation and resources for its open-source React web and React Native UI libraries.
|
||||
Browsing these public pages does not require a HeroUI account.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Information you choose to share</h2>
|
||||
<p>
|
||||
If you email <a href="mailto:sales@heroui.com">sales@heroui.com</a> or{" "}
|
||||
<a href="mailto:junior@heroui.com">junior@heroui.com</a>, you control what you include in
|
||||
the message. Do not send passwords, authentication tokens, financial account details,
|
||||
government identification numbers, or other sensitive personal information by email.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Site analytics and skill installation</h2>
|
||||
<p>
|
||||
HeroUI uses Vercel Analytics on the documentation site. When the public{" "}
|
||||
<code>/install</code> route is used to download an agent-skill installer, the production
|
||||
service records an installation event in PostHog with request metadata supplied by the
|
||||
hosting platform: IP address, city, region, postal code, country, user agent, referring
|
||||
page, and requested URL. This page does not claim a retention period or other processing
|
||||
detail that HeroUI has not published.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Library use and questions</h2>
|
||||
<p>
|
||||
Using HeroUI components in an application is separate from visiting this documentation
|
||||
website. Developers who build with HeroUI are responsible for the privacy practices of
|
||||
their own applications and for explaining those practices to their users. For a
|
||||
privacy-related question about heroui.com or email sent to the addresses above, contact{" "}
|
||||
<a href="mailto:junior@heroui.com">junior@heroui.com</a>. If you need a detail that is not
|
||||
documented here, ask directly rather than assuming a policy or practice.
|
||||
</p>
|
||||
</section>
|
||||
</TrustPage>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import {agentErrorResponse} from "@/lib/agent-discovery";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = false;
|
||||
|
||||
function notFoundResponse() {
|
||||
return agentErrorResponse({
|
||||
code: "AGENT_ENDPOINT_NOT_FOUND",
|
||||
hint: "Read /openapi.json or GET /api/agent for the available endpoints.",
|
||||
message: "The requested HeroUI agent API endpoint does not exist.",
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
export const DELETE = notFoundResponse;
|
||||
export const GET = notFoundResponse;
|
||||
export const PATCH = notFoundResponse;
|
||||
export const POST = notFoundResponse;
|
||||
export const PUT = notFoundResponse;
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {NextRequest} from "next/server";
|
||||
|
||||
import {getAgentDocPage} from "@/lib/agent-api";
|
||||
import {jsonResponse} from "@/lib/agent-discovery";
|
||||
import {agentErrorResponse, jsonResponse} from "@/lib/agent-discovery";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = false;
|
||||
@@ -10,13 +10,23 @@ export async function GET(request: NextRequest) {
|
||||
const url = request.nextUrl.searchParams.get("url") ?? request.nextUrl.searchParams.get("path");
|
||||
|
||||
if (!url) {
|
||||
return jsonResponse({error: "Missing required query parameter: url"}, {status: 400});
|
||||
return agentErrorResponse({
|
||||
code: "MISSING_PAGE_URL",
|
||||
hint: "Add a HeroUI docs path, for example ?url=/docs/react/components/button.",
|
||||
message: "The url query parameter is required.",
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const page = await getAgentDocPage(url);
|
||||
|
||||
if (!page) {
|
||||
return jsonResponse({error: "Documentation page not found", url}, {status: 404});
|
||||
return agentErrorResponse({
|
||||
code: "PAGE_NOT_FOUND",
|
||||
hint: "Use /api/agent/search to find a canonical HeroUI documentation URL.",
|
||||
message: `No HeroUI documentation page was found for ${url}.`,
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
return jsonResponse(page);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import {AGENT_API_VERSION, jsonResponse} from "@/lib/agent-discovery";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = false;
|
||||
|
||||
export function GET() {
|
||||
return jsonResponse({
|
||||
description: "Public, read-only HeroUI documentation API for coding agents.",
|
||||
endpoints: {
|
||||
health: "/api/agent/health",
|
||||
openapi: "/openapi.json",
|
||||
page: "/api/agent/page?url=/docs/react/components/button",
|
||||
search: "/api/agent/search?q=button&platform=react",
|
||||
},
|
||||
name: "HeroUI Docs Agent API",
|
||||
version: AGENT_API_VERSION,
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {NextRequest} from "next/server";
|
||||
|
||||
import {parseAgentLimit, parseAgentSearchPlatform, searchAgentDocs} from "@/lib/agent-api";
|
||||
import {jsonResponse} from "@/lib/agent-discovery";
|
||||
import {agentErrorResponse, jsonResponse} from "@/lib/agent-discovery";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = false;
|
||||
@@ -10,11 +10,42 @@ export async function GET(request: NextRequest) {
|
||||
const query = request.nextUrl.searchParams.get("q")?.trim() ?? "";
|
||||
|
||||
if (!query) {
|
||||
return jsonResponse({error: "Missing required query parameter: q"}, {status: 400});
|
||||
return agentErrorResponse({
|
||||
code: "MISSING_QUERY",
|
||||
hint: "Add a non-empty q query parameter, for example ?q=button.",
|
||||
message: "The q query parameter is required.",
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const platform = parseAgentSearchPlatform(request.nextUrl.searchParams.get("platform"));
|
||||
const limit = parseAgentLimit(request.nextUrl.searchParams.get("limit"));
|
||||
const platformParam = request.nextUrl.searchParams.get("platform");
|
||||
|
||||
if (platformParam && !["all", "react", "native"].includes(platformParam)) {
|
||||
return agentErrorResponse({
|
||||
code: "INVALID_PLATFORM",
|
||||
hint: "Use one of: all, react, native.",
|
||||
message: `Unsupported platform: ${platformParam}.`,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const limitParam = request.nextUrl.searchParams.get("limit");
|
||||
const numericLimit = limitParam === null ? null : Number(limitParam);
|
||||
|
||||
if (
|
||||
numericLimit !== null &&
|
||||
(!Number.isInteger(numericLimit) || numericLimit < 1 || numericLimit > 20)
|
||||
) {
|
||||
return agentErrorResponse({
|
||||
code: "INVALID_LIMIT",
|
||||
hint: "Use an integer from 1 through 20.",
|
||||
message: `Invalid result limit: ${limitParam}.`,
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const platform = parseAgentSearchPlatform(platformParam);
|
||||
const limit = parseAgentLimit(limitParam);
|
||||
const results = searchAgentDocs(query, platform, limit);
|
||||
|
||||
return jsonResponse({
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import {OPENAPI_HEADERS, absoluteUrl, getRequestOrigin} from "@/lib/agent-discovery";
|
||||
import {agentApiDocument} from "@/lib/agent-openapi";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = false;
|
||||
|
||||
export function GET(request: Request) {
|
||||
const origin = getRequestOrigin(request);
|
||||
|
||||
return new Response(JSON.stringify(agentApiDocument(origin), null, 2), {
|
||||
headers: {
|
||||
...OPENAPI_HEADERS,
|
||||
Link: `<${absoluteUrl(origin, "/.well-known/openapi/heroui-agent-api.json")}>; rel="alternate"; type="application/vnd.oai.openapi+json"`,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -51,6 +51,10 @@ function parseDate(value: string | undefined): Date | undefined {
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const generatedAt = new Date();
|
||||
const paths: LocalizedPaths = new Map();
|
||||
const trustEntries: SitemapEntry[] = ["/about", "/contact", "/privacy"].map((path) => ({
|
||||
lastModified: generatedAt,
|
||||
url: absoluteUrl(path),
|
||||
}));
|
||||
|
||||
for (const locale of LOCALES) {
|
||||
addPath(paths, "/", locale);
|
||||
@@ -77,5 +81,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
|
||||
// Keep one canonical entry per URL. Machine-readable `.mdx`/`llms` routes
|
||||
// and the unprefixed native-showcase fallback are intentionally excluded.
|
||||
return [...new Map(localizedEntries.map((entry) => [entry.url, entry])).values()];
|
||||
return [
|
||||
...new Map([...trustEntries, ...localizedEntries].map((entry) => [entry.url, entry])).values(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,17 @@ export function Footer({dict}: {dict: Dictionary["footer"]}) {
|
||||
<p className="text-sm">
|
||||
© {new Date().getFullYear()} NextUI Inc. {dict.allRightsReserved}
|
||||
</p>
|
||||
<nav aria-label="HeroUI company information" className="flex items-center gap-2 text-sm">
|
||||
<a className="hover:text-foreground" href="/about">
|
||||
{dict.about}
|
||||
</a>
|
||||
<a className="hover:text-foreground" href="/contact">
|
||||
{dict.contact}
|
||||
</a>
|
||||
<a className="hover:text-foreground" href="/privacy">
|
||||
{dict.privacy}
|
||||
</a>
|
||||
</nav>
|
||||
<SocialLinks />
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ export const LINKSET_HEADERS = {
|
||||
export const MARKDOWN_HEADERS = {
|
||||
...AGENT_DISCOVERY_CACHE_HEADERS,
|
||||
"Content-Type": "text/markdown; charset=utf-8",
|
||||
Vary: "Accept, Accept-Encoding",
|
||||
} as const;
|
||||
|
||||
export const OPENAPI_HEADERS = {
|
||||
@@ -70,8 +71,10 @@ export function getHomepageLinkHeader(): string {
|
||||
return [
|
||||
'</.well-known/api-catalog>; rel="api-catalog"; type="application/linkset+json"',
|
||||
'</.well-known/agent-skills/index.json>; rel="describedby"; type="application/json"',
|
||||
'</.well-known/mcp>; rel="service"; type="application/json"',
|
||||
'</.well-known/mcp/server-card.json>; rel="service-desc"; type="application/json"',
|
||||
'</.well-known/oauth-protected-resource>; rel="oauth-protected-resource"; type="application/json"',
|
||||
'</openapi.json>; rel="service-desc"; type="application/vnd.oai.openapi+json"',
|
||||
'</llms.txt>; rel="service-doc"; type="text/plain"',
|
||||
'</llms-full.txt>; rel="describedby"; type="text/plain"',
|
||||
].join(", ");
|
||||
@@ -87,6 +90,28 @@ export function jsonResponse(data: unknown, init: ResponseInit = {}): Response {
|
||||
});
|
||||
}
|
||||
|
||||
export function agentErrorResponse({
|
||||
code,
|
||||
hint,
|
||||
message,
|
||||
status,
|
||||
}: {
|
||||
code: string;
|
||||
hint: string;
|
||||
message: string;
|
||||
status: number;
|
||||
}): Response {
|
||||
return jsonResponse(
|
||||
{
|
||||
code,
|
||||
error: true,
|
||||
hint,
|
||||
message,
|
||||
},
|
||||
{status},
|
||||
);
|
||||
}
|
||||
|
||||
export function getAgentServiceBaseUrl(origin: string): string {
|
||||
return absoluteUrl(origin, "/api/agent");
|
||||
}
|
||||
|
||||
@@ -80,7 +80,10 @@ HeroUI is the modern UI library for web and mobile, built to help teams move fas
|
||||
- [Full LLM documentation](${baseUrl}/llms-full.txt)
|
||||
- [React LLM documentation](${baseUrl}/react/llms-full.txt)
|
||||
- [Native LLM documentation](${baseUrl}/native/llms-full.txt)
|
||||
- [HeroUI OpenAPI specification](${baseUrl}/openapi.json)
|
||||
- [HeroUI Docs Agent API](${baseUrl}/api/agent)
|
||||
- [API catalog](${baseUrl}/.well-known/api-catalog)
|
||||
- [MCP discovery handshake](${baseUrl}/.well-known/mcp)
|
||||
- [MCP server card](${baseUrl}/.well-known/mcp/server-card.json)
|
||||
- [Agent skills index](${baseUrl}/.well-known/agent-skills/index.json)
|
||||
|
||||
@@ -90,6 +93,13 @@ HeroUI is the modern UI library for web and mobile, built to help teams move fas
|
||||
- [HeroUI Native MCP server](${baseUrl}/docs/native/getting-started/mcp-server)
|
||||
- [HeroUI React Agent Skills](${baseUrl}/docs/react/getting-started/agent-skills)
|
||||
- [HeroUI Native Agent Skills](${baseUrl}/docs/native/getting-started/agent-skills)
|
||||
- [HeroUI CLI](${baseUrl}/docs/react/getting-started/cli)
|
||||
|
||||
## About HeroUI
|
||||
|
||||
- [About HeroUI](${baseUrl}/about)
|
||||
- [Contact HeroUI](${baseUrl}/contact)
|
||||
- [HeroUI privacy information](${baseUrl}/privacy)
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
MCP_PACKAGE_VERSION,
|
||||
NATIVE_MCP_API_URL,
|
||||
REACT_MCP_API_URL,
|
||||
absoluteUrl,
|
||||
} from "@/lib/agent-discovery";
|
||||
|
||||
export function getMcpServerCard(origin: string) {
|
||||
return {
|
||||
$schema: "https://modelcontextprotocol.io/schemas/server-card/draft.json",
|
||||
capabilities: {
|
||||
prompts: false,
|
||||
resources: false,
|
||||
tools: true,
|
||||
},
|
||||
description:
|
||||
"HeroUI MCP servers expose read-only HeroUI React and HeroUI Native documentation, component metadata, source references, styles, and theme variables to AI coding agents.",
|
||||
endpoint: absoluteUrl(origin, "/.well-known/mcp/server-card.json"),
|
||||
links: {
|
||||
docs: [
|
||||
absoluteUrl(origin, "/docs/react/getting-started/mcp-server"),
|
||||
absoluteUrl(origin, "/docs/native/getting-started/mcp-server"),
|
||||
],
|
||||
npm: [
|
||||
"https://www.npmjs.com/package/@heroui/react-mcp",
|
||||
"https://www.npmjs.com/package/@heroui/native-mcp",
|
||||
],
|
||||
serverCard: absoluteUrl(origin, "/.well-known/mcp/server-card.json"),
|
||||
source: "https://github.com/heroui-inc/heroui-mcp",
|
||||
},
|
||||
notes:
|
||||
"HeroUI's supported MCP transport is stdio through the published npm packages. This well-known document is a discovery handshake for those existing servers, not a Streamable HTTP MCP endpoint.",
|
||||
serverInfo: {
|
||||
name: "HeroUI MCP",
|
||||
version: MCP_PACKAGE_VERSION,
|
||||
},
|
||||
tools: [
|
||||
{
|
||||
description: "List all available HeroUI v3 React components.",
|
||||
name: "list_components",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get complete React component documentation.",
|
||||
name: "get_component_docs",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get React component TypeScript source code.",
|
||||
name: "get_component_source_code",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get React component CSS source styles.",
|
||||
name: "get_component_source_styles",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get HeroUI React theme variables.",
|
||||
name: "get_theme_variables",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "Browse full HeroUI React documentation.",
|
||||
name: "get_docs",
|
||||
package: "@heroui/react-mcp",
|
||||
},
|
||||
{
|
||||
description: "List all available HeroUI Native components.",
|
||||
name: "list_components",
|
||||
package: "@heroui/native-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get complete Native component documentation.",
|
||||
name: "get_component_docs",
|
||||
package: "@heroui/native-mcp",
|
||||
},
|
||||
{
|
||||
description: "Get HeroUI Native theme variables.",
|
||||
name: "get_theme_variables",
|
||||
package: "@heroui/native-mcp",
|
||||
},
|
||||
{
|
||||
description: "Browse full HeroUI Native documentation.",
|
||||
name: "get_docs",
|
||||
package: "@heroui/native-mcp",
|
||||
},
|
||||
],
|
||||
transports: [
|
||||
{
|
||||
args: ["-y", "@heroui/react-mcp@latest"],
|
||||
command: "npx",
|
||||
dataApi: REACT_MCP_API_URL,
|
||||
package: "@heroui/react-mcp",
|
||||
type: "stdio",
|
||||
},
|
||||
{
|
||||
args: ["-y", "@heroui/native-mcp@latest"],
|
||||
command: "npx",
|
||||
dataApi: NATIVE_MCP_API_URL,
|
||||
package: "@heroui/native-mcp",
|
||||
type: "stdio",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
import {
|
||||
AGENT_API_VERSION,
|
||||
NATIVE_MCP_API_URL,
|
||||
REACT_MCP_API_URL,
|
||||
getAgentServiceBaseUrl,
|
||||
} from "@/lib/agent-discovery";
|
||||
|
||||
export type OpenAPIDocument = Record<string, unknown>;
|
||||
|
||||
function baseDocument(
|
||||
title: string,
|
||||
version: string,
|
||||
serverUrl: string,
|
||||
description: string,
|
||||
): OpenAPIDocument {
|
||||
return {
|
||||
info: {
|
||||
description,
|
||||
title,
|
||||
version,
|
||||
},
|
||||
openapi: "3.1.0",
|
||||
security: [],
|
||||
servers: [{url: serverUrl}],
|
||||
};
|
||||
}
|
||||
|
||||
const errorResponse = {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {$ref: "#/components/schemas/ErrorResponse"},
|
||||
},
|
||||
},
|
||||
description: "A structured error with a stable code and recovery hint.",
|
||||
};
|
||||
|
||||
export function agentApiDocument(origin: string): OpenAPIDocument {
|
||||
return {
|
||||
...baseDocument(
|
||||
"HeroUI Docs Agent API",
|
||||
AGENT_API_VERSION,
|
||||
getAgentServiceBaseUrl(origin),
|
||||
"Public, read-only endpoints for searching HeroUI documentation and retrieving markdown page content. No authentication or OAuth scopes are required.",
|
||||
),
|
||||
components: {
|
||||
schemas: {
|
||||
AgentPage: {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
description: {type: "string"},
|
||||
markdown: {type: "string"},
|
||||
title: {type: "string"},
|
||||
url: {type: "string"},
|
||||
},
|
||||
required: ["description", "markdown", "title", "url"],
|
||||
type: "object",
|
||||
},
|
||||
ErrorResponse: {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
code: {type: "string"},
|
||||
error: {const: true, type: "boolean"},
|
||||
hint: {type: "string"},
|
||||
message: {type: "string"},
|
||||
},
|
||||
required: ["error", "code", "message", "hint"],
|
||||
type: "object",
|
||||
},
|
||||
HealthResponse: {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
service: {const: "heroui-docs-agent-api", type: "string"},
|
||||
status: {const: "ok", type: "string"},
|
||||
version: {type: "string"},
|
||||
},
|
||||
required: ["service", "status", "version"],
|
||||
type: "object",
|
||||
},
|
||||
SearchResponse: {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
count: {minimum: 0, type: "integer"},
|
||||
platform: {enum: ["all", "react", "native"], type: "string"},
|
||||
query: {type: "string"},
|
||||
results: {
|
||||
items: {$ref: "#/components/schemas/SearchResult"},
|
||||
type: "array",
|
||||
},
|
||||
},
|
||||
required: ["count", "platform", "query", "results"],
|
||||
type: "object",
|
||||
},
|
||||
SearchResult: {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
description: {type: "string"},
|
||||
platform: {enum: ["all", "react", "native"], type: "string"},
|
||||
title: {type: "string"},
|
||||
url: {type: "string"},
|
||||
},
|
||||
required: ["description", "platform", "title", "url"],
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
paths: {
|
||||
"/health": {
|
||||
get: {
|
||||
description:
|
||||
"Returns the current status and version of the public HeroUI Docs Agent API.",
|
||||
operationId: "getAgentApiHealth",
|
||||
responses: {
|
||||
"200": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {$ref: "#/components/schemas/HealthResponse"},
|
||||
},
|
||||
},
|
||||
description: "The agent API is available.",
|
||||
},
|
||||
},
|
||||
summary: "Check HeroUI agent API health",
|
||||
},
|
||||
},
|
||||
"/page": {
|
||||
get: {
|
||||
description:
|
||||
"Retrieves one public HeroUI documentation page, including its metadata and markdown body.",
|
||||
operationId: "getHeroUIDocPageMarkdown",
|
||||
parameters: [
|
||||
{
|
||||
description: "HeroUI docs page URL, for example /docs/react/components/button.",
|
||||
in: "query",
|
||||
name: "url",
|
||||
required: true,
|
||||
schema: {minLength: 1, type: "string"},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
"200": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {$ref: "#/components/schemas/AgentPage"},
|
||||
},
|
||||
},
|
||||
description: "Documentation page metadata and markdown.",
|
||||
},
|
||||
"400": errorResponse,
|
||||
"404": errorResponse,
|
||||
},
|
||||
summary: "Retrieve HeroUI documentation as markdown",
|
||||
},
|
||||
},
|
||||
"/search": {
|
||||
get: {
|
||||
description:
|
||||
"Searches public HeroUI documentation titles, descriptions, URLs, and slugs, optionally restricted to React or React Native.",
|
||||
operationId: "searchHeroUIDocs",
|
||||
parameters: [
|
||||
{
|
||||
description: "Case-insensitive text to find in HeroUI documentation.",
|
||||
in: "query",
|
||||
name: "q",
|
||||
required: true,
|
||||
schema: {minLength: 1, type: "string"},
|
||||
},
|
||||
{
|
||||
description: "Limit results to one HeroUI platform or search all documentation.",
|
||||
in: "query",
|
||||
name: "platform",
|
||||
schema: {default: "all", enum: ["all", "react", "native"], type: "string"},
|
||||
},
|
||||
{
|
||||
description: "Maximum number of matches to return.",
|
||||
in: "query",
|
||||
name: "limit",
|
||||
schema: {default: 10, maximum: 20, minimum: 1, type: "integer"},
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
"200": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {$ref: "#/components/schemas/SearchResponse"},
|
||||
},
|
||||
},
|
||||
description: "Matching HeroUI documentation pages.",
|
||||
},
|
||||
"400": errorResponse,
|
||||
},
|
||||
summary: "Search HeroUI documentation",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mcpApiDocument(kind: "react" | "native"): OpenAPIDocument {
|
||||
const isReact = kind === "react";
|
||||
const serverUrl = isReact ? REACT_MCP_API_URL : NATIVE_MCP_API_URL;
|
||||
const title = isReact ? "HeroUI React MCP Data API" : "HeroUI Native MCP Data API";
|
||||
const componentSourcePaths = isReact
|
||||
? {
|
||||
"/v1/components/source": {
|
||||
post: {
|
||||
description: "Returns TypeScript source for requested HeroUI React components.",
|
||||
operationId: "getComponentSource",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
properties: {components: {items: {type: "string"}, type: "array"}},
|
||||
required: ["components"],
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
responses: {"200": {description: "Component source code."}},
|
||||
summary: "Get component TypeScript source",
|
||||
},
|
||||
},
|
||||
"/v1/components/styles": {
|
||||
post: {
|
||||
description: "Returns CSS source for requested HeroUI React components.",
|
||||
operationId: "getComponentStyles",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
properties: {components: {items: {type: "string"}, type: "array"}},
|
||||
required: ["components"],
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
responses: {"200": {description: "Component CSS styles."}},
|
||||
summary: "Get component styles",
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
|
||||
return {
|
||||
...baseDocument(
|
||||
title,
|
||||
"1.1.0",
|
||||
serverUrl,
|
||||
`Public, read-only data API used by the ${isReact ? "@heroui/react-mcp" : "@heroui/native-mcp"} package and HeroUI agent skills.`,
|
||||
),
|
||||
paths: {
|
||||
"/health": {
|
||||
get: {
|
||||
description: "Returns the current availability of the HeroUI MCP data API.",
|
||||
operationId: "getHealth",
|
||||
responses: {"200": {description: "Health status."}},
|
||||
summary: "Check API health",
|
||||
},
|
||||
},
|
||||
"/v1/components": {
|
||||
get: {
|
||||
description: "Lists the component names available for this HeroUI platform.",
|
||||
operationId: "listComponents",
|
||||
responses: {"200": {description: "Available components."}},
|
||||
summary: "List available components",
|
||||
},
|
||||
},
|
||||
"/v1/components/docs": {
|
||||
post: {
|
||||
description: "Returns documentation for requested HeroUI components.",
|
||||
operationId: "getComponentDocs",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
properties: {components: {items: {type: "string"}, type: "array"}},
|
||||
required: ["components"],
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
responses: {"200": {description: "Component documentation."}},
|
||||
summary: "Get component documentation",
|
||||
},
|
||||
},
|
||||
...componentSourcePaths,
|
||||
"/v1/docs/{path}": {
|
||||
get: {
|
||||
description: "Returns a HeroUI documentation resource by its canonical path.",
|
||||
operationId: "getDocs",
|
||||
parameters: [
|
||||
{
|
||||
description: "Canonical documentation path.",
|
||||
in: "path",
|
||||
name: "path",
|
||||
required: true,
|
||||
schema: {type: "string"},
|
||||
},
|
||||
],
|
||||
responses: {"200": {description: "Documentation content."}},
|
||||
summary: "Get documentation by path",
|
||||
},
|
||||
},
|
||||
"/v1/themes/variables": {
|
||||
get: {
|
||||
description: "Returns the design tokens for a named HeroUI theme.",
|
||||
operationId: "getThemeVariables",
|
||||
parameters: [
|
||||
{
|
||||
description: "Theme identifier.",
|
||||
in: "query",
|
||||
name: "theme",
|
||||
schema: {default: "default", type: "string"},
|
||||
},
|
||||
],
|
||||
responses: {"200": {description: "Theme variables."}},
|
||||
summary: "Get theme variables",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const OPENAPI_BUILDERS: Record<string, (origin: string) => OpenAPIDocument> = {
|
||||
"heroui-agent-api.json": agentApiDocument,
|
||||
"heroui-native-mcp-api.json": () => mcpApiDocument("native"),
|
||||
"heroui-react-mcp-api.json": () => mcpApiDocument("react"),
|
||||
};
|
||||
@@ -67,7 +67,10 @@
|
||||
"description": "探索展示 HeroUI 组件在真实场景中应用的互动示例。"
|
||||
},
|
||||
"footer": {
|
||||
"allRightsReserved": "保留所有权利。"
|
||||
"about": "关于",
|
||||
"allRightsReserved": "保留所有权利。",
|
||||
"contact": "联系",
|
||||
"privacy": "隐私"
|
||||
},
|
||||
"newsletter": {
|
||||
"label": "Hero 新闻通讯",
|
||||
|
||||
@@ -67,7 +67,10 @@
|
||||
"description": "Explore interactive examples showcasing HeroUI components in real-world scenarios."
|
||||
},
|
||||
"footer": {
|
||||
"allRightsReserved": "All rights reserved."
|
||||
"about": "About",
|
||||
"allRightsReserved": "All rights reserved.",
|
||||
"contact": "Contact",
|
||||
"privacy": "Privacy"
|
||||
},
|
||||
"newsletter": {
|
||||
"label": "Hero Newsletter",
|
||||
|
||||
@@ -4,6 +4,11 @@ export function getOrganizationJsonLd() {
|
||||
return {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
contactPoint: {
|
||||
"@type": "ContactPoint",
|
||||
contactType: "customer support",
|
||||
email: "sales@heroui.com",
|
||||
},
|
||||
logo: "https://heroui.com/icons/favicon.svg",
|
||||
name: "HeroUI",
|
||||
sameAs: [siteConfig.links.github, siteConfig.links.twitter, siteConfig.links.discord],
|
||||
|
||||
@@ -79,6 +79,19 @@ export function generateIndexHeader(): string[] {
|
||||
const nativeUrl = "/native/llms.txt";
|
||||
const reactAbsoluteUrl = formatAbsoluteUrl(reactUrl);
|
||||
const nativeAbsoluteUrl = formatAbsoluteUrl(nativeUrl);
|
||||
const agentResources = [
|
||||
["HeroUI OpenAPI specification", "/openapi.json"],
|
||||
["HeroUI Docs Agent API", "/api/agent"],
|
||||
["HeroUI Agent API health", "/api/agent/health"],
|
||||
["HeroUI Agent API search", "/api/agent/search"],
|
||||
["HeroUI Agent API page retrieval", "/api/agent/page"],
|
||||
["HeroUI API catalog", "/.well-known/api-catalog"],
|
||||
["HeroUI MCP discovery handshake", "/.well-known/mcp"],
|
||||
["HeroUI MCP server card", "/.well-known/mcp/server-card.json"],
|
||||
["HeroUI React MCP documentation", "/docs/react/getting-started/mcp-server"],
|
||||
["HeroUI Native MCP documentation", "/docs/native/getting-started/mcp-server"],
|
||||
["HeroUI CLI documentation", "/docs/react/getting-started/cli"],
|
||||
] as const;
|
||||
|
||||
return [
|
||||
"# HeroUI v3 Documentation",
|
||||
@@ -107,6 +120,18 @@ export function generateIndexHeader(): string[] {
|
||||
`- **React (Web)**: [${reactUrl}](${reactAbsoluteUrl}) - React component library for web applications`,
|
||||
`- **React Native**: [${nativeUrl}](${nativeAbsoluteUrl}) - React Native component library for mobile applications`,
|
||||
"",
|
||||
"## HeroUI Agent and Developer Resources",
|
||||
"",
|
||||
"HeroUI v3 web and mobile libraries are free, open-source software. The public docs agent API and existing MCP packages are read-only and require no account or paid plan.",
|
||||
"",
|
||||
...agentResources.map(([title, path]) => `- [${title}](${formatAbsoluteUrl(path)})`),
|
||||
"",
|
||||
"## HeroUI Trust Pages",
|
||||
"",
|
||||
`- [About HeroUI](${formatAbsoluteUrl("/about")})`,
|
||||
`- [Contact HeroUI](${formatAbsoluteUrl("/contact")})`,
|
||||
`- [HeroUI Privacy](${formatAbsoluteUrl("/privacy")})`,
|
||||
"",
|
||||
"## Documentation Index",
|
||||
"",
|
||||
];
|
||||
|
||||
@@ -31,6 +31,7 @@ const MARKDOWN_EXCLUDED_PATHS = new Set([
|
||||
const PUBLIC_FILE_PATTERN = /\.[a-z0-9]+$/i;
|
||||
const UNRESOLVED_TEMPLATE_PATTERN = /(?:\{[^/{}]+\}|%7B[^/]+%7D)/i;
|
||||
const PERMANENT_LOCALIZED_PREFIXES = ["/blog", "/docs", "/showcase", "/themes"];
|
||||
const TRUST_PATHS = new Set(["/about", "/contact", "/privacy"]);
|
||||
|
||||
// Routes that live outside `app/[lang]` and must never receive a locale prefix.
|
||||
const LOCALE_REDIRECT_EXCLUDED_PREFIXES = [
|
||||
@@ -136,8 +137,8 @@ export function proxy(request: NextRequest) {
|
||||
if (!getLocaleFromPath(pathname) && !shouldSkipLocaleRedirect(pathname)) {
|
||||
const url = request.nextUrl.clone();
|
||||
|
||||
if (pathname === "/") {
|
||||
url.pathname = `/${DEFAULT_LOCALE}`;
|
||||
if (pathname === "/" || TRUST_PATHS.has(pathname)) {
|
||||
url.pathname = `/${DEFAULT_LOCALE}${pathname === "/" ? "" : pathname}`;
|
||||
|
||||
return addHomepageDiscoveryHeaders(NextResponse.rewrite(url), pathname);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import type {ReactElement, ReactNode} from "react";
|
||||
|
||||
import {NextRequest} from "next/server";
|
||||
import {isValidElement} from "react";
|
||||
import {describe, expect, it, vi} from "vitest";
|
||||
|
||||
import AboutPage from "@/app/[lang]/(home)/about/page";
|
||||
import ContactPage from "@/app/[lang]/(home)/contact/page";
|
||||
import HomePage from "@/app/[lang]/(home)/page";
|
||||
import PrivacyPage from "@/app/[lang]/(home)/privacy/page";
|
||||
import {GET as getMarkdown} from "@/app/agent-markdown/route";
|
||||
import {GET as getUnknownAgentEndpoint} from "@/app/api/agent/[...path]/route";
|
||||
import {GET as getAgentPage} from "@/app/api/agent/page/route";
|
||||
import {GET as searchAgentDocs} from "@/app/api/agent/search/route";
|
||||
import {GET as getOpenApi} from "@/app/openapi.json/route";
|
||||
import {getOrganizationJsonLd} from "@/lib/json-ld";
|
||||
import {generateIndexHeader} from "@/lib/llms-utils";
|
||||
|
||||
import {GET as getMcpHandshake} from "@/app/.well-known/mcp/route";
|
||||
|
||||
vi.mock("@/lib/get-llm-text", () => ({
|
||||
getLLMText: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/source", () => ({
|
||||
source: {
|
||||
getPage: vi.fn(),
|
||||
getPages: vi.fn(() => []),
|
||||
},
|
||||
}));
|
||||
|
||||
function extractText(node: ReactNode): string {
|
||||
if (typeof node === "string" || typeof node === "number") return String(node);
|
||||
if (Array.isArray(node)) return node.map(extractText).join(" ");
|
||||
if (!isValidElement(node)) return "";
|
||||
|
||||
const element = node as ReactElement<{
|
||||
children?: ReactNode;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}>;
|
||||
|
||||
return [
|
||||
element.props.title ?? "",
|
||||
element.props.description ?? "",
|
||||
extractText(element.props.children),
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
function collectHeadingLevels(node: ReactNode): number[] {
|
||||
if (Array.isArray(node)) return node.flatMap(collectHeadingLevels);
|
||||
if (!isValidElement(node)) return [];
|
||||
|
||||
const element = node as ReactElement<{children?: ReactNode}>;
|
||||
const level =
|
||||
typeof element.type === "string" && /^h[1-6]$/.test(element.type)
|
||||
? Number(element.type.slice(1))
|
||||
: null;
|
||||
|
||||
return [...(level === null ? [] : [level]), ...collectHeadingLevels(element.props.children)];
|
||||
}
|
||||
|
||||
describe("HeroUI agent readiness", () => {
|
||||
it("publishes a typed OpenAPI alias with unique documented operations", async () => {
|
||||
const response = getOpenApi(new Request("https://heroui.com/openapi.json"));
|
||||
const document = (await response.json()) as {
|
||||
info: {title: string};
|
||||
openapi: string;
|
||||
paths: Record<string, Record<string, {description?: string; operationId?: string}>>;
|
||||
};
|
||||
const operations = Object.values(document.paths).flatMap((path) => Object.values(path));
|
||||
const operationIds = operations.map((operation) => operation.operationId);
|
||||
|
||||
expect(response.headers.get("content-type")).toContain("application/vnd.oai.openapi+json");
|
||||
expect(document.info.title).toBe("HeroUI Docs Agent API");
|
||||
expect(document.openapi).toBe("3.1.0");
|
||||
expect(operations.every((operation) => Boolean(operation.description))).toBe(true);
|
||||
expect(new Set(operationIds).size).toBe(operationIds.length);
|
||||
});
|
||||
|
||||
it("returns structured JSON errors from agent API routes", async () => {
|
||||
const responses = [
|
||||
await searchAgentDocs(new NextRequest("https://heroui.com/api/agent/search")),
|
||||
await getAgentPage(new NextRequest("https://heroui.com/api/agent/page")),
|
||||
getUnknownAgentEndpoint(),
|
||||
];
|
||||
|
||||
for (const response of responses) {
|
||||
const body = (await response.json()) as Record<string, unknown>;
|
||||
|
||||
expect(response.status).toBeGreaterThanOrEqual(400);
|
||||
expect(response.headers.get("content-type")).toContain("application/json");
|
||||
expect(body).toEqual({
|
||||
code: expect.any(String),
|
||||
error: true,
|
||||
hint: expect.any(String),
|
||||
message: expect.any(String),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("varies negotiated markdown by Accept and Accept-Encoding", async () => {
|
||||
const request = new NextRequest("https://heroui.com/agent-markdown?path=/", {
|
||||
headers: {
|
||||
accept: "text/markdown",
|
||||
"x-heroui-markdown-path": "/",
|
||||
},
|
||||
});
|
||||
const response = await getMarkdown(request);
|
||||
|
||||
expect(response.headers.get("content-type")).toContain("text/markdown");
|
||||
expect(response.headers.get("vary")).toBe("Accept, Accept-Encoding");
|
||||
expect(await response.text()).toContain("# HeroUI");
|
||||
});
|
||||
|
||||
it("serves the existing HeroUI MCP packages from the well-known handshake", async () => {
|
||||
const response = getMcpHandshake(new Request("https://heroui.com/.well-known/mcp"));
|
||||
const card = (await response.json()) as {
|
||||
endpoint: string;
|
||||
transports: {package: string; type: string}[];
|
||||
};
|
||||
|
||||
expect(card.endpoint).toBe("https://heroui.com/.well-known/mcp/server-card.json");
|
||||
expect(card.transports).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({package: "@heroui/react-mcp", type: "stdio"}),
|
||||
expect.objectContaining({package: "@heroui/native-mcp", type: "stdio"}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("lists predictable HeroUI developer resources in llms.txt", () => {
|
||||
const header = generateIndexHeader().join("\n");
|
||||
|
||||
expect(header).toContain("HeroUI OpenAPI specification");
|
||||
expect(header).toContain("/openapi.json");
|
||||
expect(header).toContain("/.well-known/mcp");
|
||||
expect(header).toContain("/docs/react/getting-started/cli");
|
||||
});
|
||||
|
||||
it("adds a verified HeroUI support contact without fabricating an address", () => {
|
||||
const organization = getOrganizationJsonLd();
|
||||
|
||||
expect(organization.contactPoint).toEqual({
|
||||
"@type": "ContactPoint",
|
||||
contactType: "customer support",
|
||||
email: "sales@heroui.com",
|
||||
});
|
||||
expect(organization).not.toHaveProperty("address");
|
||||
});
|
||||
|
||||
it("ships substantial trust-page copy", () => {
|
||||
for (const page of [AboutPage(), ContactPage(), PrivacyPage()]) {
|
||||
expect(extractText(page).replace(/\s+/g, " ").trim().length).toBeGreaterThanOrEqual(500);
|
||||
expect(collectHeadingLevels(page)).toEqual(expect.arrayContaining([2]));
|
||||
}
|
||||
|
||||
const contactText = extractText(ContactPage());
|
||||
const privacyText = extractText(PrivacyPage());
|
||||
|
||||
expect(contactText).toContain("sales@heroui.com");
|
||||
expect(contactText).toContain("junior@heroui.com");
|
||||
expect(contactText).not.toContain("jrgarciadev@gmail.com");
|
||||
expect(privacyText).toContain("sales@heroui.com");
|
||||
expect(privacyText).toContain("junior@heroui.com");
|
||||
expect(privacyText).not.toContain("jrgarciadev@gmail.com");
|
||||
expect(privacyText).toContain("Vercel Analytics");
|
||||
expect(privacyText).toContain("PostHog");
|
||||
expect(privacyText).toContain("IP address");
|
||||
});
|
||||
|
||||
it("server-renders meaningful homepage text with a hierarchical outline", async () => {
|
||||
const page = await HomePage({params: Promise.resolve({lang: "en"})});
|
||||
const text = extractText(page).replace(/\s+/g, " ").trim();
|
||||
const headings = collectHeadingLevels(page);
|
||||
|
||||
expect(text.length).toBeGreaterThanOrEqual(500);
|
||||
expect(headings).toEqual(expect.arrayContaining([1, 2, 3]));
|
||||
expect(headings.filter((level) => level === 1)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export {};
|
||||
@@ -0,0 +1,38 @@
|
||||
import {fileURLToPath} from "node:url";
|
||||
|
||||
import {defineConfig} from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
esbuild: {
|
||||
jsx: "automatic",
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: "@/.source",
|
||||
replacement: fileURLToPath(new URL("./.source/server.ts", import.meta.url)),
|
||||
},
|
||||
{
|
||||
find: "@",
|
||||
replacement: fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
{
|
||||
find: "server-only",
|
||||
replacement: fileURLToPath(new URL("./tests/server-only.ts", import.meta.url)),
|
||||
},
|
||||
{
|
||||
find: "~env",
|
||||
replacement: fileURLToPath(new URL("./env.ts", import.meta.url)),
|
||||
},
|
||||
],
|
||||
},
|
||||
test: {
|
||||
env: {
|
||||
NEXT_PUBLIC_APP_ENV: "development",
|
||||
NEXT_PUBLIC_CDN_URL: "http://localhost:3000",
|
||||
NODE_ENV: "development",
|
||||
},
|
||||
environment: "node",
|
||||
include: ["tests/**/*.test.ts"],
|
||||
},
|
||||
});
|
||||
Generated
+156
-10
@@ -349,6 +349,9 @@ importers:
|
||||
tar:
|
||||
specifier: 7.5.21
|
||||
version: 7.5.21
|
||||
vitest:
|
||||
specifier: 4.1.11
|
||||
version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@7.3.5(@types/node@25.0.3)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0))
|
||||
|
||||
packages/react:
|
||||
dependencies:
|
||||
@@ -2893,8 +2896,8 @@ packages:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||
|
||||
'@react-grab/cli@0.1.50':
|
||||
resolution: {integrity: sha512-Px/Hwhhyk2PubCA4ZaRFsfvwxhbxXsetJyvqC6aFFi8WhJhA+oVC33aTzuAeWmM3fhb4/8ce8YsHXI1d6ChcKg==}
|
||||
'@react-grab/cli@0.2.0':
|
||||
resolution: {integrity: sha512-LVfA+j5cFT0Szd958WRD8W/B8ucCtz4ViiveDKWLqAwBkZFW9VRW1ZvwOUhp/M3SmVPtjDaRkW12GvAuTArP+Q==}
|
||||
hasBin: true
|
||||
|
||||
'@react-spectrum/color@3.2.1':
|
||||
@@ -4242,6 +4245,9 @@ packages:
|
||||
'@vitest/expect@4.1.10':
|
||||
resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
|
||||
|
||||
'@vitest/expect@4.1.11':
|
||||
resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==}
|
||||
|
||||
'@vitest/mocker@4.1.10':
|
||||
resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==}
|
||||
peerDependencies:
|
||||
@@ -4253,30 +4259,56 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/mocker@4.1.11':
|
||||
resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
msw:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
|
||||
|
||||
'@vitest/pretty-format@4.1.10':
|
||||
resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==}
|
||||
|
||||
'@vitest/pretty-format@4.1.11':
|
||||
resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==}
|
||||
|
||||
'@vitest/runner@4.1.10':
|
||||
resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==}
|
||||
|
||||
'@vitest/runner@4.1.11':
|
||||
resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==}
|
||||
|
||||
'@vitest/snapshot@4.1.10':
|
||||
resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==}
|
||||
|
||||
'@vitest/snapshot@4.1.11':
|
||||
resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==}
|
||||
|
||||
'@vitest/spy@3.2.4':
|
||||
resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
|
||||
|
||||
'@vitest/spy@4.1.10':
|
||||
resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==}
|
||||
|
||||
'@vitest/spy@4.1.11':
|
||||
resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==}
|
||||
|
||||
'@vitest/utils@3.2.4':
|
||||
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
|
||||
|
||||
'@vitest/utils@4.1.10':
|
||||
resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==}
|
||||
|
||||
'@vitest/utils@4.1.11':
|
||||
resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@@ -5298,6 +5330,7 @@ packages:
|
||||
eslint@9.39.2:
|
||||
resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
jiti: '*'
|
||||
@@ -7384,8 +7417,8 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^19.2.6
|
||||
|
||||
react-grab@0.1.50:
|
||||
resolution: {integrity: sha512-zRkHKq/8a1msCpEOp8BDROeQZT50m0OH2XPrP6jk5op+JAHrlsm3pj7eAQMOsct87EZDeGNnu4r+sGsJJzyw1Q==}
|
||||
react-grab@0.2.0:
|
||||
resolution: {integrity: sha512-ohhsfXD4qN0j6cMQd56aaVJBPDF3kUdviF/Od1Eak/rEIXQ3svQ4gjkwTfuTZwTBnxHRL16p9JqdlVQO5nUHqA==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
react: '>=17.0.0'
|
||||
@@ -8403,6 +8436,47 @@ packages:
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
vitest@4.1.11:
|
||||
resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==}
|
||||
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@opentelemetry/api': ^1.9.0
|
||||
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
|
||||
'@vitest/browser-playwright': 4.1.11
|
||||
'@vitest/browser-preview': 4.1.11
|
||||
'@vitest/browser-webdriverio': 4.1.11
|
||||
'@vitest/coverage-istanbul': 4.1.11
|
||||
'@vitest/coverage-v8': 4.1.11
|
||||
'@vitest/ui': 4.1.11
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
'@edge-runtime/vm':
|
||||
optional: true
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitest/browser-playwright':
|
||||
optional: true
|
||||
'@vitest/browser-preview':
|
||||
optional: true
|
||||
'@vitest/browser-webdriverio':
|
||||
optional: true
|
||||
'@vitest/coverage-istanbul':
|
||||
optional: true
|
||||
'@vitest/coverage-v8':
|
||||
optional: true
|
||||
'@vitest/ui':
|
||||
optional: true
|
||||
happy-dom:
|
||||
optional: true
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
vscode-jsonrpc@8.2.0:
|
||||
resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -11008,7 +11082,7 @@ snapshots:
|
||||
react-dom: 19.2.6(react@19.2.6)
|
||||
react-stately: 3.49.0(react@19.2.6)
|
||||
|
||||
'@react-grab/cli@0.1.50':
|
||||
'@react-grab/cli@0.2.0':
|
||||
dependencies:
|
||||
agent-install: 0.0.6
|
||||
commander: 14.0.3
|
||||
@@ -12255,9 +12329,9 @@ snapshots:
|
||||
obug: 2.1.4
|
||||
std-env: 4.2.0
|
||||
tinyrainbow: 3.1.1
|
||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@7.3.5(@types/node@25.0.3)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0))
|
||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@7.3.5(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0))
|
||||
optionalDependencies:
|
||||
'@vitest/browser': 4.1.10(vite@7.3.5(@types/node@25.0.3)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10)
|
||||
'@vitest/browser': 4.1.10(vite@7.3.5(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10)
|
||||
|
||||
'@vitest/expect@3.2.4':
|
||||
dependencies:
|
||||
@@ -12276,6 +12350,15 @@ snapshots:
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.1
|
||||
|
||||
'@vitest/expect@4.1.11':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/spy': 4.1.11
|
||||
'@vitest/utils': 4.1.11
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.1
|
||||
|
||||
'@vitest/mocker@4.1.10(vite@7.3.5(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.10
|
||||
@@ -12292,6 +12375,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
vite: 7.3.5(@types/node@25.0.3)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0)
|
||||
|
||||
'@vitest/mocker@4.1.11(vite@7.3.5(@types/node@25.0.3)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.11
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 7.3.5(@types/node@25.0.3)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0)
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
dependencies:
|
||||
tinyrainbow: 2.0.0
|
||||
@@ -12300,11 +12391,20 @@ snapshots:
|
||||
dependencies:
|
||||
tinyrainbow: 3.1.1
|
||||
|
||||
'@vitest/pretty-format@4.1.11':
|
||||
dependencies:
|
||||
tinyrainbow: 3.1.1
|
||||
|
||||
'@vitest/runner@4.1.10':
|
||||
dependencies:
|
||||
'@vitest/utils': 4.1.10
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/runner@4.1.11':
|
||||
dependencies:
|
||||
'@vitest/utils': 4.1.11
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@4.1.10':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.10
|
||||
@@ -12312,12 +12412,21 @@ snapshots:
|
||||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@4.1.11':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.11
|
||||
'@vitest/utils': 4.1.11
|
||||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/spy@3.2.4':
|
||||
dependencies:
|
||||
tinyspy: 4.0.4
|
||||
|
||||
'@vitest/spy@4.1.10': {}
|
||||
|
||||
'@vitest/spy@4.1.11': {}
|
||||
|
||||
'@vitest/utils@3.2.4':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
@@ -12330,6 +12439,12 @@ snapshots:
|
||||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.1.1
|
||||
|
||||
'@vitest/utils@4.1.11':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.11
|
||||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.1.1
|
||||
|
||||
acorn-jsx@5.3.2(acorn@7.4.1):
|
||||
dependencies:
|
||||
acorn: 7.4.1
|
||||
@@ -15987,9 +16102,9 @@ snapshots:
|
||||
react: 19.2.6
|
||||
scheduler: 0.27.0
|
||||
|
||||
react-grab@0.1.50(react@19.2.6):
|
||||
react-grab@0.2.0(react@19.2.6):
|
||||
dependencies:
|
||||
'@react-grab/cli': 0.1.50
|
||||
'@react-grab/cli': 0.2.0
|
||||
bippy: 0.6.1(react@19.2.6)
|
||||
optionalDependencies:
|
||||
react: 19.2.6
|
||||
@@ -16035,7 +16150,7 @@ snapshots:
|
||||
react: 19.2.6
|
||||
react-doctor: 0.9.12(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(eslint@9.39.2(jiti@2.7.0))
|
||||
react-dom: 19.2.6(react@19.2.6)
|
||||
react-grab: 0.1.50(react@19.2.6)
|
||||
react-grab: 0.2.0(react@19.2.6)
|
||||
optionalDependencies:
|
||||
esbuild: 0.27.7
|
||||
unplugin: 3.3.0(esbuild@0.27.7)(rollup@4.62.4)(vite@7.3.5(@types/node@25.0.3)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0))
|
||||
@@ -17315,6 +17430,37 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@25.0.3)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@7.3.5(@types/node@25.0.3)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0)):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.11
|
||||
'@vitest/mocker': 4.1.11(vite@7.3.5(@types/node@25.0.3)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0))
|
||||
'@vitest/pretty-format': 4.1.11
|
||||
'@vitest/runner': 4.1.11
|
||||
'@vitest/snapshot': 4.1.11
|
||||
'@vitest/spy': 4.1.11
|
||||
'@vitest/utils': 4.1.11
|
||||
es-module-lexer: 2.3.1
|
||||
expect-type: 1.4.0
|
||||
magic-string: 0.30.21
|
||||
obug: 2.1.4
|
||||
pathe: 2.0.3
|
||||
picomatch: 4.0.5
|
||||
std-env: 4.2.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 1.3.0
|
||||
tinyglobby: 0.2.17
|
||||
tinyrainbow: 3.1.1
|
||||
vite: 7.3.5(@types/node@25.0.3)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.1
|
||||
'@types/node': 25.0.3
|
||||
'@vitest/browser-playwright': 4.1.10(playwright@1.62.1)(vite@7.3.5(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.33.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10)
|
||||
'@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
|
||||
jsdom: 30.0.1
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
vscode-jsonrpc@8.2.0: {}
|
||||
|
||||
vscode-languageserver-protocol@3.17.5:
|
||||
|
||||
Reference in New Issue
Block a user