specra-cli 0.3.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 (172) hide show
  1. package/LICENSE.MD +33 -0
  2. package/README.md +246 -0
  3. package/dist/api-client-VHQARPDT.js +15 -0
  4. package/dist/api-client-VHQARPDT.js.map +1 -0
  5. package/dist/chunk-5765WX4D.js +192 -0
  6. package/dist/chunk-5765WX4D.js.map +1 -0
  7. package/dist/chunk-72RDEJR2.js +94 -0
  8. package/dist/chunk-72RDEJR2.js.map +1 -0
  9. package/dist/chunk-SQ2MMFUZ.js +102 -0
  10. package/dist/chunk-SQ2MMFUZ.js.map +1 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +242 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/deploy-V4JO2D6B.js +179 -0
  15. package/dist/deploy-V4JO2D6B.js.map +1 -0
  16. package/dist/doctor-ICALAJ4N.js +309 -0
  17. package/dist/doctor-ICALAJ4N.js.map +1 -0
  18. package/dist/login-UG3WU7DY.js +92 -0
  19. package/dist/login-UG3WU7DY.js.map +1 -0
  20. package/dist/logout-WJKHJZT6.js +24 -0
  21. package/dist/logout-WJKHJZT6.js.map +1 -0
  22. package/dist/logs-BLUJPWNO.js +77 -0
  23. package/dist/logs-BLUJPWNO.js.map +1 -0
  24. package/dist/projects-LJ57GK3D.js +49 -0
  25. package/dist/projects-LJ57GK3D.js.map +1 -0
  26. package/package.json +50 -0
  27. package/templates/book-docs/.env.sample +1 -0
  28. package/templates/book-docs/docs/v1.0.0/concepts.mdx +89 -0
  29. package/templates/book-docs/docs/v1.0.0/content/_category_.json +7 -0
  30. package/templates/book-docs/docs/v1.0.0/content/formatting.mdx +128 -0
  31. package/templates/book-docs/docs/v1.0.0/content/reusable-content.mdx +116 -0
  32. package/templates/book-docs/docs/v1.0.0/content/structure.mdx +92 -0
  33. package/templates/book-docs/docs/v1.0.0/customization/_category_.json +7 -0
  34. package/templates/book-docs/docs/v1.0.0/customization/branding.mdx +115 -0
  35. package/templates/book-docs/docs/v1.0.0/customization/themes.mdx +81 -0
  36. package/templates/book-docs/docs/v1.0.0/introduction.mdx +38 -0
  37. package/templates/book-docs/docs/v1.0.0/quickstart.mdx +112 -0
  38. package/templates/book-docs/docs/v2.0.0/concepts.mdx +89 -0
  39. package/templates/book-docs/docs/v2.0.0/content/_category_.json +7 -0
  40. package/templates/book-docs/docs/v2.0.0/content/formatting.mdx +128 -0
  41. package/templates/book-docs/docs/v2.0.0/content/reusable-content.mdx +116 -0
  42. package/templates/book-docs/docs/v2.0.0/content/structure.mdx +92 -0
  43. package/templates/book-docs/docs/v2.0.0/customization/_category_.json +7 -0
  44. package/templates/book-docs/docs/v2.0.0/customization/branding.mdx +115 -0
  45. package/templates/book-docs/docs/v2.0.0/customization/themes.mdx +81 -0
  46. package/templates/book-docs/docs/v2.0.0/introduction.mdx +39 -0
  47. package/templates/book-docs/docs/v2.0.0/quickstart.mdx +112 -0
  48. package/templates/book-docs/gitignore +7 -0
  49. package/templates/book-docs/package.json +28 -0
  50. package/templates/book-docs/postcss.config.mjs +8 -0
  51. package/templates/book-docs/public/api-specs/openapi-example.json +259 -0
  52. package/templates/book-docs/public/api-specs/postman-example.json +205 -0
  53. package/templates/book-docs/public/api-specs/test-api.json +256 -0
  54. package/templates/book-docs/public/api-specs/users-api.json +264 -0
  55. package/templates/book-docs/specra.config.json +77 -0
  56. package/templates/book-docs/src/app.css +86 -0
  57. package/templates/book-docs/src/app.html +17 -0
  58. package/templates/book-docs/src/params/product.ts +7 -0
  59. package/templates/book-docs/src/routes/+layout.server.ts +14 -0
  60. package/templates/book-docs/src/routes/+layout.svelte +21 -0
  61. package/templates/book-docs/src/routes/+page.server.ts +9 -0
  62. package/templates/book-docs/src/routes/docs/[product=product]/[version]/+layout.server.ts +40 -0
  63. package/templates/book-docs/src/routes/docs/[product=product]/[version]/+page.server.ts +24 -0
  64. package/templates/book-docs/src/routes/docs/[product=product]/[version]/[...slug]/+page.server.ts +131 -0
  65. package/templates/book-docs/src/routes/docs/[product=product]/[version]/[...slug]/+page.svelte +180 -0
  66. package/templates/book-docs/src/routes/docs/[version]/+layout.server.ts +42 -0
  67. package/templates/book-docs/src/routes/docs/[version]/+page.server.ts +27 -0
  68. package/templates/book-docs/src/routes/docs/[version]/[...slug]/+page.server.ts +106 -0
  69. package/templates/book-docs/src/routes/docs/[version]/[...slug]/+page.svelte +172 -0
  70. package/templates/book-docs/static/favicon.svg +4 -0
  71. package/templates/book-docs/svelte.config.js +13 -0
  72. package/templates/book-docs/tsconfig.json +12 -0
  73. package/templates/book-docs/vite.config.ts +6 -0
  74. package/templates/jbrains-docs/.env.sample +1 -0
  75. package/templates/jbrains-docs/docs/v1.0.0/advanced/_category_.json +8 -0
  76. package/templates/jbrains-docs/docs/v1.0.0/advanced/async.mdx +95 -0
  77. package/templates/jbrains-docs/docs/v1.0.0/advanced/generics.mdx +126 -0
  78. package/templates/jbrains-docs/docs/v1.0.0/basics/_category_.json +8 -0
  79. package/templates/jbrains-docs/docs/v1.0.0/basics/control-flow.mdx +106 -0
  80. package/templates/jbrains-docs/docs/v1.0.0/basics/syntax.mdx +129 -0
  81. package/templates/jbrains-docs/docs/v1.0.0/basics/types.mdx +135 -0
  82. package/templates/jbrains-docs/docs/v1.0.0/getting-started.mdx +111 -0
  83. package/templates/jbrains-docs/docs/v1.0.0/home.mdx +37 -0
  84. package/templates/jbrains-docs/docs/v1.0.0/tools/_category_.json +8 -0
  85. package/templates/jbrains-docs/docs/v1.0.0/tools/build-tools.mdx +165 -0
  86. package/templates/jbrains-docs/docs/v1.0.0/tools/testing.mdx +112 -0
  87. package/templates/jbrains-docs/docs/v2.0.0/advanced/_category_.json +8 -0
  88. package/templates/jbrains-docs/docs/v2.0.0/advanced/async.mdx +95 -0
  89. package/templates/jbrains-docs/docs/v2.0.0/advanced/generics.mdx +126 -0
  90. package/templates/jbrains-docs/docs/v2.0.0/basics/_category_.json +8 -0
  91. package/templates/jbrains-docs/docs/v2.0.0/basics/control-flow.mdx +106 -0
  92. package/templates/jbrains-docs/docs/v2.0.0/basics/syntax.mdx +129 -0
  93. package/templates/jbrains-docs/docs/v2.0.0/basics/types.mdx +135 -0
  94. package/templates/jbrains-docs/docs/v2.0.0/getting-started.mdx +111 -0
  95. package/templates/jbrains-docs/docs/v2.0.0/home.mdx +37 -0
  96. package/templates/jbrains-docs/docs/v2.0.0/tools/_category_.json +8 -0
  97. package/templates/jbrains-docs/docs/v2.0.0/tools/build-tools.mdx +165 -0
  98. package/templates/jbrains-docs/docs/v2.0.0/tools/testing.mdx +112 -0
  99. package/templates/jbrains-docs/gitignore +7 -0
  100. package/templates/jbrains-docs/package.json +28 -0
  101. package/templates/jbrains-docs/postcss.config.mjs +8 -0
  102. package/templates/jbrains-docs/public/api-specs/openapi-example.json +259 -0
  103. package/templates/jbrains-docs/public/api-specs/postman-example.json +205 -0
  104. package/templates/jbrains-docs/public/api-specs/test-api.json +256 -0
  105. package/templates/jbrains-docs/public/api-specs/users-api.json +264 -0
  106. package/templates/jbrains-docs/specra.config.json +80 -0
  107. package/templates/jbrains-docs/src/app.css +86 -0
  108. package/templates/jbrains-docs/src/app.html +17 -0
  109. package/templates/jbrains-docs/src/params/product.ts +7 -0
  110. package/templates/jbrains-docs/src/routes/+layout.server.ts +14 -0
  111. package/templates/jbrains-docs/src/routes/+layout.svelte +21 -0
  112. package/templates/jbrains-docs/src/routes/+page.server.ts +9 -0
  113. package/templates/jbrains-docs/src/routes/docs/[product=product]/[version]/+layout.server.ts +40 -0
  114. package/templates/jbrains-docs/src/routes/docs/[product=product]/[version]/+page.server.ts +24 -0
  115. package/templates/jbrains-docs/src/routes/docs/[product=product]/[version]/[...slug]/+page.server.ts +131 -0
  116. package/templates/jbrains-docs/src/routes/docs/[product=product]/[version]/[...slug]/+page.svelte +180 -0
  117. package/templates/jbrains-docs/src/routes/docs/[version]/+layout.server.ts +42 -0
  118. package/templates/jbrains-docs/src/routes/docs/[version]/+page.server.ts +27 -0
  119. package/templates/jbrains-docs/src/routes/docs/[version]/[...slug]/+page.server.ts +106 -0
  120. package/templates/jbrains-docs/src/routes/docs/[version]/[...slug]/+page.svelte +172 -0
  121. package/templates/jbrains-docs/static/favicon.svg +4 -0
  122. package/templates/jbrains-docs/svelte.config.js +13 -0
  123. package/templates/jbrains-docs/tsconfig.json +12 -0
  124. package/templates/jbrains-docs/vite.config.ts +6 -0
  125. package/templates/minimal/.env.sample +1 -0
  126. package/templates/minimal/docs/v1.0.0/about.mdx +57 -0
  127. package/templates/minimal/docs/v1.0.0/components/_category_.json +8 -0
  128. package/templates/minimal/docs/v1.0.0/components/callout.mdx +83 -0
  129. package/templates/minimal/docs/v1.0.0/components/code-block.mdx +103 -0
  130. package/templates/minimal/docs/v1.0.0/components/index.mdx +8 -0
  131. package/templates/minimal/docs/v1.0.0/components/tabs.mdx +92 -0
  132. package/templates/minimal/docs/v1.0.0/configuration.mdx +322 -0
  133. package/templates/minimal/docs/v1.0.0/features.mdx +197 -0
  134. package/templates/minimal/docs/v1.0.0/getting-started.mdx +183 -0
  135. package/templates/minimal/docs/v2.0.0/about.mdx +57 -0
  136. package/templates/minimal/docs/v2.0.0/components/_category_.json +8 -0
  137. package/templates/minimal/docs/v2.0.0/components/callout.mdx +83 -0
  138. package/templates/minimal/docs/v2.0.0/components/code-block.mdx +103 -0
  139. package/templates/minimal/docs/v2.0.0/components/index.mdx +8 -0
  140. package/templates/minimal/docs/v2.0.0/components/tabs.mdx +92 -0
  141. package/templates/minimal/docs/v2.0.0/configuration.mdx +322 -0
  142. package/templates/minimal/docs/v2.0.0/features.mdx +197 -0
  143. package/templates/minimal/docs/v2.0.0/getting-started.mdx +183 -0
  144. package/templates/minimal/gitignore +7 -0
  145. package/templates/minimal/package.json +29 -0
  146. package/templates/minimal/postcss.config.mjs +8 -0
  147. package/templates/minimal/specra.config.json +91 -0
  148. package/templates/minimal/src/app.css +86 -0
  149. package/templates/minimal/src/app.html +17 -0
  150. package/templates/minimal/src/hooks.server.ts +8 -0
  151. package/templates/minimal/src/params/product.ts +7 -0
  152. package/templates/minimal/src/routes/+error.svelte +10 -0
  153. package/templates/minimal/src/routes/+layout.server.ts +14 -0
  154. package/templates/minimal/src/routes/+layout.svelte +21 -0
  155. package/templates/minimal/src/routes/+page.server.ts +9 -0
  156. package/templates/minimal/src/routes/+page.svelte +149 -0
  157. package/templates/minimal/src/routes/docs/[product=product]/[version]/+layout.server.ts +40 -0
  158. package/templates/minimal/src/routes/docs/[product=product]/[version]/+page.server.ts +24 -0
  159. package/templates/minimal/src/routes/docs/[product=product]/[version]/[...slug]/+page.server.ts +131 -0
  160. package/templates/minimal/src/routes/docs/[product=product]/[version]/[...slug]/+page.svelte +180 -0
  161. package/templates/minimal/src/routes/docs/[version]/+layout.server.ts +42 -0
  162. package/templates/minimal/src/routes/docs/[version]/+page.server.ts +27 -0
  163. package/templates/minimal/src/routes/docs/[version]/[...slug]/+page.server.ts +106 -0
  164. package/templates/minimal/src/routes/docs/[version]/[...slug]/+page.svelte +172 -0
  165. package/templates/minimal/static/api-specs/openapi-example.json +259 -0
  166. package/templates/minimal/static/api-specs/postman-example.json +205 -0
  167. package/templates/minimal/static/api-specs/test-api.json +256 -0
  168. package/templates/minimal/static/api-specs/users-api.json +264 -0
  169. package/templates/minimal/static/favicon.svg +4 -0
  170. package/templates/minimal/svelte.config.js +13 -0
  171. package/templates/minimal/tsconfig.json +12 -0
  172. package/templates/minimal/vite.config.ts +6 -0
