@axerity/cli 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.
Files changed (186) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/axerity.default.json +135 -0
  4. package/axerity.schema.json +268 -0
  5. package/bin/axerity.js +305 -0
  6. package/mdsvex.config.js +261 -0
  7. package/package.json +103 -0
  8. package/scripts/prepare-engine.mjs +20 -0
  9. package/src/app.d.ts +17 -0
  10. package/src/app.html +39 -0
  11. package/src/content/demo/api/meta.json +5 -0
  12. package/src/content/demo/api/pet/add-pet.md +105 -0
  13. package/src/content/demo/api/pet/delete-pet.md +70 -0
  14. package/src/content/demo/api/pet/find-by-status.md +72 -0
  15. package/src/content/demo/api/pet/find-by-tags.md +64 -0
  16. package/src/content/demo/api/pet/get-pet.md +99 -0
  17. package/src/content/demo/api/pet/meta.json +15 -0
  18. package/src/content/demo/api/pet/pet-object.md +112 -0
  19. package/src/content/demo/api/pet/update-pet-with-form.md +69 -0
  20. package/src/content/demo/api/pet/update-pet.md +79 -0
  21. package/src/content/demo/api/pet/upload-image.md +79 -0
  22. package/src/content/demo/api/store/delete-order.md +62 -0
  23. package/src/content/demo/api/store/get-order.md +70 -0
  24. package/src/content/demo/api/store/inventory.md +54 -0
  25. package/src/content/demo/api/store/meta.json +5 -0
  26. package/src/content/demo/api/store/order-object.md +77 -0
  27. package/src/content/demo/api/store/place-order.md +83 -0
  28. package/src/content/demo/api/user/create-user.md +69 -0
  29. package/src/content/demo/api/user/create-with-list.md +57 -0
  30. package/src/content/demo/api/user/delete-user.md +61 -0
  31. package/src/content/demo/api/user/get-user.md +69 -0
  32. package/src/content/demo/api/user/login.md +80 -0
  33. package/src/content/demo/api/user/logout.md +45 -0
  34. package/src/content/demo/api/user/meta.json +14 -0
  35. package/src/content/demo/api/user/update-user.md +69 -0
  36. package/src/content/demo/api/user/user-object.md +85 -0
  37. package/src/content/demo/changelog.md +44 -0
  38. package/src/content/demo/components/accordion.md +70 -0
  39. package/src/content/demo/components/api.md +185 -0
  40. package/src/content/demo/components/badge.md +34 -0
  41. package/src/content/demo/components/callout.md +83 -0
  42. package/src/content/demo/components/cards.md +88 -0
  43. package/src/content/demo/components/code-group.md +55 -0
  44. package/src/content/demo/components/columns.md +42 -0
  45. package/src/content/demo/components/frame.md +51 -0
  46. package/src/content/demo/components/icon.md +54 -0
  47. package/src/content/demo/components/kbd.md +28 -0
  48. package/src/content/demo/components/meta.json +26 -0
  49. package/src/content/demo/components/roadmap.md +86 -0
  50. package/src/content/demo/components/steps.md +72 -0
  51. package/src/content/demo/components/tabs.md +146 -0
  52. package/src/content/demo/components/tooltip.md +44 -0
  53. package/src/content/demo/components/tree.md +83 -0
  54. package/src/content/demo/components/type-table.md +77 -0
  55. package/src/content/demo/components/update.md +48 -0
  56. package/src/content/demo/components/video.md +56 -0
  57. package/src/content/demo/components/webhooks.md +109 -0
  58. package/src/content/demo/components/websockets.md +101 -0
  59. package/src/content/demo/configuration/ai.md +40 -0
  60. package/src/content/demo/configuration/cli.md +46 -0
  61. package/src/content/demo/configuration/deployment.md +105 -0
  62. package/src/content/demo/configuration/index.md +92 -0
  63. package/src/content/demo/configuration/layouts.md +51 -0
  64. package/src/content/demo/configuration/meta.json +5 -0
  65. package/src/content/demo/configuration/navigation.md +167 -0
  66. package/src/content/demo/configuration/openapi.md +103 -0
  67. package/src/content/demo/configuration/search.md +36 -0
  68. package/src/content/demo/index.md +59 -0
  69. package/src/content/demo/installation.md +49 -0
  70. package/src/content/demo/meta.json +15 -0
  71. package/src/content/demo/quick-start.md +47 -0
  72. package/src/content/demo/theming/advanced.md +116 -0
  73. package/src/content/demo/theming/code.md +66 -0
  74. package/src/content/demo/theming/colors.md +103 -0
  75. package/src/content/demo/theming/index.md +88 -0
  76. package/src/content/demo/theming/layout.md +71 -0
  77. package/src/content/demo/theming/meta.json +5 -0
  78. package/src/content/demo/theming/themes.md +99 -0
  79. package/src/content/demo/theming/typography.md +83 -0
  80. package/src/content/demo/writing/code-blocks.md +154 -0
  81. package/src/content/demo/writing/diagrams.md +44 -0
  82. package/src/content/demo/writing/frontmatter.md +33 -0
  83. package/src/content/demo/writing/markdown.md +62 -0
  84. package/src/content/demo/writing/meta.json +5 -0
  85. package/src/hooks.server.ts +49 -0
  86. package/src/lib/assets/favicon.svg +1 -0
  87. package/src/lib/base.ts +12 -0
  88. package/src/lib/components/DynamicIcon.svelte +26 -0
  89. package/src/lib/components/docs/Analytics.svelte +38 -0
  90. package/src/lib/components/docs/Banner.svelte +44 -0
  91. package/src/lib/components/docs/Breadcrumbs.svelte +38 -0
  92. package/src/lib/components/docs/CopyPageMenu.svelte +119 -0
  93. package/src/lib/components/docs/DocsLayout.svelte +192 -0
  94. package/src/lib/components/docs/Footer.svelte +60 -0
  95. package/src/lib/components/docs/Mermaid.svelte +39 -0
  96. package/src/lib/components/docs/Navbar.svelte +144 -0
  97. package/src/lib/components/docs/PageMeta.svelte +35 -0
  98. package/src/lib/components/docs/PageNav.svelte +44 -0
  99. package/src/lib/components/docs/SearchDialog.svelte +182 -0
  100. package/src/lib/components/docs/Sidebar.svelte +85 -0
  101. package/src/lib/components/docs/SidebarDropdown.svelte +56 -0
  102. package/src/lib/components/docs/SidebarFooterLinks.svelte +19 -0
  103. package/src/lib/components/docs/SidebarGroup.svelte +54 -0
  104. package/src/lib/components/docs/SidebarLink.svelte +67 -0
  105. package/src/lib/components/docs/TableOfContents.svelte +77 -0
  106. package/src/lib/components/docs/ThemeToggle.svelte +19 -0
  107. package/src/lib/components/docs/VersionSwitcher.svelte +80 -0
  108. package/src/lib/components/kit/Accordion.svelte +60 -0
  109. package/src/lib/components/kit/AccordionGroup.svelte +13 -0
  110. package/src/lib/components/kit/Badge.svelte +32 -0
  111. package/src/lib/components/kit/Callout.svelte +51 -0
  112. package/src/lib/components/kit/Card.svelte +72 -0
  113. package/src/lib/components/kit/CardGroup.svelte +21 -0
  114. package/src/lib/components/kit/CodeGroup.svelte +65 -0
  115. package/src/lib/components/kit/Columns.svelte +26 -0
  116. package/src/lib/components/kit/Event.svelte +23 -0
  117. package/src/lib/components/kit/EventList.svelte +9 -0
  118. package/src/lib/components/kit/File.svelte +15 -0
  119. package/src/lib/components/kit/Folder.svelte +46 -0
  120. package/src/lib/components/kit/Frame.svelte +81 -0
  121. package/src/lib/components/kit/Icon.svelte +17 -0
  122. package/src/lib/components/kit/Kbd.svelte +11 -0
  123. package/src/lib/components/kit/Roadmap.svelte +15 -0
  124. package/src/lib/components/kit/RoadmapItem.svelte +109 -0
  125. package/src/lib/components/kit/Step.svelte +63 -0
  126. package/src/lib/components/kit/Steps.svelte +16 -0
  127. package/src/lib/components/kit/Tab.svelte +27 -0
  128. package/src/lib/components/kit/Tabs.svelte +75 -0
  129. package/src/lib/components/kit/Tooltip.svelte +33 -0
  130. package/src/lib/components/kit/Tree.svelte +11 -0
  131. package/src/lib/components/kit/TypeTable.svelte +187 -0
  132. package/src/lib/components/kit/Update.svelte +32 -0
  133. package/src/lib/components/kit/Video.svelte +64 -0
  134. package/src/lib/components/kit/accordion-context.ts +1 -0
  135. package/src/lib/components/kit/api/Api.svelte +80 -0
  136. package/src/lib/components/kit/api/ApiExamplePanel.svelte +100 -0
  137. package/src/lib/components/kit/api/ApiField.svelte +124 -0
  138. package/src/lib/components/kit/api/Channel.svelte +121 -0
  139. package/src/lib/components/kit/api/Endpoint.svelte +116 -0
  140. package/src/lib/components/kit/api/Enum.svelte +44 -0
  141. package/src/lib/components/kit/api/EnumValues.svelte +35 -0
  142. package/src/lib/components/kit/api/Expandable.svelte +70 -0
  143. package/src/lib/components/kit/api/Message.svelte +67 -0
  144. package/src/lib/components/kit/api/ObjectExample.svelte +11 -0
  145. package/src/lib/components/kit/api/RequestExample.svelte +11 -0
  146. package/src/lib/components/kit/api/ResponseExample.svelte +11 -0
  147. package/src/lib/components/kit/api/Webhook.svelte +115 -0
  148. package/src/lib/components/kit/api/api-context.ts +15 -0
  149. package/src/lib/components/kit/tabs-context.ts +8 -0
  150. package/src/lib/components/kit/tabs-store.svelte.ts +28 -0
  151. package/src/lib/config/site.ts +34 -0
  152. package/src/lib/content/index.ts +50 -0
  153. package/src/lib/content/raw.ts +21 -0
  154. package/src/lib/content/tree.ts +169 -0
  155. package/src/lib/index.ts +79 -0
  156. package/src/lib/nav-match.ts +23 -0
  157. package/src/lib/openapi/generate.ts +629 -0
  158. package/src/lib/server/og.ts +140 -0
  159. package/src/lib/state/search.svelte.ts +9 -0
  160. package/src/lib/state/theme.svelte.ts +58 -0
  161. package/src/lib/types.ts +216 -0
  162. package/src/params/docpage.ts +3 -0
  163. package/src/params/mdfile.ts +3 -0
  164. package/src/routes/+error.svelte +46 -0
  165. package/src/routes/+layout.svelte +25 -0
  166. package/src/routes/[...path=mdfile]/+server.ts +21 -0
  167. package/src/routes/[...slug=docpage]/+page.svelte +63 -0
  168. package/src/routes/[...slug=docpage]/+page.ts +44 -0
  169. package/src/routes/layout.css +897 -0
  170. package/src/routes/llms-full.txt/+server.ts +22 -0
  171. package/src/routes/llms.txt/+server.ts +20 -0
  172. package/src/routes/og/[...slug]/+server.ts +77 -0
  173. package/src/routes/rss.xml/+server.ts +65 -0
  174. package/src/routes/search.json/+server.ts +54 -0
  175. package/src/routes/sitemap.xml/+server.ts +21 -0
  176. package/static/favicon-dark.svg +6 -0
  177. package/static/favicon-light.svg +6 -0
  178. package/static/favicon.svg +14 -0
  179. package/static/fonts/geist-400.ttf +0 -0
  180. package/static/fonts/geist-600.ttf +0 -0
  181. package/static/fonts/geist-700.ttf +0 -0
  182. package/static/og-image.png +0 -0
  183. package/static/robots.txt +4 -0
  184. package/svelte.config.js +35 -0
  185. package/tsconfig.json +20 -0
  186. package/vite.config.ts +46 -0
