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,17 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="%sveltekit.assets%/favicon.svg" type="image/svg+xml" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&display=swap"
11
+ rel="stylesheet">
12
+ %sveltekit.head%
13
+ </head>
14
+ <body data-sveltekit-preload-data="hover" class="font-sans antialiased">
15
+ <div style="display: contents">%sveltekit.body%</div>
16
+ </body>
17
+ </html>
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SvelteKit param matcher for product slugs.
3
+ * Matches any string that does NOT look like a version (e.g., v1.0.0).
4
+ */
5
+ export function match(param: string): boolean {
6
+ return !/^v\d/.test(param);
7
+ }
@@ -0,0 +1,14 @@
1
+ import { getConfig, initConfig } from 'specra';
2
+ import specraConfig from '../../specra.config.json';
3
+ import type { LayoutServerLoad } from './$types';
4
+ import type { SpecraConfig } from 'specra';
5
+
6
+ initConfig(specraConfig as unknown as Partial<SpecraConfig>);
7
+
8
+ export const prerender = true;
9
+ export const trailingSlash = 'always';
10
+
11
+ export const load: LayoutServerLoad = async () => {
12
+ const config = getConfig();
13
+ return { config };
14
+ };
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import '../app.css';
3
+ import { LayoutProviders } from 'specra/components';
4
+ import type { Snippet } from 'svelte';
5
+ import type { LayoutData } from './$types';
6
+
7
+ let { data, children }: { data: LayoutData; children: Snippet } = $props();
8
+ </script>
9
+
10
+ <svelte:head>
11
+ <title>{data?.config?.site?.title || 'Documentation'}</title>
12
+ <meta name="description" content={data?.config?.site?.description || 'Modern documentation platform'} />
13
+ </svelte:head>
14
+
15
+ {#if data?.config}
16
+ <LayoutProviders config={data.config}>
17
+ {@render children?.()}
18
+ </LayoutProviders>
19
+ {:else}
20
+ {@render children?.()}
21
+ {/if}
@@ -0,0 +1,9 @@
1
+ import { redirect } from '@sveltejs/kit';
2
+ import { getConfig } from 'specra';
3
+ import type { PageServerLoad } from './$types';
4
+
5
+ export const load: PageServerLoad = async () => {
6
+ const config = getConfig();
7
+ const activeVersion = config.site?.activeVersion || 'v1.0.0';
8
+ redirect(302, `/docs/${activeVersion}/home`);
9
+ };
@@ -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
+ };
@@ -0,0 +1,106 @@
1
+ import {
2
+ extractTableOfContents,
3
+ getAdjacentDocs,
4
+ isCategoryPage,
5
+ getCachedAllDocs,
6
+ getCachedDocBySlug,
7
+ getI18nConfig,
8
+ } from 'specra';
9
+ import type { PageServerLoad } from './$types';
10
+
11
+ export const load: PageServerLoad = async ({ params }) => {
12
+ const { version, slug: slugArray } = params;
13
+ const slug = slugArray.replace(/\/$/, '');
14
+
15
+ const i18nConfig = getI18nConfig();
16
+ const slugParts = slug.split('/');
17
+ let locale: string | undefined;
18
+ if (i18nConfig && i18nConfig.locales.includes(slugParts[0])) {
19
+ locale = slugParts[0];
20
+ }
21
+
22
+ const allDocs = await getCachedAllDocs(version, locale);
23
+ const isCategory = isCategoryPage(slug, allDocs);
24
+ const doc = await getCachedDocBySlug(slug, version);
25
+
26
+ let title = 'Page Not Found';
27
+ let description = 'The requested documentation page could not be found.';
28
+ let ogUrl = `/docs/${version}/${slug}`;
29
+
30
+ if (doc) {
31
+ title = doc.meta.title || doc.title;
32
+ description = doc.meta.description || `Documentation for ${title}`;
33
+ }
34
+
35
+ // Category page without doc content
36
+ if (!doc && isCategory) {
37
+ const categoryDoc = allDocs.find((d) => d.slug.startsWith(slug + '/'));
38
+ const categoryTabGroup = categoryDoc?.meta?.tab_group || categoryDoc?.categoryTabGroup;
39
+ const categoryTitle = slug
40
+ .split('/')
41
+ .pop()
42
+ ?.replace(/-/g, ' ')
43
+ .replace(/\b\w/g, (l) => l.toUpperCase()) || 'Category';
44
+
45
+ return {
46
+ version,
47
+ slug,
48
+ isCategory: true,
49
+ isNotFound: false,
50
+ doc: null,
51
+ categoryTitle,
52
+ categoryDescription: 'Browse the documentation in this section.',
53
+ categoryTabGroup,
54
+ toc: [],
55
+ previous: null,
56
+ next: null,
57
+ title,
58
+ description,
59
+ ogUrl,
60
+ };
61
+ }
62
+
63
+ // Not found
64
+ if (!doc) {
65
+ return {
66
+ version,
67
+ slug,
68
+ isCategory: false,
69
+ isNotFound: true,
70
+ doc: null,
71
+ categoryTitle: null,
72
+ categoryDescription: null,
73
+ categoryTabGroup: undefined,
74
+ toc: [],
75
+ previous: null,
76
+ next: null,
77
+ title,
78
+ description,
79
+ ogUrl,
80
+ };
81
+ }
82
+
83
+ // Normal doc page
84
+ const toc = extractTableOfContents(doc.meta.content || doc.content);
85
+ const { previous, next } = getAdjacentDocs(slug, allDocs);
86
+ const showCategoryIndex = isCategory && !!doc;
87
+ const matchingDoc = allDocs.find((d) => d.slug === slug);
88
+ const currentPageTabGroup = doc.meta?.tab_group || matchingDoc?.categoryTabGroup;
89
+
90
+ return {
91
+ version,
92
+ slug,
93
+ isCategory: showCategoryIndex,
94
+ isNotFound: false,
95
+ doc,
96
+ categoryTitle: null,
97
+ categoryDescription: null,
98
+ categoryTabGroup: currentPageTabGroup,
99
+ toc,
100
+ previous: previous ? { title: previous.meta.title, slug: previous.slug } : null,
101
+ next: next ? { title: next.meta.title, slug: next.slug } : null,
102
+ title,
103
+ description,
104
+ ogUrl,
105
+ };
106
+ };