@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,165 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DocsApp,
|
|
3
|
+
openApiEndpointDataPath,
|
|
4
|
+
type OpenApiEndpoint,
|
|
5
|
+
} from "@heyo-sh/heyo-docs";
|
|
6
|
+
import {
|
|
7
|
+
createContext,
|
|
8
|
+
forwardRef,
|
|
9
|
+
useCallback,
|
|
10
|
+
useContext,
|
|
11
|
+
useEffect,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
type ComponentPropsWithoutRef,
|
|
15
|
+
type MouseEvent,
|
|
16
|
+
} from "react";
|
|
17
|
+
|
|
18
|
+
import config from "../../heyo-docs.config";
|
|
19
|
+
import { iconSet } from "../heyo-docs-icons";
|
|
20
|
+
import { THEME_STORAGE_KEY } from "../lib/theme";
|
|
21
|
+
import { openApiEndpoints } from "virtual:heyo-docs-openapi/index";
|
|
22
|
+
import { pages } from "virtual:heyo-docs-content";
|
|
23
|
+
import { ThemeProvider, useTheme } from "./theme-provider";
|
|
24
|
+
|
|
25
|
+
type Navigate = (href: string) => void;
|
|
26
|
+
|
|
27
|
+
const AstroNavigationContext = createContext<Navigate | undefined>(undefined);
|
|
28
|
+
|
|
29
|
+
const AstroLink = forwardRef<HTMLAnchorElement, ComponentPropsWithoutRef<"a">>(
|
|
30
|
+
function AstroLink({ href, onClick, target, ...props }, ref) {
|
|
31
|
+
const navigate = useContext(AstroNavigationContext);
|
|
32
|
+
|
|
33
|
+
const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {
|
|
34
|
+
onClick?.(event);
|
|
35
|
+
if (
|
|
36
|
+
!navigate ||
|
|
37
|
+
event.defaultPrevented ||
|
|
38
|
+
event.button !== 0 ||
|
|
39
|
+
event.metaKey ||
|
|
40
|
+
event.altKey ||
|
|
41
|
+
event.ctrlKey ||
|
|
42
|
+
event.shiftKey ||
|
|
43
|
+
target
|
|
44
|
+
)
|
|
45
|
+
return;
|
|
46
|
+
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
navigate(href ?? "/");
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<a
|
|
53
|
+
{...props}
|
|
54
|
+
href={href}
|
|
55
|
+
onClick={handleClick}
|
|
56
|
+
ref={ref}
|
|
57
|
+
target={target}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
export function AstroDocsApp({
|
|
64
|
+
currentOpenApiEndpoint,
|
|
65
|
+
pathname,
|
|
66
|
+
}: {
|
|
67
|
+
currentOpenApiEndpoint?: OpenApiEndpoint;
|
|
68
|
+
pathname: string;
|
|
69
|
+
}) {
|
|
70
|
+
return (
|
|
71
|
+
<ThemeProvider defaultTheme={config.mode} storageKey={THEME_STORAGE_KEY}>
|
|
72
|
+
<DocsShell
|
|
73
|
+
currentOpenApiEndpoint={currentOpenApiEndpoint}
|
|
74
|
+
pathname={pathname}
|
|
75
|
+
/>
|
|
76
|
+
</ThemeProvider>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Content is imported in this island instead of crossing Astro's serialisation
|
|
82
|
+
* boundary. MDX pages are React components and must stay module references.
|
|
83
|
+
*/
|
|
84
|
+
function DocsShell({
|
|
85
|
+
currentOpenApiEndpoint,
|
|
86
|
+
pathname,
|
|
87
|
+
}: {
|
|
88
|
+
currentOpenApiEndpoint?: OpenApiEndpoint;
|
|
89
|
+
pathname: string;
|
|
90
|
+
}) {
|
|
91
|
+
const { mounted, resolvedTheme, setTheme } = useTheme();
|
|
92
|
+
const [activePathname, setActivePathname] = useState(pathname);
|
|
93
|
+
const [activeOpenApiEndpoint, setActiveOpenApiEndpoint] = useState(
|
|
94
|
+
currentOpenApiEndpoint,
|
|
95
|
+
);
|
|
96
|
+
const navigationId = useRef(0);
|
|
97
|
+
|
|
98
|
+
const navigate = useCallback(async (href: string, updateHistory = true) => {
|
|
99
|
+
const destination = new URL(href, window.location.href);
|
|
100
|
+
if (destination.origin !== window.location.origin) {
|
|
101
|
+
window.location.assign(href);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const nextPathname = destination.pathname;
|
|
106
|
+
const indexedEndpoint = openApiEndpoints.find(
|
|
107
|
+
(endpoint) => endpoint.slug === nextPathname,
|
|
108
|
+
);
|
|
109
|
+
const page = pages.find((candidate) => candidate.slug === nextPathname);
|
|
110
|
+
if (!indexedEndpoint && !page) {
|
|
111
|
+
window.location.assign(href);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const currentNavigation = ++navigationId.current;
|
|
116
|
+
let endpoint: OpenApiEndpoint | undefined;
|
|
117
|
+
if (indexedEndpoint) {
|
|
118
|
+
const response = await fetch(
|
|
119
|
+
openApiEndpointDataPath(indexedEndpoint.slug),
|
|
120
|
+
);
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
window.location.assign(href);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
endpoint = (await response.json()) as OpenApiEndpoint;
|
|
126
|
+
}
|
|
127
|
+
if (currentNavigation !== navigationId.current) return;
|
|
128
|
+
|
|
129
|
+
if (updateHistory) window.history.pushState({}, "", destination);
|
|
130
|
+
document.title =
|
|
131
|
+
page?.seo.title ?? `${indexedEndpoint!.title} | ${config.title}`;
|
|
132
|
+
setActiveOpenApiEndpoint(endpoint);
|
|
133
|
+
setActivePathname(nextPathname);
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
const onPopState = () => {
|
|
138
|
+
void navigate(window.location.href, false);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
window.addEventListener("popstate", onPopState);
|
|
142
|
+
return () => window.removeEventListener("popstate", onPopState);
|
|
143
|
+
}, [navigate]);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<AstroNavigationContext.Provider value={navigate}>
|
|
147
|
+
<DocsApp
|
|
148
|
+
config={config}
|
|
149
|
+
iconSet={iconSet}
|
|
150
|
+
isDark={mounted ? resolvedTheme === "dark" : undefined}
|
|
151
|
+
link={AstroLink}
|
|
152
|
+
onThemeToggle={
|
|
153
|
+
mounted
|
|
154
|
+
? () => setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
|
155
|
+
: undefined
|
|
156
|
+
}
|
|
157
|
+
currentOpenApiEndpoint={activeOpenApiEndpoint}
|
|
158
|
+
openApiEndpoints={openApiEndpoints}
|
|
159
|
+
openApiRequestUrl="/heyo-docs-internal/openapi-request"
|
|
160
|
+
pages={pages}
|
|
161
|
+
pathname={activePathname}
|
|
162
|
+
/>
|
|
163
|
+
</AstroNavigationContext.Provider>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from "react";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
applyTheme,
|
|
11
|
+
DEFAULT_THEME,
|
|
12
|
+
getStoredTheme,
|
|
13
|
+
type ResolvedTheme,
|
|
14
|
+
THEME_STORAGE_KEY,
|
|
15
|
+
type Theme,
|
|
16
|
+
} from "../lib/theme";
|
|
17
|
+
|
|
18
|
+
type ThemeProviderProps = {
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
defaultTheme?: Theme;
|
|
21
|
+
storageKey?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type ThemeProviderState = {
|
|
25
|
+
theme: Theme;
|
|
26
|
+
resolvedTheme: ResolvedTheme;
|
|
27
|
+
mounted: boolean;
|
|
28
|
+
setTheme: (theme: Theme) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ThemeProviderContext = createContext<ThemeProviderState | undefined>(
|
|
32
|
+
undefined,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export function ThemeProvider({
|
|
36
|
+
children,
|
|
37
|
+
defaultTheme = DEFAULT_THEME,
|
|
38
|
+
storageKey = THEME_STORAGE_KEY,
|
|
39
|
+
}: ThemeProviderProps) {
|
|
40
|
+
const [theme, setThemeState] = useState<Theme>(defaultTheme);
|
|
41
|
+
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(
|
|
42
|
+
defaultTheme === "system" ? "light" : defaultTheme,
|
|
43
|
+
);
|
|
44
|
+
const [mounted, setMounted] = useState(false);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const syncTheme = () => {
|
|
48
|
+
const nextTheme = getStoredTheme(storageKey, defaultTheme);
|
|
49
|
+
|
|
50
|
+
setThemeState(nextTheme);
|
|
51
|
+
setResolvedTheme(applyTheme(nextTheme));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
syncTheme();
|
|
55
|
+
|
|
56
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
57
|
+
const handleSystemThemeChange = () => {
|
|
58
|
+
const nextTheme = getStoredTheme(storageKey, defaultTheme);
|
|
59
|
+
setResolvedTheme(applyTheme(nextTheme));
|
|
60
|
+
};
|
|
61
|
+
const handleStorage = (event: StorageEvent) => {
|
|
62
|
+
if (event.key !== null && event.key !== storageKey) return;
|
|
63
|
+
syncTheme();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
mediaQuery.addEventListener("change", handleSystemThemeChange);
|
|
67
|
+
window.addEventListener("storage", handleStorage);
|
|
68
|
+
setMounted(true);
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
mediaQuery.removeEventListener("change", handleSystemThemeChange);
|
|
72
|
+
window.removeEventListener("storage", handleStorage);
|
|
73
|
+
};
|
|
74
|
+
}, [defaultTheme, storageKey]);
|
|
75
|
+
|
|
76
|
+
const value = {
|
|
77
|
+
theme,
|
|
78
|
+
resolvedTheme,
|
|
79
|
+
mounted,
|
|
80
|
+
setTheme: (nextTheme: Theme) => {
|
|
81
|
+
try {
|
|
82
|
+
window.localStorage.setItem(storageKey, nextTheme);
|
|
83
|
+
} catch {
|
|
84
|
+
// The requested preference still applies when storage is unavailable.
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setThemeState(nextTheme);
|
|
88
|
+
setResolvedTheme(applyTheme(nextTheme));
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<ThemeProviderContext.Provider value={value}>
|
|
94
|
+
{children}
|
|
95
|
+
</ThemeProviderContext.Provider>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function useTheme() {
|
|
100
|
+
const context = useContext(ThemeProviderContext);
|
|
101
|
+
|
|
102
|
+
if (context === undefined)
|
|
103
|
+
throw new Error("useTheme must be used within a ThemeProvider");
|
|
104
|
+
|
|
105
|
+
return context;
|
|
106
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RiArrowDownLine,
|
|
3
|
+
RiArrowDownSLine,
|
|
4
|
+
RiArrowRightLine,
|
|
5
|
+
RiArrowRightSLine,
|
|
6
|
+
RiArrowUpLine,
|
|
7
|
+
RiBookOpenLine,
|
|
8
|
+
RiChatAiLine,
|
|
9
|
+
RiCheckboxCircleLine,
|
|
10
|
+
RiCheckLine,
|
|
11
|
+
RiCloseCircleLine,
|
|
12
|
+
RiCloseLine,
|
|
13
|
+
RiCodeBoxLine,
|
|
14
|
+
RiCornerDownLeftLine,
|
|
15
|
+
RiCursorLine,
|
|
16
|
+
RiExternalLinkLine,
|
|
17
|
+
RiFileCopyLine,
|
|
18
|
+
RiFileTextLine,
|
|
19
|
+
RiFolderLine,
|
|
20
|
+
RiGitForkLine,
|
|
21
|
+
RiGitRepositoryLine,
|
|
22
|
+
RiGithubLine,
|
|
23
|
+
RiGlobalLine,
|
|
24
|
+
RiInformationLine,
|
|
25
|
+
RiLightbulbLine,
|
|
26
|
+
RiLoginBoxLine,
|
|
27
|
+
RiMoonLine,
|
|
28
|
+
RiNewsLine,
|
|
29
|
+
RiSearchLine,
|
|
30
|
+
RiStarLine,
|
|
31
|
+
RiSunLine,
|
|
32
|
+
} from "@remixicon/react";
|
|
33
|
+
import type { IconSet } from "@heyo-sh/heyo-docs";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* This mapping is intentionally local to the generated application. Replace
|
|
37
|
+
* it with another pack to change the built-in UI without bundling all packs.
|
|
38
|
+
*/
|
|
39
|
+
const remixIconSet = {
|
|
40
|
+
book: RiBookOpenLine,
|
|
41
|
+
changelog: RiNewsLine,
|
|
42
|
+
code: RiCodeBoxLine,
|
|
43
|
+
file: RiFileTextLine,
|
|
44
|
+
github: RiGithubLine,
|
|
45
|
+
globe: RiGlobalLine,
|
|
46
|
+
signIn: RiLoginBoxLine,
|
|
47
|
+
chevronDown: RiArrowDownSLine,
|
|
48
|
+
chevronRight: RiArrowRightSLine,
|
|
49
|
+
arrowDown: RiArrowDownLine,
|
|
50
|
+
arrowUp: RiArrowUpLine,
|
|
51
|
+
arrowRight: RiArrowRightLine,
|
|
52
|
+
close: RiCloseLine,
|
|
53
|
+
cornerDownLeft: RiCornerDownLeftLine,
|
|
54
|
+
search: RiSearchLine,
|
|
55
|
+
sun: RiSunLine,
|
|
56
|
+
moon: RiMoonLine,
|
|
57
|
+
checkCircle: RiCheckboxCircleLine,
|
|
58
|
+
check: RiCheckLine,
|
|
59
|
+
closeCircle: RiCloseCircleLine,
|
|
60
|
+
externalLink: RiExternalLinkLine,
|
|
61
|
+
folder: RiFolderLine,
|
|
62
|
+
gitFork: RiGitForkLine,
|
|
63
|
+
gitRepository: RiGitRepositoryLine,
|
|
64
|
+
information: RiInformationLine,
|
|
65
|
+
lightbulb: RiLightbulbLine,
|
|
66
|
+
copy: RiFileCopyLine,
|
|
67
|
+
star: RiStarLine,
|
|
68
|
+
bot: RiChatAiLine,
|
|
69
|
+
cursor: RiCursorLine,
|
|
70
|
+
chat: RiChatAiLine,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A generated project can temporarily resolve the icon package and Heyo Docs
|
|
75
|
+
* through distinct React type installations (notably with file dependencies).
|
|
76
|
+
* The runtime contract is identical SVG props, so expose the local mapping as
|
|
77
|
+
* the framework-neutral icon contract after keeping the imports tree-shakeable.
|
|
78
|
+
*/
|
|
79
|
+
export const iconSet = remixIconSet as unknown as Required<IconSet>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from "../../heyo-docs.config";
|
|
3
|
+
import "../styles/app.css";
|
|
4
|
+
import { getThemeScript, THEME_STORAGE_KEY } from "../lib/theme";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
canonical?: string;
|
|
10
|
+
robots?: "index, follow" | "noindex";
|
|
11
|
+
structuredData: unknown[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
title,
|
|
16
|
+
description,
|
|
17
|
+
canonical,
|
|
18
|
+
robots = "index, follow",
|
|
19
|
+
structuredData,
|
|
20
|
+
} = Astro.props;
|
|
21
|
+
const jsonLd = JSON.stringify(structuredData).replace(/</g, "\\u003c");
|
|
22
|
+
const hasChangelog = config.groups.some((group) => group.type === "changelog");
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<!doctype html>
|
|
26
|
+
<html lang="en">
|
|
27
|
+
<head>
|
|
28
|
+
<meta charset="utf-8" />
|
|
29
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
30
|
+
<meta name="color-scheme" content="light dark" />
|
|
31
|
+
<meta name="robots" content={robots} />
|
|
32
|
+
<meta name="referrer" content="strict-origin-when-cross-origin" />
|
|
33
|
+
<title>{title}</title>
|
|
34
|
+
<meta name="description" content={description} />
|
|
35
|
+
<meta property="og:type" content="article" />
|
|
36
|
+
<meta property="og:site_name" content={config.title} />
|
|
37
|
+
<meta property="og:title" content={title} />
|
|
38
|
+
<meta property="og:description" content={description} />
|
|
39
|
+
<meta name="twitter:card" content="summary" />
|
|
40
|
+
<meta name="twitter:title" content={title} />
|
|
41
|
+
<meta name="twitter:description" content={description} />
|
|
42
|
+
{canonical && <link rel="canonical" href={canonical} />}
|
|
43
|
+
{canonical && <meta property="og:url" content={canonical} />}
|
|
44
|
+
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
45
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
46
|
+
<link
|
|
47
|
+
rel="icon"
|
|
48
|
+
href="/favicon-96x96.png"
|
|
49
|
+
sizes="96x96"
|
|
50
|
+
type="image/png"
|
|
51
|
+
/>
|
|
52
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" />
|
|
53
|
+
<link rel="manifest" href="/site.webmanifest" />
|
|
54
|
+
{
|
|
55
|
+
hasChangelog && (
|
|
56
|
+
<link
|
|
57
|
+
rel="alternate"
|
|
58
|
+
href="/rss.xml"
|
|
59
|
+
type="application/rss+xml"
|
|
60
|
+
title={`${config.title} updates`}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
<script is:inline set:html={getThemeScript(THEME_STORAGE_KEY, config.mode)}></script>
|
|
65
|
+
<script is:inline type="application/ld+json" set:html={jsonLd}></script>
|
|
66
|
+
</head>
|
|
67
|
+
<body>
|
|
68
|
+
<slot />
|
|
69
|
+
</body>
|
|
70
|
+
</html>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
changelogGroupForPage,
|
|
3
|
+
createDocsModel,
|
|
4
|
+
findDocsPage,
|
|
5
|
+
findOpenApiEndpoint,
|
|
6
|
+
normaliseDocsPathname,
|
|
7
|
+
} from "@heyo-sh/heyo-docs";
|
|
8
|
+
|
|
9
|
+
import config from "../../heyo-docs.config";
|
|
10
|
+
import { openApiDocuments } from "virtual:heyo-docs-openapi";
|
|
11
|
+
import { pages } from "virtual:heyo-docs-content";
|
|
12
|
+
|
|
13
|
+
export function pathnameForSlug(slug: string | undefined): string {
|
|
14
|
+
return normaliseDocsPathname(slug ? `/${slug}` : "/");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function docsContext(pathname: string) {
|
|
18
|
+
const model = createDocsModel(config, pages, openApiDocuments);
|
|
19
|
+
const page = findDocsPage(model.pages, pathname);
|
|
20
|
+
const endpoint = findOpenApiEndpoint(model.endpoints, pathname);
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
config,
|
|
24
|
+
model,
|
|
25
|
+
page,
|
|
26
|
+
endpoint,
|
|
27
|
+
changelogGroup: page
|
|
28
|
+
? changelogGroupForPage(config.groups, page, model.pages)
|
|
29
|
+
: undefined,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createDocsModel,
|
|
3
|
+
findOpenApiEndpoint,
|
|
4
|
+
type OpenApiEndpoint,
|
|
5
|
+
} from "@heyo-sh/heyo-docs";
|
|
6
|
+
|
|
7
|
+
import config from "../../heyo-docs.config";
|
|
8
|
+
import { openApiDocuments } from "virtual:heyo-docs-openapi";
|
|
9
|
+
import { pages } from "virtual:heyo-docs-content";
|
|
10
|
+
|
|
11
|
+
interface OpenApiRequestPayload {
|
|
12
|
+
bearerToken: string;
|
|
13
|
+
body?: string;
|
|
14
|
+
endpointSlug: string;
|
|
15
|
+
parameters: Record<string, string>;
|
|
16
|
+
server: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sends documented API calls from the server, so browsers do not need the API
|
|
21
|
+
* to expose CORS headers. Targets are limited to servers from the OpenAPI file.
|
|
22
|
+
*/
|
|
23
|
+
export async function handleOpenApiRequest(
|
|
24
|
+
request: Request,
|
|
25
|
+
): Promise<Response> {
|
|
26
|
+
if (request.method !== "POST")
|
|
27
|
+
return new Response("Method Not Allowed", {
|
|
28
|
+
headers: { Allow: "POST" },
|
|
29
|
+
status: 405,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const payload = await requestPayload(request);
|
|
33
|
+
if (!payload)
|
|
34
|
+
return new Response("Invalid OpenAPI request.", { status: 400 });
|
|
35
|
+
|
|
36
|
+
const model = createDocsModel(config, pages, openApiDocuments);
|
|
37
|
+
const endpoint = findOpenApiEndpoint(model.endpoints, payload.endpointSlug);
|
|
38
|
+
if (!endpoint) return new Response("Endpoint not found.", { status: 404 });
|
|
39
|
+
if (endpoint.servers.length && !endpoint.servers.includes(payload.server))
|
|
40
|
+
return new Response("API server is not declared in the OpenAPI schema.", {
|
|
41
|
+
status: 400,
|
|
42
|
+
});
|
|
43
|
+
if (!endpoint.servers.length && payload.server)
|
|
44
|
+
return new Response("This endpoint does not declare an API server.", {
|
|
45
|
+
status: 400,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const headers = requestHeaders(
|
|
49
|
+
endpoint,
|
|
50
|
+
payload.parameters,
|
|
51
|
+
payload.bearerToken,
|
|
52
|
+
);
|
|
53
|
+
const url = requestUrl(
|
|
54
|
+
endpoint,
|
|
55
|
+
payload.server,
|
|
56
|
+
payload.parameters,
|
|
57
|
+
request.url,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const response = await fetch(url, {
|
|
62
|
+
method: endpoint.method.toUpperCase(),
|
|
63
|
+
headers,
|
|
64
|
+
body: payload.body,
|
|
65
|
+
});
|
|
66
|
+
const responseHeaders = new Headers();
|
|
67
|
+
const contentType = response.headers.get("content-type");
|
|
68
|
+
if (contentType) responseHeaders.set("content-type", contentType);
|
|
69
|
+
const body = [204, 205, 304].includes(response.status)
|
|
70
|
+
? null
|
|
71
|
+
: await response.arrayBuffer();
|
|
72
|
+
return new Response(body, {
|
|
73
|
+
headers: responseHeaders,
|
|
74
|
+
status: response.status,
|
|
75
|
+
statusText: response.statusText,
|
|
76
|
+
});
|
|
77
|
+
} catch {
|
|
78
|
+
return new Response("The API server could not be reached.", {
|
|
79
|
+
status: 502,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function requestPayload(
|
|
85
|
+
request: Request,
|
|
86
|
+
): Promise<OpenApiRequestPayload | undefined> {
|
|
87
|
+
try {
|
|
88
|
+
const value: unknown = await request.json();
|
|
89
|
+
if (!isRecord(value)) return undefined;
|
|
90
|
+
const endpointSlug = stringValue(value.endpointSlug);
|
|
91
|
+
const server = stringValue(value.server);
|
|
92
|
+
if (!endpointSlug || server === undefined) return undefined;
|
|
93
|
+
return {
|
|
94
|
+
bearerToken: stringValue(value.bearerToken) ?? "",
|
|
95
|
+
body: stringValue(value.body),
|
|
96
|
+
endpointSlug,
|
|
97
|
+
parameters: stringRecord(value.parameters),
|
|
98
|
+
server,
|
|
99
|
+
};
|
|
100
|
+
} catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function requestUrl(
|
|
106
|
+
endpoint: OpenApiEndpoint,
|
|
107
|
+
server: string,
|
|
108
|
+
parameters: Record<string, string>,
|
|
109
|
+
requestUrl: string,
|
|
110
|
+
) {
|
|
111
|
+
let path = endpoint.path.replace(/\{([^}]+)\}/g, (match, name: string) => {
|
|
112
|
+
const value = parameters[`path:${name}`];
|
|
113
|
+
return value ? encodeURIComponent(value) : match;
|
|
114
|
+
});
|
|
115
|
+
const query = new URLSearchParams();
|
|
116
|
+
for (const parameter of endpoint.parameters) {
|
|
117
|
+
if (parameter.in !== "query") continue;
|
|
118
|
+
const value = parameters[`query:${parameter.name}`];
|
|
119
|
+
if (value) query.set(parameter.name, value);
|
|
120
|
+
}
|
|
121
|
+
const search = query.toString();
|
|
122
|
+
path = `${path}${search ? `?${search}` : ""}`;
|
|
123
|
+
return server
|
|
124
|
+
? `${server.replace(/\/$/, "")}${path}`
|
|
125
|
+
: new URL(path, requestUrl).toString();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function requestHeaders(
|
|
129
|
+
endpoint: OpenApiEndpoint,
|
|
130
|
+
parameters: Record<string, string>,
|
|
131
|
+
bearerToken: string,
|
|
132
|
+
) {
|
|
133
|
+
const headers = new Headers();
|
|
134
|
+
for (const parameter of endpoint.parameters) {
|
|
135
|
+
if (parameter.in !== "header") continue;
|
|
136
|
+
const value = parameters[`header:${parameter.name}`];
|
|
137
|
+
if (value) headers.set(parameter.name, value);
|
|
138
|
+
}
|
|
139
|
+
if (endpoint.requestBody?.contentType)
|
|
140
|
+
headers.set("Content-Type", endpoint.requestBody.contentType);
|
|
141
|
+
if (bearerToken) headers.set("Authorization", `Bearer ${bearerToken}`);
|
|
142
|
+
return headers;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
146
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function stringRecord(value: unknown): Record<string, string> {
|
|
150
|
+
if (!isRecord(value)) return {};
|
|
151
|
+
return Object.fromEntries(
|
|
152
|
+
Object.entries(value).filter(
|
|
153
|
+
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function stringValue(value: unknown): string | undefined {
|
|
159
|
+
return typeof value === "string" ? value : undefined;
|
|
160
|
+
}
|