@@ -0,0 +1,140 @@
1
+ import satori from 'satori';
2
+ import { Resvg } from '@resvg/resvg-js';
3
+ import type { OgConfig } from '$lib/types';
4
+
5
+ const WIDTH = 1200;
6
+ const HEIGHT = 630;
7
+
8
+ interface OgFont {
9
+ data: ArrayBuffer;
10
+ weight: 400 | 600 | 700;
11
+ }
12
+
13
+ interface RenderInput {
14
+ title: string;
15
+ description?: string;
16
+ siteName: string;
17
+ og?: OgConfig;
18
+ fonts: OgFont[];
19
+ logo?: string;
20
+ }
21
+
22
+ function clamp(value: string, max: number): string {
23
+ const trimmed = value.trim();
24
+ return trimmed.length > max ? `${trimmed.slice(0, max - 1).trimEnd()}…` : trimmed;
25
+ }
26
+
27
+ export async function renderOgImage({
28
+ title,
29
+ description,
30
+ siteName,
31
+ og,
32
+ fonts,
33
+ logo
34
+ }: RenderInput): Promise<Uint8Array> {
35
+ const background = og?.background ?? '#0a0a0a';
36
+ const foreground = og?.foreground ?? '#fafafa';
37
+ const muted = og?.muted ?? '#a1a1a1';
38
+ const accent = og?.accent ?? foreground;
39
+
40
+ const headline = clamp(title, 80);
41
+ const subtitle = description ? clamp(description, 140) : '';
42
+
43
+ const element = {
44
+ type: 'div',
45
+ props: {
46
+ style: {
47
+ width: '100%',
48
+ height: '100%',
49
+ display: 'flex',
50
+ flexDirection: 'column',
51
+ justifyContent: 'space-between',
52
+ padding: '80px',
53
+ backgroundColor: background,
54
+ color: foreground,
55
+ fontFamily: 'Geist'
56
+ },
57
+ children: [
58
+ {
59
+ type: 'div',
60
+ props: {
61
+ style: {
62
+ display: 'flex',
63
+ alignItems: 'center',
64
+ gap: '14px',
65
+ fontSize: '30px',
66
+ fontWeight: 600,
67
+ color: accent
68
+ },
69
+ children: [
70
+ logo
71
+ ? {
72
+ type: 'img',
73
+ props: { src: logo, width: 30, height: 32 }
74
+ }
75
+ : {
76
+ type: 'div',
77
+ props: {
78
+ style: {
79
+ width: '20px',
80
+ height: '20px',
81
+ borderRadius: '6px',
82
+ backgroundColor: accent
83
+ },
84
+ children: ''
85
+ }
86
+ },
87
+ siteName
88
+ ]
89
+ }
90
+ },
91
+ {
92
+ type: 'div',
93
+ props: {
94
+ style: { display: 'flex', flexDirection: 'column' },
95
+ children: [
96
+ {
97
+ type: 'div',
98
+ props: {
99
+ style: { display: 'flex', fontSize: '68px', fontWeight: 700, lineHeight: 1.1 },
100
+ children: headline
101
+ }
102
+ },
103
+ subtitle
104
+ ? {
105
+ type: 'div',
106
+ props: {
107
+ style: {
108
+ display: 'flex',
109
+ marginTop: '24px',
110
+ fontSize: '32px',
111
+ lineHeight: 1.3,
112
+ color: muted
113
+ },
114
+ children: subtitle
115
+ }
116
+ }
117
+ : null
118
+ ]
119
+ }
120
+ },
121
+ {
122
+ type: 'div',
123
+ props: {
124
+ style: { display: 'flex', fontSize: '26px', color: muted },
125
+ children: siteName.toLowerCase()
126
+ }
127
+ }
128
+ ]
129
+ }
130
+ };
131
+
132
+ const svg = await satori(element, {
133
+ width: WIDTH,
134
+ height: HEIGHT,
135
+ fonts: fonts.map((f) => ({ name: 'Geist', data: f.data, weight: f.weight, style: 'normal' }))
136
+ });
137
+
138
+ const resvg = new Resvg(svg, { fitTo: { mode: 'width', value: WIDTH } });
139
+ return resvg.render().asPng();
140
+ }
@@ -0,0 +1,9 @@
1
+ class SearchState {
2
+ open = $state(false);
3
+
4
+ toggle() {
5
+ this.open = !this.open;
6
+ }
7
+ }
8
+
9
+ export const searchState = new SearchState();
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Theme state
3
+ *
4
+ */
5
+ import { browser } from '$app/environment';
6
+
7
+ export type ThemePreference = 'light' | 'dark' | 'system';
8
+ export type ResolvedTheme = 'light' | 'dark';
9
+
10
+ const STORAGE_KEY = 'axerity-theme';
11
+
12
+ function systemTheme(): ResolvedTheme {
13
+ if (!browser) return 'light';
14
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
15
+ }
16
+
17
+ class ThemeState {
18
+ preference = $state<ThemePreference>('dark');
19
+ #system = $state<ResolvedTheme>('light');
20
+
21
+ resolved = $derived<ResolvedTheme>(this.preference === 'system' ? this.#system : this.preference);
22
+
23
+ constructor() {
24
+ if (!browser) return;
25
+
26
+ const stored = localStorage.getItem(STORAGE_KEY) as ThemePreference | null;
27
+ if (stored === 'light' || stored === 'dark' || stored === 'system') {
28
+ this.preference = stored;
29
+ }
30
+ this.#system = systemTheme();
31
+
32
+ const mq = window.matchMedia('(prefers-color-scheme: dark)');
33
+ mq.addEventListener('change', (e) => {
34
+ this.#system = e.matches ? 'dark' : 'light';
35
+ this.#apply();
36
+ });
37
+
38
+ this.#apply();
39
+ }
40
+
41
+ #apply() {
42
+ if (!browser) return;
43
+ const resolved = this.preference === 'system' ? this.#system : this.preference;
44
+ document.documentElement.classList.toggle('dark', resolved === 'dark');
45
+ }
46
+
47
+ set(preference: ThemePreference) {
48
+ this.preference = preference;
49
+ if (browser) localStorage.setItem(STORAGE_KEY, preference);
50
+ this.#apply();
51
+ }
52
+
53
+ toggle() {
54
+ this.set(this.resolved === 'dark' ? 'light' : 'dark');
55
+ }
56
+ }
57
+
58
+ export const theme = new ThemeState();
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Core data shapes for the docs engine.
3
+ *
4
+ */
5
+
6
+ /** A single navigable page in the sidebar. */
7
+ export interface NavLink {
8
+ title: string;
9
+ href: string;
10
+ /** Lucide icon name (see `$lib/icons`). */
11
+ icon?: string;
12
+ badge?: string;
13
+ /** Page description, used by prev/next navigation. */
14
+ description?: string;
15
+ /** HTTP method (API pages) — shown as a badge in the sidebar. */
16
+ method?: string;
17
+ }
18
+
19
+ /** A collapsible, nestable folder in the sidebar. */
20
+ export interface NavGroup {
21
+ title: string;
22
+ icon?: string;
23
+ items: NavEntry[];
24
+ /** Expand by default (otherwise collapsed unless it holds the active page). */
25
+ defaultOpen?: boolean;
26
+ }
27
+
28
+ /** Either a page link or a nested group. */
29
+ export type NavEntry = NavLink | NavGroup;
30
+
31
+ /** A top-level titled section in the sidebar (a top-level content folder). */
32
+ export interface NavSection {
33
+ title: string;
34
+ icon?: string;
35
+ items: NavEntry[];
36
+ }
37
+
38
+ /** A top-level entry in the global navbar. */
39
+ export interface TopNavLink {
40
+ title: string;
41
+ href: string;
42
+ icon?: string;
43
+ match?: string;
44
+ external?: boolean;
45
+ }
46
+
47
+ /** One heading captured for the on-page table of contents. */
48
+ export interface TocEntry {
49
+ id: string;
50
+ title: string;
51
+ /** Heading level, 2 = h2, 3 = h3 (h1 is the page title, excluded). */
52
+ depth: 2 | 3;
53
+ }
54
+
55
+ /**
56
+ * Page layout width.
57
+ * - `flat` — full-bleed; the sidebar sits flush at the left edge (default).
58
+ * - `boxed` — centered within a max-width container on wide screens.
59
+ */
60
+ export type LayoutVariant = 'flat' | 'boxed';
61
+
62
+ /**
63
+ * A built-in theme preset. Each is a cohesive palette (and where it matters,
64
+ * font + corner radius) inspired by a well-known product's docs.
65
+ */
66
+ export type ThemeName =
67
+ | 'neutral'
68
+ | 'stripe'
69
+ | 'vercel'
70
+ | 'linear'
71
+ | 'supabase'
72
+ | 'github'
73
+ | 'tailwind'
74
+ | 'mintlify'
75
+ | 'anthropic';
76
+
77
+ export interface Version {
78
+ label: string;
79
+ href: string;
80
+ }
81
+
82
+ export interface Dropdown {
83
+ label: string;
84
+ icon?: string;
85
+ href: string;
86
+ match?: string;
87
+ tabs?: TopNavLink[];
88
+ }
89
+
90
+ export interface Logo {
91
+ light?: string;
92
+ dark?: string;
93
+ alt?: string;
94
+ href?: string;
95
+ }
96
+
97
+ export interface SocialLink {
98
+ icon: string;
99
+ href: string;
100
+ label?: string;
101
+ }
102
+
103
+ export interface FooterConfig {
104
+ note?: string;
105
+ links?: { title: string; href: string }[];
106
+ }
107
+
108
+ export interface Banner {
109
+ text: string;
110
+ href?: string;
111
+ id?: string;
112
+ dismissible?: boolean;
113
+ }
114
+
115
+ /**
116
+ * Sidebar appearance.
117
+ * - `flush` — tinted panel flush to the edge with a divider (default).
118
+ * - `card` — a bordered, rounded panel inset from the edges.
119
+ * - `floating` — a detached, rounded, shadowed card that floats in the margin.
120
+ */
121
+ export interface SidebarConfig {
122
+ variant?: 'flush' | 'card' | 'floating';
123
+ }
124
+
125
+ export interface AnalyticsConfig {
126
+ plausible?: string;
127
+ googleAnalytics?: string;
128
+ }
129
+
130
+ /**
131
+ * Brand overrides. Set your own accent and corners on top of any `theme`
132
+ * preset. Colors take any CSS color value; `radius` takes a length like
133
+ * `0.5rem`. Applied with `!important` so they win over the preset.
134
+ */
135
+ export interface BrandConfig {
136
+ accent?: string;
137
+ accentDark?: string;
138
+ accentContrast?: string;
139
+ radius?: string;
140
+ }
141
+
142
+ export interface OpenApiConfig {
143
+ /** Local path or http(s) URL to an OpenAPI 3 spec (JSON or YAML). */
144
+ spec: string;
145
+ /** Content folder for this reference (its own group). Default `api-reference`. */
146
+ output?: string;
147
+ /** Section title (defaults to the spec's `info.title`). */
148
+ title?: string;
149
+ }
150
+
151
+ export interface OgConfig {
152
+ /** Generate a per-page OpenGraph image. Off uses the static `ogImage`. */
153
+ enabled?: boolean;
154
+ background?: string;
155
+ foreground?: string;
156
+ muted?: string;
157
+ accent?: string;
158
+ /** Logo shown in the card. Defaults to the site logo (dark variant). */
159
+ logo?: string;
160
+ }
161
+
162
+ /** Site-wide configuration (the future `docs.json` equivalent). */
163
+ export interface SiteConfig {
164
+ name: string;
165
+ tagline?: string;
166
+ description?: string;
167
+ url?: string;
168
+ ogImage?: string;
169
+ og?: OgConfig;
170
+ logo?: Logo;
171
+ topNav: TopNavLink[];
172
+ versions?: Version[];
173
+ dropdowns?: Dropdown[];
174
+ sidebarLinks?: { title: string; href: string }[];
175
+ social?: SocialLink[];
176
+ footer?: FooterConfig;
177
+ banner?: Banner;
178
+ analytics?: AnalyticsConfig;
179
+ brand?: BrandConfig;
180
+ openapi?: string | OpenApiConfig | OpenApiConfig[];
181
+ editLink?: string;
182
+ github?: string;
183
+ layout?: LayoutVariant;
184
+ theme?: ThemeName;
185
+ sidebar?: SidebarConfig;
186
+ /** Serve the whole site under a sub-path, e.g. "/docs". Empty = root. */
187
+ basePath?: string;
188
+ }
189
+
190
+ /**
191
+ * `meta.json`
192
+ *
193
+ */
194
+ export interface FolderMeta {
195
+ title: string;
196
+ icon?: string;
197
+ pages?: string[];
198
+ /** When this folder is a nested group, expand it by default in the sidebar. */
199
+ defaultOpen?: boolean;
200
+ }
201
+
202
+ /** Frontmatter on a markdown page. */
203
+ export interface PageFrontmatter {
204
+ title?: string;
205
+ description?: string;
206
+ icon?: string;
207
+ badge?: string;
208
+ /** `api` switches the page to the wide, TOC-less API reference layout. */
209
+ layout?: 'doc' | 'api';
210
+ /** HTTP method (API pages) — shown as a badge in the sidebar. */
211
+ method?: string;
212
+ /** Publish date (ISO); pages with a date appear in the RSS feed. */
213
+ date?: string;
214
+ /** Last-updated date (ISO), shown near the page footer. */
215
+ updated?: string;
216
+ }
@@ -0,0 +1,3 @@
1
+ import type { ParamMatcher } from '@sveltejs/kit';
2
+
3
+ export const match: ParamMatcher = (param) => !param.endsWith('.md');
@@ -0,0 +1,3 @@
1
+ import type { ParamMatcher } from '@sveltejs/kit';
2
+
3
+ export const match: ParamMatcher = (param) => param.endsWith('.md');
@@ -0,0 +1,46 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/state';
3
+ import { site } from '$lib/config/site';
4
+ import House from '@lucide/svelte/icons/house';
5
+ import BookOpen from '@lucide/svelte/icons/book-open';
6
+
7
+ const status = $derived(page.status);
8
+ const heading = $derived(status === 404 ? 'Page not found' : 'Something went wrong');
9
+ const message = $derived(
10
+ status === 404
11
+ ? "Sorry, we couldn't find the page you're looking for. It may have been moved or removed."
12
+ : (page.error?.message ?? 'An unexpected error occurred.')
13
+ );
14
+ </script>
15
+
16
+ <svelte:head>
17
+ <title>{status} · {site.name}</title>
18
+ </svelte:head>
19
+
20
+ <div class="flex min-h-screen flex-col items-center justify-center gap-4 bg-bg px-6 text-center">
21
+ <p
22
+ class="font-mono text-[5rem] leading-none font-bold text-border-strong select-none sm:text-[7rem]"
23
+ >
24
+ {status}
25
+ </p>
26
+
27
+ <h1 class="text-2xl font-bold tracking-tight text-fg sm:text-3xl">{heading}</h1>
28
+ <p class="max-w-md text-fg-muted">{message}</p>
29
+
30
+ <div class="mt-3 flex flex-wrap items-center justify-center gap-3">
31
+ <a
32
+ href="/"
33
+ class="inline-flex items-center gap-2 rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-contrast transition hover:opacity-90"
34
+ >
35
+ <BookOpen size={16} />
36
+ Back to docs
37
+ </a>
38
+ <a
39
+ href="/"
40
+ class="inline-flex items-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium text-fg-muted transition hover:border-border-strong hover:text-fg"
41
+ >
42
+ <House size={16} />
43
+ Home
44
+ </a>
45
+ </div>
46
+ </div>
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import '@fontsource-variable/geist/index.css';
3
+ import '@fontsource-variable/geist-mono/index.css';
4
+ import '@shikijs/twoslash/style-rich.css';
5
+ import './layout.css';
6
+ import { base } from '$app/paths';
7
+ import Analytics from '$lib/components/docs/Analytics.svelte';
8
+ import { site } from '$lib/config/site';
9
+
10
+ let { children } = $props();
11
+ </script>
12
+
13
+ <svelte:head>
14
+ <link rel="icon" href="{base}/favicon.svg" type="image/svg+xml" />
15
+ {#if site.url}
16
+ <link
17
+ rel="alternate"
18
+ type="application/rss+xml"
19
+ title="{site.name} changelog"
20
+ href="{site.url}{base}/rss.xml"
21
+ />
22
+ {/if}
23
+ </svelte:head>
24
+ <Analytics />
25
+ {@render children()}
@@ -0,0 +1,21 @@
1
+ import { error } from '@sveltejs/kit';
2
+ import { base } from '$app/paths';
3
+ import { flatPages } from '$lib/content';
4
+ import { getRawMarkdown } from '$lib/content/raw';
5
+ import type { EntryGenerator, RequestHandler } from './$types';
6
+
7
+ export const prerender = true;
8
+
9
+ export const entries: EntryGenerator = () =>
10
+ flatPages.map((page) => ({ path: page.href.slice(base.length).replace(/^\//, '') + '.md' }));
11
+
12
+ export const GET: RequestHandler = async ({ params }) => {
13
+ const slug = params.path.replace(/\.md$/, '');
14
+ const markdown = await getRawMarkdown(slug);
15
+
16
+ if (markdown == null) error(404, 'Not found');
17
+
18
+ return new Response(markdown, {
19
+ headers: { 'content-type': 'text/plain; charset=utf-8' }
20
+ });
21
+ };
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/state';
3
+ import { base } from '$app/paths';
4
+ import { DocsLayout } from '$lib';
5
+ import { site } from '$lib/config/site';
6
+ import { navFor, pathInVersion } from '$lib/content';
7
+ import type { PageProps } from './$types';
8
+
9
+ let { data }: PageProps = $props();
10
+
11
+ const nav = $derived(navFor(page.url.pathname));
12
+ const Content = $derived(data.component);
13
+ const fm = $derived(data.frontmatter);
14
+ const wide = $derived(fm.layout === 'api');
15
+
16
+ const editUrl = $derived(site.editLink ? `${site.editLink}/${data.sourcePath}` : undefined);
17
+
18
+ const pageTitle = $derived(fm.title ? `${fm.title} · ${site.name}` : site.name);
19
+ const pageDescription = $derived(fm.description ?? site.description ?? '');
20
+ const canonical = $derived(site.url ? site.url + page.url.pathname : undefined);
21
+ const ogImagePath = $derived(
22
+ site.og?.enabled
23
+ ? `${base}/og/${data.sourcePath.replace(/\.md$/, '')}.png`
24
+ : `${base}${site.ogImage ?? '/og-image.png'}`
25
+ );
26
+ const ogImage = $derived(site.url ? site.url + ogImagePath : ogImagePath);
27
+ </script>
28
+
29
+ <svelte:head>
30
+ <title>{pageTitle}</title>
31
+ {#if pageDescription}
32
+ <meta name="description" content={pageDescription} />
33
+ {/if}
34
+ {#if canonical}
35
+ <link rel="canonical" href={canonical} />
36
+ <meta property="og:url" content={canonical} />
37
+ {/if}
38
+ <meta property="og:type" content="article" />
39
+ <meta property="og:site_name" content={site.name} />
40
+ <meta property="og:title" content={fm.title ?? site.name} />
41
+ {#if pageDescription}
42
+ <meta property="og:description" content={pageDescription} />
43
+ {/if}
44
+ <meta property="og:image" content={ogImage} />
45
+ <meta name="twitter:card" content="summary_large_image" />
46
+ <meta name="twitter:title" content={fm.title ?? site.name} />
47
+ {#if pageDescription}
48
+ <meta name="twitter:description" content={pageDescription} />
49
+ {/if}
50
+ <meta name="twitter:image" content={ogImage} />
51
+ </svelte:head>
52
+
53
+ <DocsLayout
54
+ {site}
55
+ sidebar={nav.sidebar}
56
+ flatPages={nav.flatPages}
57
+ {wide}
58
+ {editUrl}
59
+ updated={fm.updated}
60
+ resolveVersion={pathInVersion}
61
+ >
62
+ <Content />
63
+ </DocsLayout>
@@ -0,0 +1,44 @@
1
+ import { error, redirect } from '@sveltejs/kit';
2
+ import { base } from '$app/paths';
3
+ import type { Component } from 'svelte';
4
+ import type { PageFrontmatter } from '$lib/types';
5
+ import { defaultVersionPath, versioned } from '$lib/content';
6
+ import type { EntryGenerator, PageLoad } from './$types';
7
+
8
+ const pages = import.meta.glob<{ default: Component; metadata: PageFrontmatter }>(
9
+ '/src/content/docs/**/*.md',
10
+ { eager: true }
11
+ );
12
+
13
+ const BASE = '/src/content/docs/';
14
+
15
+ function pathToSlug(path: string): string {
16
+ const rel = path.slice(BASE.length).replace(/\.md$/, '');
17
+ return rel === 'index' ? '' : rel.replace(/\/index$/, '');
18
+ }
19
+
20
+ export const prerender = true;
21
+
22
+ export const entries: EntryGenerator = () => {
23
+ const slugs = Object.keys(pages).map((path) => ({ slug: pathToSlug(path) }));
24
+ if (versioned) slugs.push({ slug: '' });
25
+ return slugs;
26
+ };
27
+
28
+ export const load: PageLoad = ({ params }) => {
29
+ const slug = params.slug;
30
+
31
+ if (!slug && versioned) redirect(307, `${base}${defaultVersionPath}`);
32
+
33
+ const candidates = slug ? [`${BASE}${slug}.md`, `${BASE}${slug}/index.md`] : [`${BASE}index.md`];
34
+ const path = candidates.find((p) => p in pages);
35
+
36
+ if (!path) error(404, 'Page not found');
37
+
38
+ const mod = pages[path];
39
+ return {
40
+ component: mod.default,
41
+ frontmatter: mod.metadata ?? {},
42
+ sourcePath: path.slice(BASE.length)
43
+ };
44
+ };