@heyo-sh/create-heyo-docs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/deployments/cloudflare/astro/package.json +12 -0
- package/deployments/cloudflare/astro/platform.astro.ts +4 -0
- package/deployments/cloudflare/astro/wrangler.jsonc +6 -0
- package/deployments/cloudflare/next/open-next.config.ts +3 -0
- package/deployments/cloudflare/next/package.json +13 -0
- package/deployments/cloudflare/next/wrangler.jsonc +11 -0
- package/deployments/cloudflare/react-router/app/entry.server.tsx +48 -0
- package/deployments/cloudflare/react-router/package.json +21 -0
- package/deployments/cloudflare/react-router/platform.vite.ts +6 -0
- package/deployments/cloudflare/react-router/react-router.config.ts +20 -0
- package/deployments/cloudflare/react-router/workers/app.ts +12 -0
- package/deployments/cloudflare/react-router/wrangler.jsonc +10 -0
- package/deployments/vercel/astro/package.json +11 -0
- package/deployments/vercel/astro/platform.astro.ts +4 -0
- package/deployments/vercel/astro/vercel.json +4 -0
- package/deployments/vercel/next/package.json +8 -0
- package/deployments/vercel/next/vercel.json +3 -0
- package/deployments/vercel/react-router/package.json +12 -0
- package/deployments/vercel/react-router/platform.vite.ts +4 -0
- package/deployments/vercel/react-router/react-router.config.ts +22 -0
- package/deployments/vercel/react-router/vercel.json +4 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +328 -0
- package/dist/package-manager.d.ts +6 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/project-name.d.ts +3 -0
- package/dist/project-name.d.ts.map +1 -0
- package/dist/scaffold.d.ts +5 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +36 -0
- package/templates/astro/astro.config.ts +15 -0
- package/templates/astro/components.json +25 -0
- package/templates/astro/content/getting-started.mdx +16 -0
- package/templates/astro/content/index.mdx +22 -0
- package/templates/astro/content/mdx-components.mdx +44 -0
- package/templates/astro/heyo-docs.config.ts +24 -0
- package/templates/astro/package.json +34 -0
- package/templates/astro/platform.astro.ts +4 -0
- package/templates/astro/public/apple-touch-icon.png +0 -0
- package/templates/astro/public/favicon-96x96.png +0 -0
- package/templates/astro/public/favicon.ico +0 -0
- package/templates/astro/public/favicon.svg +1 -0
- package/templates/astro/public/logo.svg +12 -0
- package/templates/astro/public/site.webmanifest +21 -0
- package/templates/astro/public/web-app-manifest-192x192.png +0 -0
- package/templates/astro/public/web-app-manifest-512x512.png +0 -0
- package/templates/astro/src/components/docs-app.tsx +165 -0
- package/templates/astro/src/components/theme-provider.tsx +106 -0
- package/templates/astro/src/env.d.ts +2 -0
- package/templates/astro/src/heyo-docs-icons.tsx +79 -0
- package/templates/astro/src/layouts/docs-layout.astro +70 -0
- package/templates/astro/src/lib/docs.ts +31 -0
- package/templates/astro/src/lib/openapi-request.ts +160 -0
- package/templates/astro/src/lib/seo.ts +221 -0
- package/templates/astro/src/lib/theme.ts +74 -0
- package/templates/astro/src/pages/[...slug].astro +50 -0
- package/templates/astro/src/pages/[...slug].md.ts +28 -0
- package/templates/astro/src/pages/heyo-docs-internal/openapi-request.ts +8 -0
- package/templates/astro/src/pages/llms-full.txt.ts +13 -0
- package/templates/astro/src/pages/llms.txt.ts +13 -0
- package/templates/astro/src/pages/robots.txt.ts +19 -0
- package/templates/astro/src/pages/rss.xml.ts +13 -0
- package/templates/astro/src/pages/sitemap.xml.ts +19 -0
- package/templates/astro/src/styles/app.css +8 -0
- package/templates/astro/src/virtual.d.ts +19 -0
- package/templates/astro/tsconfig.json +12 -0
- package/templates/next/app/[[...slug]]/page.tsx +59 -0
- package/templates/next/app/app.css +8 -0
- package/templates/next/app/components/docs-app.tsx +139 -0
- package/templates/next/app/components/theme-provider.tsx +93 -0
- package/templates/next/app/error.tsx +8 -0
- package/templates/next/app/heyo-docs-icons.tsx +70 -0
- package/templates/next/app/heyo-docs-internal/markdown/[[...slug]]/route.ts +26 -0
- package/templates/next/app/heyo-docs-internal/openapi-request/route.ts +5 -0
- package/templates/next/app/layout.tsx +79 -0
- package/templates/next/app/lib/docs.ts +43 -0
- package/templates/next/app/lib/openapi-request.ts +147 -0
- package/templates/next/app/lib/seo.ts +189 -0
- package/templates/next/app/lib/theme.ts +61 -0
- package/templates/next/app/llms-full.txt/route.ts +10 -0
- package/templates/next/app/llms.txt/route.ts +10 -0
- package/templates/next/app/not-found.tsx +13 -0
- package/templates/next/app/robots.txt/route.ts +16 -0
- package/templates/next/app/rss.xml/route.ts +10 -0
- package/templates/next/app/sitemap.xml/route.ts +14 -0
- package/templates/next/components.json +20 -0
- package/templates/next/content/getting-started.mdx +16 -0
- package/templates/next/content/index.mdx +22 -0
- package/templates/next/content/mdx-components.mdx +41 -0
- package/templates/next/heyo-docs.config.ts +24 -0
- package/templates/next/mdx.d.ts +6 -0
- package/templates/next/next-env.d.ts +7 -0
- package/templates/next/next.config.ts +23 -0
- package/templates/next/package.json +40 -0
- package/templates/next/postcss.config.mjs +5 -0
- package/templates/next/public/apple-touch-icon.png +0 -0
- package/templates/next/public/favicon-96x96.png +0 -0
- package/templates/next/public/favicon.ico +0 -0
- package/templates/next/public/favicon.svg +1 -0
- package/templates/next/public/logo.svg +12 -0
- package/templates/next/public/site.webmanifest +21 -0
- package/templates/next/public/web-app-manifest-192x192.png +0 -0
- package/templates/next/public/web-app-manifest-512x512.png +0 -0
- package/templates/next/scripts/generate-heyo-docs.ts +50 -0
- package/templates/next/tsconfig.json +27 -0
- package/templates/react-router/app/app.css +8 -0
- package/templates/react-router/app/components/theme-provider.tsx +106 -0
- package/templates/react-router/app/heyo-docs-icons.tsx +71 -0
- package/templates/react-router/app/lib/theme.ts +74 -0
- package/templates/react-router/app/root.tsx +140 -0
- package/templates/react-router/app/routes/docs.tsx +179 -0
- package/templates/react-router/app/routes/home.tsx +1 -0
- package/templates/react-router/app/routes/llms-full.ts +13 -0
- package/templates/react-router/app/routes/llms.ts +13 -0
- package/templates/react-router/app/routes/markdown.ts +25 -0
- package/templates/react-router/app/routes/openapi-request.ts +159 -0
- package/templates/react-router/app/routes/page.tsx +1 -0
- package/templates/react-router/app/routes/robots.ts +19 -0
- package/templates/react-router/app/routes/rss.ts +13 -0
- package/templates/react-router/app/routes/sitemap.ts +18 -0
- package/templates/react-router/app/routes.ts +13 -0
- package/templates/react-router/app/virtual.d.ts +19 -0
- package/templates/react-router/components.json +25 -0
- package/templates/react-router/content/getting-started.mdx +16 -0
- package/templates/react-router/content/index.mdx +22 -0
- package/templates/react-router/content/mdx-components.mdx +293 -0
- package/templates/react-router/heyo-docs.config.ts +24 -0
- package/templates/react-router/package.json +36 -0
- package/templates/react-router/platform.vite.ts +2 -0
- package/templates/react-router/public/apple-touch-icon.png +0 -0
- package/templates/react-router/public/favicon-96x96.png +0 -0
- package/templates/react-router/public/favicon.ico +0 -0
- package/templates/react-router/public/favicon.svg +1 -0
- package/templates/react-router/public/logo.svg +12 -0
- package/templates/react-router/public/site.webmanifest +21 -0
- package/templates/react-router/public/web-app-manifest-192x192.png +0 -0
- package/templates/react-router/public/web-app-manifest-512x512.png +0 -0
- package/templates/react-router/react-router.config.ts +20 -0
- package/templates/react-router/tsconfig.json +20 -0
- package/templates/react-router/vite.config.ts +17 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Links,
|
|
4
|
+
Meta,
|
|
5
|
+
Outlet,
|
|
6
|
+
Scripts,
|
|
7
|
+
ScrollRestoration,
|
|
8
|
+
type LinksFunction,
|
|
9
|
+
type MetaFunction,
|
|
10
|
+
} from "react-router";
|
|
11
|
+
import { pathnameFromMarkdownPath } from "@heyo-sh/heyo-docs";
|
|
12
|
+
import type { Route } from "./+types/root";
|
|
13
|
+
|
|
14
|
+
import "./app.css";
|
|
15
|
+
import { ThemeProvider } from "./components/theme-provider";
|
|
16
|
+
import config from "../heyo-docs.config";
|
|
17
|
+
import { getThemeScript, THEME_STORAGE_KEY } from "./lib/theme";
|
|
18
|
+
|
|
19
|
+
export const meta: MetaFunction = () => {
|
|
20
|
+
const canonical = config.siteUrl ? `${config.siteUrl}/` : undefined;
|
|
21
|
+
|
|
22
|
+
return [
|
|
23
|
+
{ title: config.title },
|
|
24
|
+
{ name: "description", content: config.description },
|
|
25
|
+
{ name: "robots", content: "index, follow" },
|
|
26
|
+
{ name: "referrer", content: "strict-origin-when-cross-origin" },
|
|
27
|
+
{ property: "og:type", content: "website" },
|
|
28
|
+
{ property: "og:site_name", content: config.title },
|
|
29
|
+
{ property: "og:title", content: config.title },
|
|
30
|
+
{ property: "og:description", content: config.description },
|
|
31
|
+
{ name: "twitter:card", content: "summary" },
|
|
32
|
+
{ name: "twitter:title", content: config.title },
|
|
33
|
+
{ name: "twitter:description", content: config.description },
|
|
34
|
+
...(canonical
|
|
35
|
+
? [
|
|
36
|
+
{ property: "og:url", content: canonical },
|
|
37
|
+
{
|
|
38
|
+
tagName: "link" as const,
|
|
39
|
+
rel: "canonical",
|
|
40
|
+
href: canonical,
|
|
41
|
+
},
|
|
42
|
+
]
|
|
43
|
+
: []),
|
|
44
|
+
{
|
|
45
|
+
"script:ld+json": {
|
|
46
|
+
"@context": "https://schema.org",
|
|
47
|
+
"@type": "WebSite",
|
|
48
|
+
name: config.title,
|
|
49
|
+
description: config.description,
|
|
50
|
+
...(config.siteUrl ? { url: config.siteUrl } : {}),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const links: LinksFunction = () => [
|
|
57
|
+
{ rel: "icon", href: "/favicon.ico", sizes: "any" },
|
|
58
|
+
{ rel: "icon", href: "/favicon.svg", type: "image/svg+xml" },
|
|
59
|
+
{
|
|
60
|
+
rel: "icon",
|
|
61
|
+
href: "/favicon-96x96.png",
|
|
62
|
+
sizes: "96x96",
|
|
63
|
+
type: "image/png",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
rel: "apple-touch-icon",
|
|
67
|
+
href: "/apple-touch-icon.png",
|
|
68
|
+
sizes: "180x180",
|
|
69
|
+
},
|
|
70
|
+
{ rel: "manifest", href: "/site.webmanifest" },
|
|
71
|
+
...(config.groups.some((group) => group.type === "changelog")
|
|
72
|
+
? [
|
|
73
|
+
{
|
|
74
|
+
rel: "alternate",
|
|
75
|
+
href: "/rss.xml",
|
|
76
|
+
type: "application/rss+xml",
|
|
77
|
+
title: `${config.title} updates`,
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
: []),
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
export function Layout({ children }: { children: ReactNode }) {
|
|
84
|
+
return (
|
|
85
|
+
<html lang="en" suppressHydrationWarning>
|
|
86
|
+
<head>
|
|
87
|
+
<meta charSet="utf-8" />
|
|
88
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
89
|
+
<meta name="color-scheme" content="light dark" />
|
|
90
|
+
<script
|
|
91
|
+
dangerouslySetInnerHTML={{
|
|
92
|
+
__html: getThemeScript(THEME_STORAGE_KEY, config.mode),
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
<Meta />
|
|
96
|
+
<Links />
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<ThemeProvider
|
|
100
|
+
defaultTheme={config.mode}
|
|
101
|
+
storageKey={THEME_STORAGE_KEY}
|
|
102
|
+
>
|
|
103
|
+
{children}
|
|
104
|
+
<ScrollRestoration />
|
|
105
|
+
<Scripts />
|
|
106
|
+
</ThemeProvider>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default function App() {
|
|
113
|
+
return <Outlet />;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const MARKDOWN_RESOURCE_PREFIX = "/__heyo-docs/markdown";
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Follows the Fumadocs React Router pattern: Markdown needs a resource route,
|
|
120
|
+
* while this middleware keeps the public URL as `/<page>.md`.
|
|
121
|
+
*/
|
|
122
|
+
const markdownMiddleware: Route.MiddlewareFunction = async (
|
|
123
|
+
{ request },
|
|
124
|
+
next,
|
|
125
|
+
) => {
|
|
126
|
+
const url = new URL(request.url);
|
|
127
|
+
if (
|
|
128
|
+
!url.pathname.startsWith(`${MARKDOWN_RESOURCE_PREFIX}/`) &&
|
|
129
|
+
pathnameFromMarkdownPath(url.pathname) !== undefined
|
|
130
|
+
) {
|
|
131
|
+
const target = new URL(
|
|
132
|
+
`${MARKDOWN_RESOURCE_PREFIX}${url.pathname}${url.search}`,
|
|
133
|
+
url,
|
|
134
|
+
);
|
|
135
|
+
return Response.redirect(target, 307);
|
|
136
|
+
}
|
|
137
|
+
return next();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const middleware = [markdownMiddleware];
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDocsModel,
|
|
3
|
+
DocsApp,
|
|
4
|
+
findDocsPage,
|
|
5
|
+
findOpenApiEndpoint,
|
|
6
|
+
openApiEndpointDataPath,
|
|
7
|
+
openApiEndpointDetail,
|
|
8
|
+
type OpenApiEndpoint,
|
|
9
|
+
} from "@heyo-sh/heyo-docs";
|
|
10
|
+
import {
|
|
11
|
+
Link,
|
|
12
|
+
useLoaderData,
|
|
13
|
+
useParams,
|
|
14
|
+
type LoaderFunctionArgs,
|
|
15
|
+
type MetaFunction,
|
|
16
|
+
} from "react-router";
|
|
17
|
+
import { forwardRef, type ComponentPropsWithoutRef } from "react";
|
|
18
|
+
|
|
19
|
+
import config from "../../heyo-docs.config";
|
|
20
|
+
import { iconSet } from "../heyo-docs-icons";
|
|
21
|
+
import { useTheme } from "../components/theme-provider";
|
|
22
|
+
import { pages } from "virtual:heyo-docs-content";
|
|
23
|
+
import { openApiDocuments } from "virtual:heyo-docs-openapi";
|
|
24
|
+
import { openApiEndpoints } from "virtual:heyo-docs-openapi/index";
|
|
25
|
+
|
|
26
|
+
function pathnameFor(params: Record<string, string | undefined>): string {
|
|
27
|
+
const path = params["*"]?.replace(/^\/+|\/+$/g, "");
|
|
28
|
+
return path ? `/${path}` : "/";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const RouterLink = forwardRef<HTMLAnchorElement, ComponentPropsWithoutRef<"a">>(
|
|
32
|
+
function RouterLink({ href = "/", ...props }, ref) {
|
|
33
|
+
return <Link {...props} ref={ref} to={href} />;
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export async function loader({ params }: LoaderFunctionArgs) {
|
|
38
|
+
const pathname = pathnameFor(params);
|
|
39
|
+
const model = createDocsModel(config, pages, [], openApiEndpoints);
|
|
40
|
+
const page = findDocsPage(model.pages, pathname);
|
|
41
|
+
const endpoint = findOpenApiEndpoint(model.endpoints, pathname);
|
|
42
|
+
if (!page && !endpoint) throw new Response("Not Found", { status: 404 });
|
|
43
|
+
|
|
44
|
+
if (!endpoint) return {};
|
|
45
|
+
|
|
46
|
+
// Prerendering serialises this operation-specific data with its static route,
|
|
47
|
+
// so the browser's first paint already contains the complete API reference.
|
|
48
|
+
if (typeof window === "undefined") {
|
|
49
|
+
const serverModel = createDocsModel(config, pages, openApiDocuments);
|
|
50
|
+
const currentOpenApiEndpoint = findOpenApiEndpoint(
|
|
51
|
+
serverModel.endpoints,
|
|
52
|
+
pathname,
|
|
53
|
+
);
|
|
54
|
+
return {
|
|
55
|
+
currentOpenApiEndpoint: currentOpenApiEndpoint
|
|
56
|
+
? openApiEndpointDetail(currentOpenApiEndpoint)
|
|
57
|
+
: undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Client transitions wait for the static shard before committing the new
|
|
62
|
+
// route. This prevents rendering the compact index and replacing it later.
|
|
63
|
+
const response = await fetch(openApiEndpointDataPath(endpoint.slug));
|
|
64
|
+
if (!response.ok)
|
|
65
|
+
throw new Response("OpenAPI endpoint data could not be loaded", {
|
|
66
|
+
status: response.status,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
currentOpenApiEndpoint: (await response.json()) as OpenApiEndpoint,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const meta: MetaFunction = ({ params }) => {
|
|
74
|
+
const model = createDocsModel(config, pages, [], openApiEndpoints);
|
|
75
|
+
const pathname = pathnameFor(params);
|
|
76
|
+
const page = findDocsPage(model.pages, pathname);
|
|
77
|
+
const endpoint = findOpenApiEndpoint(model.endpoints, pathname);
|
|
78
|
+
if (!page && !endpoint)
|
|
79
|
+
return [
|
|
80
|
+
{ title: `Not found | ${config.title}` },
|
|
81
|
+
{ name: "robots", content: "noindex" },
|
|
82
|
+
];
|
|
83
|
+
const title = page?.seo.title ?? `${endpoint!.title} | ${config.title}`;
|
|
84
|
+
const description =
|
|
85
|
+
page?.seo.description ??
|
|
86
|
+
endpoint?.description ??
|
|
87
|
+
`${endpoint!.method.toUpperCase()} ${endpoint!.path} API endpoint.`;
|
|
88
|
+
const canonical =
|
|
89
|
+
page?.seo.canonical ??
|
|
90
|
+
(config.siteUrl ? `${config.siteUrl}${pathname}` : undefined);
|
|
91
|
+
return [
|
|
92
|
+
{ title },
|
|
93
|
+
{ name: "description", content: description },
|
|
94
|
+
{ name: "robots", content: "index, follow" },
|
|
95
|
+
{ property: "og:type", content: "article" },
|
|
96
|
+
{ property: "og:site_name", content: config.title },
|
|
97
|
+
{ property: "og:title", content: title },
|
|
98
|
+
{ property: "og:description", content: description },
|
|
99
|
+
{ name: "twitter:card", content: "summary" },
|
|
100
|
+
{ name: "twitter:title", content: title },
|
|
101
|
+
{ name: "twitter:description", content: description },
|
|
102
|
+
...(canonical
|
|
103
|
+
? [
|
|
104
|
+
{ property: "og:url", content: canonical },
|
|
105
|
+
{
|
|
106
|
+
tagName: "link" as const,
|
|
107
|
+
rel: "canonical",
|
|
108
|
+
href: canonical,
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
: []),
|
|
112
|
+
{
|
|
113
|
+
"script:ld+json": [
|
|
114
|
+
{
|
|
115
|
+
"@context": "https://schema.org",
|
|
116
|
+
"@type": "WebSite",
|
|
117
|
+
name: config.title,
|
|
118
|
+
description: config.description,
|
|
119
|
+
...(config.siteUrl ? { url: config.siteUrl } : {}),
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"@context": "https://schema.org",
|
|
123
|
+
"@type": "TechArticle",
|
|
124
|
+
headline: page?.title ?? endpoint!.title,
|
|
125
|
+
description,
|
|
126
|
+
...(canonical ? { url: canonical } : {}),
|
|
127
|
+
isPartOf: {
|
|
128
|
+
"@type": "WebSite",
|
|
129
|
+
name: config.title,
|
|
130
|
+
...(config.siteUrl ? { url: config.siteUrl } : {}),
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export default function DocsRoute() {
|
|
139
|
+
const { currentOpenApiEndpoint } = useLoaderData<typeof loader>();
|
|
140
|
+
return (
|
|
141
|
+
<DocsShell
|
|
142
|
+
currentOpenApiEndpoint={currentOpenApiEndpoint}
|
|
143
|
+
pathname={pathnameFor(useParams())}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function DocsShell({
|
|
149
|
+
currentOpenApiEndpoint,
|
|
150
|
+
pathname,
|
|
151
|
+
}: {
|
|
152
|
+
currentOpenApiEndpoint?: OpenApiEndpoint;
|
|
153
|
+
pathname: string;
|
|
154
|
+
}) {
|
|
155
|
+
const { mounted, resolvedTheme, setTheme } = useTheme();
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<DocsApp
|
|
159
|
+
config={config}
|
|
160
|
+
currentOpenApiEndpoint={currentOpenApiEndpoint}
|
|
161
|
+
iconSet={iconSet}
|
|
162
|
+
link={RouterLink}
|
|
163
|
+
openApiEndpoints={openApiEndpoints}
|
|
164
|
+
openApiRequestUrl="/heyo-docs-internal/openapi-request"
|
|
165
|
+
pages={pages}
|
|
166
|
+
pathname={pathname}
|
|
167
|
+
isDark={mounted ? resolvedTheme === "dark" : undefined}
|
|
168
|
+
onThemeToggle={
|
|
169
|
+
mounted
|
|
170
|
+
? () => setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
|
171
|
+
: undefined
|
|
172
|
+
}
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function ErrorBoundary() {
|
|
178
|
+
return <DocsShell pathname="/__not-found" />;
|
|
179
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default, loader, meta, ErrorBoundary } from "./docs";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { llmsFull } from "@heyo-sh/heyo-docs";
|
|
2
|
+
import type { LoaderFunctionArgs } from "react-router";
|
|
3
|
+
|
|
4
|
+
import config from "../../heyo-docs.config";
|
|
5
|
+
import { pages } from "virtual:heyo-docs-content/server";
|
|
6
|
+
|
|
7
|
+
/** The complete documentation corpus, ready for an LLM to consume. */
|
|
8
|
+
export function loader({ request }: LoaderFunctionArgs) {
|
|
9
|
+
const siteUrl = config.siteUrl ?? new URL(request.url).origin;
|
|
10
|
+
return new Response(llmsFull(pages, siteUrl), {
|
|
11
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { llmsIndex } from "@heyo-sh/heyo-docs";
|
|
2
|
+
import type { LoaderFunctionArgs } from "react-router";
|
|
3
|
+
|
|
4
|
+
import config from "../../heyo-docs.config";
|
|
5
|
+
import { pages } from "virtual:heyo-docs-content/server";
|
|
6
|
+
|
|
7
|
+
/** A concise, discoverable index of every documentation page. */
|
|
8
|
+
export function loader({ request }: LoaderFunctionArgs) {
|
|
9
|
+
const siteUrl = config.siteUrl ?? new URL(request.url).origin;
|
|
10
|
+
return new Response(llmsIndex(pages, config, siteUrl), {
|
|
11
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { markdownForPage, pathnameFromMarkdownPath } from "@heyo-sh/heyo-docs";
|
|
2
|
+
import type { LoaderFunctionArgs } from "react-router";
|
|
3
|
+
|
|
4
|
+
import { pages } from "virtual:heyo-docs-content/server";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resource route used by the root middleware to serve `*.md` paths. Keeping
|
|
8
|
+
* it route-only makes React Router send the Response body directly.
|
|
9
|
+
*/
|
|
10
|
+
export function loader({ params }: LoaderFunctionArgs) {
|
|
11
|
+
const pagePathname = pathnameFromMarkdownPath(`/${params["*"] ?? ""}`);
|
|
12
|
+
const page = pagePathname
|
|
13
|
+
? pages.find((candidate) => candidate.slug === pagePathname)
|
|
14
|
+
: undefined;
|
|
15
|
+
|
|
16
|
+
if (!page)
|
|
17
|
+
return new Response("Not Found", {
|
|
18
|
+
status: 404,
|
|
19
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return new Response(markdownForPage(page), {
|
|
23
|
+
headers: { "content-type": "text/markdown; charset=utf-8" },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDocsModel,
|
|
3
|
+
findOpenApiEndpoint,
|
|
4
|
+
type OpenApiEndpoint,
|
|
5
|
+
} from "@heyo-sh/heyo-docs";
|
|
6
|
+
import type { ActionFunctionArgs } from "react-router";
|
|
7
|
+
|
|
8
|
+
import config from "../../heyo-docs.config";
|
|
9
|
+
import { pages } from "virtual:heyo-docs-content";
|
|
10
|
+
import { openApiDocuments } from "virtual:heyo-docs-openapi";
|
|
11
|
+
|
|
12
|
+
interface OpenApiRequestPayload {
|
|
13
|
+
bearerToken: string;
|
|
14
|
+
body?: string;
|
|
15
|
+
endpointSlug: string;
|
|
16
|
+
parameters: Record<string, string>;
|
|
17
|
+
server: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Sends documented API calls from the server, so browsers do not need the API
|
|
22
|
+
* to expose CORS headers. Targets are limited to servers from the OpenAPI file.
|
|
23
|
+
*/
|
|
24
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
25
|
+
if (request.method !== "POST")
|
|
26
|
+
return new Response("Method Not Allowed", {
|
|
27
|
+
headers: { Allow: "POST" },
|
|
28
|
+
status: 405,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const payload = await requestPayload(request);
|
|
32
|
+
if (!payload)
|
|
33
|
+
return new Response("Invalid OpenAPI request.", { status: 400 });
|
|
34
|
+
|
|
35
|
+
const model = createDocsModel(config, pages, openApiDocuments);
|
|
36
|
+
const endpoint = findOpenApiEndpoint(model.endpoints, payload.endpointSlug);
|
|
37
|
+
if (!endpoint) return new Response("Endpoint not found.", { status: 404 });
|
|
38
|
+
if (endpoint.servers.length && !endpoint.servers.includes(payload.server))
|
|
39
|
+
return new Response("API server is not declared in the OpenAPI schema.", {
|
|
40
|
+
status: 400,
|
|
41
|
+
});
|
|
42
|
+
if (!endpoint.servers.length && payload.server)
|
|
43
|
+
return new Response("This endpoint does not declare an API server.", {
|
|
44
|
+
status: 400,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const headers = requestHeaders(
|
|
48
|
+
endpoint,
|
|
49
|
+
payload.parameters,
|
|
50
|
+
payload.bearerToken,
|
|
51
|
+
);
|
|
52
|
+
const url = requestUrl(
|
|
53
|
+
endpoint,
|
|
54
|
+
payload.server,
|
|
55
|
+
payload.parameters,
|
|
56
|
+
request.url,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(url, {
|
|
61
|
+
method: endpoint.method.toUpperCase(),
|
|
62
|
+
headers,
|
|
63
|
+
body: payload.body,
|
|
64
|
+
});
|
|
65
|
+
const responseHeaders = new Headers();
|
|
66
|
+
const contentType = response.headers.get("content-type");
|
|
67
|
+
if (contentType) responseHeaders.set("content-type", contentType);
|
|
68
|
+
const body = [204, 205, 304].includes(response.status)
|
|
69
|
+
? null
|
|
70
|
+
: await response.arrayBuffer();
|
|
71
|
+
return new Response(body, {
|
|
72
|
+
headers: responseHeaders,
|
|
73
|
+
status: response.status,
|
|
74
|
+
statusText: response.statusText,
|
|
75
|
+
});
|
|
76
|
+
} catch {
|
|
77
|
+
return new Response("The API server could not be reached.", {
|
|
78
|
+
status: 502,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function requestPayload(
|
|
84
|
+
request: Request,
|
|
85
|
+
): Promise<OpenApiRequestPayload | undefined> {
|
|
86
|
+
try {
|
|
87
|
+
const value: unknown = await request.json();
|
|
88
|
+
if (!isRecord(value)) return undefined;
|
|
89
|
+
const endpointSlug = stringValue(value.endpointSlug);
|
|
90
|
+
const server = stringValue(value.server);
|
|
91
|
+
if (!endpointSlug || server === undefined) return undefined;
|
|
92
|
+
return {
|
|
93
|
+
bearerToken: stringValue(value.bearerToken) ?? "",
|
|
94
|
+
body: stringValue(value.body),
|
|
95
|
+
endpointSlug,
|
|
96
|
+
parameters: stringRecord(value.parameters),
|
|
97
|
+
server,
|
|
98
|
+
};
|
|
99
|
+
} catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function requestUrl(
|
|
105
|
+
endpoint: OpenApiEndpoint,
|
|
106
|
+
server: string,
|
|
107
|
+
parameters: Record<string, string>,
|
|
108
|
+
requestUrl: string,
|
|
109
|
+
) {
|
|
110
|
+
let path = endpoint.path.replace(/\{([^}]+)\}/g, (match, name: string) => {
|
|
111
|
+
const value = parameters[`path:${name}`];
|
|
112
|
+
return value ? encodeURIComponent(value) : match;
|
|
113
|
+
});
|
|
114
|
+
const query = new URLSearchParams();
|
|
115
|
+
for (const parameter of endpoint.parameters) {
|
|
116
|
+
if (parameter.in !== "query") continue;
|
|
117
|
+
const value = parameters[`query:${parameter.name}`];
|
|
118
|
+
if (value) query.set(parameter.name, value);
|
|
119
|
+
}
|
|
120
|
+
const search = query.toString();
|
|
121
|
+
path = `${path}${search ? `?${search}` : ""}`;
|
|
122
|
+
return server
|
|
123
|
+
? `${server.replace(/\/$/, "")}${path}`
|
|
124
|
+
: new URL(path, requestUrl).toString();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function requestHeaders(
|
|
128
|
+
endpoint: OpenApiEndpoint,
|
|
129
|
+
parameters: Record<string, string>,
|
|
130
|
+
bearerToken: string,
|
|
131
|
+
) {
|
|
132
|
+
const headers = new Headers();
|
|
133
|
+
for (const parameter of endpoint.parameters) {
|
|
134
|
+
if (parameter.in !== "header") continue;
|
|
135
|
+
const value = parameters[`header:${parameter.name}`];
|
|
136
|
+
if (value) headers.set(parameter.name, value);
|
|
137
|
+
}
|
|
138
|
+
if (endpoint.requestBody?.contentType)
|
|
139
|
+
headers.set("Content-Type", endpoint.requestBody.contentType);
|
|
140
|
+
if (bearerToken) headers.set("Authorization", `Bearer ${bearerToken}`);
|
|
141
|
+
return headers;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
145
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function stringRecord(value: unknown): Record<string, string> {
|
|
149
|
+
if (!isRecord(value)) return {};
|
|
150
|
+
return Object.fromEntries(
|
|
151
|
+
Object.entries(value).filter(
|
|
152
|
+
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function stringValue(value: unknown): string | undefined {
|
|
158
|
+
return typeof value === "string" ? value : undefined;
|
|
159
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default, loader, meta, ErrorBoundary } from "./docs";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Route } from "./+types/robots";
|
|
2
|
+
|
|
3
|
+
import config from "../../heyo-docs.config";
|
|
4
|
+
|
|
5
|
+
export function loader({ request }: Route.LoaderArgs) {
|
|
6
|
+
const siteUrl = config.siteUrl ?? new URL(request.url).origin;
|
|
7
|
+
|
|
8
|
+
return new Response(
|
|
9
|
+
[
|
|
10
|
+
"User-agent: *",
|
|
11
|
+
"Allow: /",
|
|
12
|
+
"Disallow: /__heyo-docs/",
|
|
13
|
+
"",
|
|
14
|
+
`Sitemap: ${siteUrl}/sitemap.xml`,
|
|
15
|
+
"",
|
|
16
|
+
].join("\n"),
|
|
17
|
+
{ headers: { "content-type": "text/plain; charset=utf-8" } },
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { rssXml } from "@heyo-sh/heyo-docs";
|
|
2
|
+
import type { LoaderFunctionArgs } from "react-router";
|
|
3
|
+
|
|
4
|
+
import config from "../../heyo-docs.config";
|
|
5
|
+
import { pages } from "virtual:heyo-docs-content/server";
|
|
6
|
+
|
|
7
|
+
/** RSS 2.0 feed for every `<Update>` in the configured changelog groups. */
|
|
8
|
+
export function loader({ request }: LoaderFunctionArgs) {
|
|
9
|
+
const siteUrl = config.siteUrl ?? new URL(request.url).origin;
|
|
10
|
+
return new Response(rssXml(pages, config, siteUrl), {
|
|
11
|
+
headers: { "content-type": "application/rss+xml; charset=utf-8" },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createDocsModel, sitemapXml } from "@heyo-sh/heyo-docs";
|
|
2
|
+
import type { Route } from "./+types/sitemap";
|
|
3
|
+
|
|
4
|
+
import config from "../../heyo-docs.config";
|
|
5
|
+
import { pages } from "virtual:heyo-docs-content";
|
|
6
|
+
import { openApiDocuments } from "virtual:heyo-docs-openapi";
|
|
7
|
+
|
|
8
|
+
export function loader({ request }: Route.LoaderArgs) {
|
|
9
|
+
const siteUrl = config.siteUrl ?? new URL(request.url).origin;
|
|
10
|
+
const model = createDocsModel(config, pages, openApiDocuments);
|
|
11
|
+
return new Response(
|
|
12
|
+
sitemapXml(
|
|
13
|
+
siteUrl,
|
|
14
|
+
[...model.pages, ...model.endpoints].map((page) => page.slug),
|
|
15
|
+
),
|
|
16
|
+
{ headers: { "content-type": "application/xml; charset=utf-8" } },
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { index, route, type RouteConfig } from "@react-router/dev/routes";
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
index("routes/home.tsx"),
|
|
5
|
+
route("robots.txt", "routes/robots.ts"),
|
|
6
|
+
route("sitemap.xml", "routes/sitemap.ts"),
|
|
7
|
+
route("rss.xml", "routes/rss.ts"),
|
|
8
|
+
route("llms.txt", "routes/llms.ts"),
|
|
9
|
+
route("llms-full.txt", "routes/llms-full.ts"),
|
|
10
|
+
route("__heyo-docs/markdown/*", "routes/markdown.ts"),
|
|
11
|
+
route("heyo-docs-internal/openapi-request", "routes/openapi-request.ts"),
|
|
12
|
+
route("*", "routes/page.tsx"),
|
|
13
|
+
] satisfies RouteConfig;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare module "virtual:heyo-docs-content" {
|
|
2
|
+
import type { DocsPage } from "@heyo-sh/heyo-docs";
|
|
3
|
+
export const pages: DocsPage[];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module "virtual:heyo-docs-content/server" {
|
|
7
|
+
import type { MarkdownPage } from "@heyo-sh/heyo-docs";
|
|
8
|
+
export const pages: MarkdownPage[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module "virtual:heyo-docs-openapi" {
|
|
12
|
+
import type { OpenApiDocumentSource } from "@heyo-sh/heyo-docs";
|
|
13
|
+
export const openApiDocuments: OpenApiDocumentSource[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare module "virtual:heyo-docs-openapi/index" {
|
|
17
|
+
import type { OpenApiEndpoint } from "@heyo-sh/heyo-docs";
|
|
18
|
+
export const openApiEndpoints: OpenApiEndpoint[];
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-mira",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/app.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "remixicon",
|
|
14
|
+
"rtl": false,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "~/components",
|
|
17
|
+
"utils": "~/lib/utils",
|
|
18
|
+
"ui": "~/components/ui",
|
|
19
|
+
"lib": "~/lib",
|
|
20
|
+
"hooks": "~/hooks"
|
|
21
|
+
},
|
|
22
|
+
"menuColor": "default",
|
|
23
|
+
"menuAccent": "subtle",
|
|
24
|
+
"registries": {}
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting started
|
|
3
|
+
description: Run your new documentation site locally.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Getting started
|
|
7
|
+
|
|
8
|
+
Install dependencies with your chosen package manager and start the development server.
|
|
9
|
+
|
|
10
|
+
## Create a page
|
|
11
|
+
|
|
12
|
+
Create `content/guides/deploying.mdx` to get the `/guides/deploying` route. Its frontmatter controls its title and description.
|
|
13
|
+
|
|
14
|
+
## Configure the documentation
|
|
15
|
+
|
|
16
|
+
Use `heyo-docs.config.ts` to change the title, colors, tabs, sidebar groups, footer links and selected theme without regenerating your project. Add pages or directories to a section's `pages` list to control the sidebar order.
|