@@ -0,0 +1,149 @@
1
+ <script lang="ts">
2
+ import { ArrowRight, BookOpen, Zap, Code, Github, Twitter, Linkedin } from 'lucide-svelte';
3
+ import { Button, SiteBanner, Logo } from 'specra/components';
4
+
5
+ let { data } = $props();
6
+ const config = data.config;
7
+ const activeVersion = config.site.activeVersion || 'v1.0.0';
8
+ const docsUrl = `/docs/${activeVersion}/about`;
9
+ </script>
10
+
11
+ <svelte:head>
12
+ <title>{config.site.title}</title>
13
+ <meta name="description" content={config.site.description || 'Modern documentation platform'} />
14
+ </svelte:head>
15
+
16
+ <div class="min-h-screen bg-background">
17
+ <SiteBanner {config} />
18
+ <header class="border-b border-border">
19
+ <div class="container flex h-16 items-center justify-between px-6 mx-auto">
20
+ <a href="/" class="flex items-center gap-2">
21
+ <Logo logo={config.site.logo} alt={config.site.title} class="w-18 object-contain" />
22
+ <span class="font-semibold text-lg text-foreground">Specra</span>
23
+ </a>
24
+ <div class="flex items-center gap-6">
25
+ <a href={docsUrl} class="text-sm text-muted-foreground hover:text-foreground transition-colors">
26
+ Documentation
27
+ </a>
28
+ {#if config?.social?.github}
29
+ <a href={config.social.github} target="_blank" rel="noopener noreferrer" class="text-muted-foreground hover:text-foreground transition-colors">
30
+ <Github class="h-5 w-5" />
31
+ </a>
32
+ {/if}
33
+ <Button href={docsUrl}>Get Started</Button>
34
+ </div>
35
+ </div>
36
+ </header>
37
+
38
+ <main class="container px-6 mx-auto">
39
+ <!-- Hero Section -->
40
+ <div class="mx-auto text-center space-y-6 py-20 max-w-4xl">
41
+ <h1 class="text-5xl md:text-6xl font-bold tracking-tight text-foreground">
42
+ Beautiful documentation made <span class="text-primary">simple</span>
43
+ </h1>
44
+ <p class="text-xl md:text-2xl text-muted-foreground leading-relaxed max-w-3xl mx-auto">
45
+ The modern documentation framework that grows with your project
46
+ </p>
47
+ <p class="text-base md:text-lg text-muted-foreground leading-relaxed max-w-2xl mx-auto">
48
+ Built for developers who want powerful, flexible documentation without the complexity.
49
+ Write in MDX, configure with ease, and ship beautiful docs in minutes.
50
+ </p>
51
+ <div class="flex items-center justify-center gap-4 pt-4">
52
+ <Button href={docsUrl} size="lg">
53
+ Get Started
54
+ <ArrowRight class="ml-2 h-4 w-4" />
55
+ </Button>
56
+ {#if config?.social?.github}
57
+ <Button href={config.social.github} size="lg" variant="outline">
58
+ <Github class="mr-2 h-4 w-4" />
59
+ View on GitHub
60
+ </Button>
61
+ {/if}
62
+ </div>
63
+ </div>
64
+
65
+ <!-- Key Features -->
66
+ <div class="grid md:grid-cols-3 gap-6 py-16 max-w-6xl mx-auto">
67
+ <div class="p-6 rounded-lg border border-border bg-card hover:shadow-md transition-shadow">
68
+ <div class="h-12 w-12 rounded-lg bg-primary/10 flex items-center justify-center mb-4">
69
+ <BookOpen class="h-6 w-6 text-primary" />
70
+ </div>
71
+ <h3 class="text-lg font-semibold text-foreground mb-2">MDX-Powered</h3>
72
+ <p class="text-sm text-muted-foreground leading-relaxed">
73
+ Write your docs in MDX with support for custom components, code blocks with syntax highlighting, and rich interactive content.
74
+ </p>
75
+ </div>
76
+
77
+ <div class="p-6 rounded-lg border border-border bg-card hover:shadow-md transition-shadow">
78
+ <div class="h-12 w-12 rounded-lg bg-primary/10 flex items-center justify-center mb-4">
79
+ <Zap class="h-6 w-6 text-primary" />
80
+ </div>
81
+ <h3 class="text-lg font-semibold text-foreground mb-2">Lightning Fast</h3>
82
+ <p class="text-sm text-muted-foreground leading-relaxed">
83
+ Built on SvelteKit for optimal performance. Server-side rendering, instant page loads, and seamless navigation out of the box.
84
+ </p>
85
+ </div>
86
+
87
+ <div class="p-6 rounded-lg border border-border bg-card hover:shadow-md transition-shadow">
88
+ <div class="h-12 w-12 rounded-lg bg-primary/10 flex items-center justify-center mb-4">
89
+ <Code class="h-6 w-6 text-primary" />
90
+ </div>
91
+ <h3 class="text-lg font-semibold text-foreground mb-2">Developer First</h3>
92
+ <p class="text-sm text-muted-foreground leading-relaxed">
93
+ Version control friendly, Git-based workflow, and full TypeScript support. Your docs live alongside your code.
94
+ </p>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- CTA Section -->
99
+ <div class="py-16 max-w-4xl mx-auto">
100
+ <div class="rounded-xl border border-border bg-card p-8 md:p-12 text-center space-y-6">
101
+ <h2 class="text-3xl md:text-4xl font-bold text-foreground">Ready to Get Started?</h2>
102
+ <p class="text-muted-foreground max-w-2xl mx-auto">
103
+ Create beautiful, versioned documentation for your project in minutes.<br />
104
+ Specra gives you everything you need to write, organize, and publish great docs.
105
+ </p>
106
+ <div class="flex flex-col sm:flex-row items-center justify-center gap-4 pt-2">
107
+ <Button href={docsUrl} size="lg" variant="default">
108
+ Read the Docs
109
+ <ArrowRight class="ml-2 h-4 w-4" />
110
+ </Button>
111
+ {#if config?.social?.github}
112
+ <Button href={config.social.github} size="lg" variant="outline">
113
+ <Github class="mr-2 h-4 w-4" />
114
+ Star on GitHub
115
+ </Button>
116
+ {/if}
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Community Section -->
122
+ <div class="py-16 max-w-3xl mx-auto text-center space-y-6">
123
+ <h2 class="text-2xl md:text-3xl font-bold text-foreground">Join the Community</h2>
124
+ <p class="text-muted-foreground">
125
+ Specra is open source and community-driven. Connect with developers and stay updated.
126
+ </p>
127
+ <div class="flex items-center justify-center gap-4 pt-4">
128
+ {#if config?.social?.github}
129
+ <Button href={config.social.github} variant="outline" size="lg">
130
+ <Github class="mr-2 h-5 w-5" />
131
+ GitHub
132
+ </Button>
133
+ {/if}
134
+ {#if config?.social?.twitter}
135
+ <Button href={config.social.twitter} variant="outline" size="lg">
136
+ <Twitter class="mr-2 h-5 w-5" />
137
+ Twitter
138
+ </Button>
139
+ {/if}
140
+ {#if config?.social?.linkedin}
141
+ <Button href={config.social.linkedin} variant="outline" size="lg">
142
+ <Linkedin class="mr-2 h-5 w-5" />
143
+ LinkedIn
144
+ </Button>
145
+ {/if}
146
+ </div>
147
+ </div>
148
+ </main>
149
+ </div>
@@ -0,0 +1,40 @@
1
+ import { getCachedVersions, getCachedAllDocs, getEffectiveConfig, getI18nConfig, getVersionsMeta, getProducts, loadVersionConfig } from 'specra';
2
+ import { redirect } from '@sveltejs/kit';
3
+ import type { LayoutServerLoad } from './$types';
4
+
5
+ export const load: LayoutServerLoad = async ({ params }) => {
6
+ const { product, version } = params;
7
+
8
+ // Verify this is a valid product — if not, fall through to 404
9
+ const products = getProducts();
10
+ const matchedProduct = products.find(p => p.slug === product);
11
+ if (!matchedProduct) {
12
+ return {};
13
+ }
14
+
15
+ const i18nConfig = getI18nConfig();
16
+ const defaultLocale = i18nConfig?.defaultLocale || 'en';
17
+
18
+ // Block access to hidden versions — redirect to product's active version
19
+ const currentVersionConfig = loadVersionConfig(version, product);
20
+ if (currentVersionConfig?.hidden) {
21
+ const config = getEffectiveConfig(version, product);
22
+ const activeVersion = matchedProduct.config.activeVersion || config.site?.activeVersion || 'v1.0.0';
23
+ throw redirect(302, `/docs/${product}/${activeVersion}`);
24
+ }
25
+
26
+ const allDocs = await getCachedAllDocs(version, defaultLocale, product);
27
+ const versions = getCachedVersions(product);
28
+ const config = getEffectiveConfig(version, product);
29
+ const versionsMeta = getVersionsMeta(versions, product);
30
+
31
+ return {
32
+ allDocs,
33
+ versions,
34
+ versionsMeta,
35
+ config,
36
+ product,
37
+ products,
38
+ versionBanner: currentVersionConfig?.banner,
39
+ };
40
+ };
@@ -0,0 +1,24 @@
1
+ import { redirect } from '@sveltejs/kit';
2
+ import { getCachedAllDocs, getProducts } from 'specra';
3
+ import type { PageServerLoad } from './$types';
4
+
5
+ export const load: PageServerLoad = async ({ params }) => {
6
+ const { product, version } = params;
7
+
8
+ // Verify product exists
9
+ const products = getProducts();
10
+ const matchedProduct = products.find(p => p.slug === product);
11
+ if (!matchedProduct) {
12
+ return {};
13
+ }
14
+
15
+ const docs = await getCachedAllDocs(version, undefined, product);
16
+
17
+ if (docs.length === 0) {
18
+ const activeVersion = matchedProduct.config.activeVersion || 'v1.0.0';
19
+ redirect(302, `/docs/${product}/${activeVersion}`);
20
+ }
21
+
22
+ // Redirect to first doc in this product's version
23
+ redirect(302, `/docs/${product}/${version}/${docs[0].slug}`);
24
+ };
@@ -0,0 +1,131 @@
1
+ import {
2
+ extractTableOfContents,
3
+ getAdjacentDocs,
4
+ isCategoryPage,
5
+ getCachedAllDocs,
6
+ getCachedDocBySlug,
7
+ getI18nConfig,
8
+ getProducts,
9
+ } from 'specra';
10
+ import type { PageServerLoad } from './$types';
11
+
12
+ export const load: PageServerLoad = async ({ params }) => {
13
+ const { product, version, slug: slugArray } = params;
14
+ const slug = slugArray.replace(/\/$/, '');
15
+
16
+ // Verify product exists
17
+ const products = getProducts();
18
+ const matchedProduct = products.find(p => p.slug === product);
19
+ if (!matchedProduct) {
20
+ return {
21
+ version,
22
+ slug,
23
+ product,
24
+ isCategory: false,
25
+ isNotFound: true,
26
+ doc: null,
27
+ categoryTitle: null,
28
+ categoryDescription: null,
29
+ categoryTabGroup: undefined,
30
+ toc: [],
31
+ previous: null,
32
+ next: null,
33
+ title: 'Page Not Found',
34
+ description: 'The requested documentation page could not be found.',
35
+ ogUrl: `/docs/${product}/${version}/${slug}`,
36
+ };
37
+ }
38
+
39
+ const i18nConfig = getI18nConfig();
40
+ const slugParts = slug.split('/');
41
+ let locale: string | undefined;
42
+ if (i18nConfig && i18nConfig.locales.includes(slugParts[0])) {
43
+ locale = slugParts[0];
44
+ }
45
+
46
+ const allDocs = await getCachedAllDocs(version, locale, product);
47
+ const isCategory = isCategoryPage(slug, allDocs);
48
+ const doc = await getCachedDocBySlug(slug, version, product);
49
+ const urlPrefix = `/docs/${product}/${version}`;
50
+
51
+ let title = 'Page Not Found';
52
+ let description = 'The requested documentation page could not be found.';
53
+ let ogUrl = `${urlPrefix}/${slug}`;
54
+
55
+ if (doc) {
56
+ title = doc.meta.title || doc.title;
57
+ description = doc.meta.description || `Documentation for ${title}`;
58
+ }
59
+
60
+ if (!doc && isCategory) {
61
+ const categoryDoc = allDocs.find((d) => d.slug.startsWith(slug + '/'));
62
+ const categoryTabGroup = categoryDoc?.meta?.tab_group || categoryDoc?.categoryTabGroup;
63
+ const categoryTitle = slug
64
+ .split('/')
65
+ .pop()
66
+ ?.replace(/-/g, ' ')
67
+ .replace(/\b\w/g, (l) => l.toUpperCase()) || 'Category';
68
+
69
+ return {
70
+ version,
71
+ slug,
72
+ product,
73
+ isCategory: true,
74
+ isNotFound: false,
75
+ doc: null,
76
+ categoryTitle,
77
+ categoryDescription: 'Browse the documentation in this section.',
78
+ categoryTabGroup,
79
+ toc: [],
80
+ previous: null,
81
+ next: null,
82
+ title,
83
+ description,
84
+ ogUrl,
85
+ };
86
+ }
87
+
88
+ if (!doc) {
89
+ return {
90
+ version,
91
+ slug,
92
+ product,
93
+ isCategory: false,
94
+ isNotFound: true,
95
+ doc: null,
96
+ categoryTitle: null,
97
+ categoryDescription: null,
98
+ categoryTabGroup: undefined,
99
+ toc: [],
100
+ previous: null,
101
+ next: null,
102
+ title,
103
+ description,
104
+ ogUrl,
105
+ };
106
+ }
107
+
108
+ const toc = extractTableOfContents(doc.meta.content || doc.content);
109
+ const { previous, next } = getAdjacentDocs(slug, allDocs);
110
+ const showCategoryIndex = isCategory && !!doc;
111
+ const matchingDoc = allDocs.find((d) => d.slug === slug);
112
+ const currentPageTabGroup = doc.meta?.tab_group || matchingDoc?.categoryTabGroup;
113
+
114
+ return {
115
+ version,
116
+ slug,
117
+ product,
118
+ isCategory: showCategoryIndex,
119
+ isNotFound: false,
120
+ doc,
121
+ categoryTitle: null,
122
+ categoryDescription: null,
123
+ categoryTabGroup: currentPageTabGroup,
124
+ toc,
125
+ previous: previous ? { title: previous.meta.title, slug: previous.slug } : null,
126
+ next: next ? { title: next.meta.title, slug: next.slug } : null,
127
+ title,
128
+ description,
129
+ ogUrl,
130
+ };
131
+ };
@@ -0,0 +1,180 @@
1
+ <script lang="ts">
2
+ import {
3
+ TableOfContents,
4
+ Header,
5
+ TabGroups,
6
+ DocLayout,
7
+ CategoryIndex,
8
+ HotReloadIndicator,
9
+ DevModeBadge,
10
+ MdxHotReload,
11
+ MdxContent,
12
+ NotFoundContent,
13
+ SearchHighlight,
14
+ MobileDocLayout,
15
+ mdxComponents,
16
+ } from 'specra/components';
17
+ import type { PageData } from './$types';
18
+
19
+ let { data }: { data: PageData } = $props();
20
+
21
+ let allDocsCompat: any[] = $derived(data.allDocs);
22
+ let previousDoc = $derived(data.previous ?? undefined);
23
+ let nextDoc = $derived(data.next ?? undefined);
24
+ let categoryTitle = $derived(data.categoryTitle ?? undefined);
25
+ let categoryDescription = $derived(data.categoryDescription ?? undefined);
26
+ </script>
27
+
28
+ <svelte:head>
29
+ <title>{data.title}</title>
30
+ <meta name="description" content={data.description} />
31
+ <meta property="og:title" content={data.title} />
32
+ <meta property="og:description" content={data.description} />
33
+ <meta property="og:url" content={data.ogUrl} />
34
+ <meta property="og:site_name" content="Documentation Platform" />
35
+ <meta property="og:type" content="article" />
36
+ <meta property="og:locale" content="en_US" />
37
+ <meta name="twitter:card" content="summary_large_image" />
38
+ <meta name="twitter:title" content={data.title} />
39
+ <meta name="twitter:description" content={data.description} />
40
+ <link rel="canonical" href={data.ogUrl} />
41
+ </svelte:head>
42
+
43
+ {#if !data.doc && data.isCategory}
44
+ <!-- Category page without doc content -->
45
+ <MobileDocLayout
46
+ docs={allDocsCompat}
47
+ version={data.version}
48
+ product={data.product}
49
+ config={data.config}
50
+ activeTabGroup={data.categoryTabGroup}
51
+ >
52
+ {#snippet header()}
53
+ <Header currentVersion={data.version} versions={data.versions} versionsMeta={data.versionsMeta} versionBanner={data.versionBanner} config={data.config} products={data.products} currentProduct={data.product}>
54
+ {#snippet subheader()}
55
+ {#if data.config.navigation?.tabGroups && data.config.navigation.tabGroups.length > 0}
56
+ <TabGroups
57
+ tabGroups={data.config.navigation.tabGroups}
58
+ activeTabId={data.categoryTabGroup}
59
+ docs={allDocsCompat}
60
+ version={data.version}
61
+ product={data.product}
62
+ flush={data.config.navigation?.sidebarStyle === 'flush'}
63
+ />
64
+ {/if}
65
+ {/snippet}
66
+ </Header>
67
+ {/snippet}
68
+ <CategoryIndex
69
+ categoryPath={data.slug}
70
+ version={data.version}
71
+ product={data.product}
72
+ allDocs={allDocsCompat}
73
+ title={categoryTitle}
74
+ description={categoryDescription}
75
+ config={data.config}
76
+ />
77
+ </MobileDocLayout>
78
+ <MdxHotReload />
79
+ <HotReloadIndicator />
80
+ <DevModeBadge />
81
+ {:else if data.isNotFound}
82
+ <!-- Not found -->
83
+ <MobileDocLayout
84
+ docs={allDocsCompat}
85
+ version={data.version}
86
+ product={data.product}
87
+ config={data.config}
88
+ >
89
+ {#snippet header()}
90
+ <Header currentVersion={data.version} versions={data.versions} versionsMeta={data.versionsMeta} versionBanner={data.versionBanner} config={data.config} products={data.products} currentProduct={data.product}>
91
+ {#snippet subheader()}
92
+ {#if data.config.navigation?.tabGroups && data.config.navigation.tabGroups.length > 0}
93
+ <TabGroups
94
+ tabGroups={data.config.navigation.tabGroups}
95
+ activeTabId={data.categoryTabGroup}
96
+ docs={allDocsCompat}
97
+ version={data.version}
98
+ product={data.product}
99
+ flush={data.config.navigation?.sidebarStyle === 'flush'}
100
+ />
101
+ {/if}
102
+ {/snippet}
103
+ </Header>
104
+ {/snippet}
105
+ <NotFoundContent version={data.version} />
106
+ </MobileDocLayout>
107
+ <MdxHotReload />
108
+ <HotReloadIndicator />
109
+ <DevModeBadge />
110
+ {:else if data.doc}
111
+ <!-- Normal doc or category with doc content -->
112
+ <MobileDocLayout
113
+ docs={allDocsCompat}
114
+ version={data.version}
115
+ product={data.product}
116
+ config={data.config}
117
+ activeTabGroup={data.categoryTabGroup}
118
+ >
119
+ {#snippet header()}
120
+ <Header currentVersion={data.version} versions={data.versions} versionsMeta={data.versionsMeta} versionBanner={data.versionBanner} config={data.config} products={data.products} currentProduct={data.product}>
121
+ {#snippet subheader()}
122
+ {#if data.config.navigation?.tabGroups && data.config.navigation.tabGroups.length > 0}
123
+ <TabGroups
124
+ tabGroups={data.config.navigation.tabGroups}
125
+ activeTabId={data.categoryTabGroup}
126
+ docs={allDocsCompat}
127
+ version={data.version}
128
+ product={data.product}
129
+ flush={data.config.navigation?.sidebarStyle === 'flush'}
130
+ />
131
+ {/if}
132
+ {/snippet}
133
+ </Header>
134
+ {/snippet}
135
+ {#snippet toc()}
136
+ {#if !data.isCategory}
137
+ <TableOfContents items={data.toc} config={data.config} />
138
+ {/if}
139
+ {/snippet}
140
+
141
+ {#if data.isCategory}
142
+ {#snippet categoryContent()}
143
+ {#if data.doc?.contentNodes}
144
+ <MdxContent nodes={data.doc.contentNodes} components={mdxComponents} />
145
+ {:else if data.doc?.content}
146
+ {@html data.doc.content}
147
+ {/if}
148
+ {/snippet}
149
+ <CategoryIndex
150
+ categoryPath={data.slug}
151
+ version={data.version}
152
+ allDocs={allDocsCompat}
153
+ title={data.doc.meta.title}
154
+ description={data.doc.meta.description}
155
+ content={categoryContent}
156
+ config={data.config}
157
+ />
158
+ {:else}
159
+ <SearchHighlight />
160
+ <DocLayout
161
+ meta={data.doc.meta}
162
+ previousDoc={previousDoc}
163
+ nextDoc={nextDoc}
164
+ version={data.version}
165
+ slug={data.slug}
166
+ product={data.product}
167
+ config={data.config}
168
+ >
169
+ {#if data.doc.contentNodes}
170
+ <MdxContent nodes={data.doc.contentNodes} components={mdxComponents} />
171
+ {:else}
172
+ {@html data.doc.content}
173
+ {/if}
174
+ </DocLayout>
175
+ {/if}
176
+ </MobileDocLayout>
177
+ <MdxHotReload />
178
+ <HotReloadIndicator />
179
+ <DevModeBadge />
180
+ {/if}
@@ -0,0 +1,42 @@
1
+ import { getCachedVersions, getCachedAllDocs, getEffectiveConfig, getI18nConfig, getVersionsMeta, getProducts, loadVersionConfig } from 'specra';
2
+ import { redirect } from '@sveltejs/kit';
3
+ import type { LayoutServerLoad } from './$types';
4
+
5
+ export const load: LayoutServerLoad = async ({ params }) => {
6
+ const { version } = params;
7
+
8
+ // Route disambiguation: if this "version" is actually a product slug,
9
+ // the +page.server.ts will handle the redirect. The layout still needs
10
+ // to return data for the version case.
11
+ const products = getProducts();
12
+ const isProduct = products.some(p => p.slug === version);
13
+ if (isProduct) {
14
+ // Return minimal data — the page will redirect before rendering
15
+ return { allDocs: [], versions: [], versionsMeta: [], config: getEffectiveConfig(''), products };
16
+ }
17
+
18
+ const i18nConfig = getI18nConfig();
19
+ const defaultLocale = i18nConfig?.defaultLocale || 'en';
20
+
21
+ // Block access to hidden versions — redirect to active version
22
+ const currentVersionConfig = loadVersionConfig(version);
23
+ if (currentVersionConfig?.hidden) {
24
+ const config = getEffectiveConfig(version);
25
+ const activeVersion = config.site?.activeVersion || 'v1.0.0';
26
+ throw redirect(302, `/docs/${activeVersion}`);
27
+ }
28
+
29
+ const allDocs = await getCachedAllDocs(version, defaultLocale);
30
+ const versions = getCachedVersions();
31
+ const config = getEffectiveConfig(version);
32
+ const versionsMeta = getVersionsMeta(versions);
33
+
34
+ return {
35
+ allDocs,
36
+ versions,
37
+ versionsMeta,
38
+ config,
39
+ products,
40
+ versionBanner: currentVersionConfig?.banner,
41
+ };
42
+ };
@@ -0,0 +1,27 @@
1
+ import { redirect } from '@sveltejs/kit';
2
+ import { getCachedVersions, getCachedAllDocs, getProducts, getEffectiveConfig } from 'specra';
3
+ import type { PageServerLoad } from './$types';
4
+
5
+ export const load: PageServerLoad = async ({ params }) => {
6
+ const { version } = params;
7
+
8
+ // Route disambiguation: check if this "version" is actually a product slug
9
+ const products = getProducts();
10
+ const matchedProduct = products.find(p => p.slug === version);
11
+ if (matchedProduct) {
12
+ // This is /docs/{product} — redirect to the product's active version
13
+ const config = getEffectiveConfig('', version);
14
+ const activeVersion = matchedProduct.config.activeVersion || config.site?.activeVersion || 'v1.0.0';
15
+ redirect(302, `/docs/${version}/${activeVersion}`);
16
+ }
17
+
18
+ // Standard version route
19
+ const docs = await getCachedAllDocs(version);
20
+
21
+ if (docs.length === 0) {
22
+ redirect(302, '/docs/v1.0.0');
23
+ }
24
+
25
+ // Redirect to first doc
26
+ redirect(302, `/docs/${version}/${docs[0].slug}`);
27
+ };