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,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
+ };
@@ -0,0 +1,172 @@
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
+ config={data.config}
49
+ activeTabGroup={data.categoryTabGroup}
50
+ >
51
+ {#snippet header()}
52
+ <Header currentVersion={data.version} versions={data.versions} versionsMeta={data.versionsMeta} versionBanner={data.versionBanner} config={data.config} products={data.products}>
53
+ {#snippet subheader()}
54
+ {#if data.config.navigation?.tabGroups && data.config.navigation.tabGroups.length > 0}
55
+ <TabGroups
56
+ tabGroups={data.config.navigation.tabGroups}
57
+ activeTabId={data.categoryTabGroup}
58
+ docs={allDocsCompat}
59
+ version={data.version}
60
+ flush={data.config.navigation?.sidebarStyle === 'flush'}
61
+ />
62
+ {/if}
63
+ {/snippet}
64
+ </Header>
65
+ {/snippet}
66
+ <CategoryIndex
67
+ categoryPath={data.slug}
68
+ version={data.version}
69
+ allDocs={allDocsCompat}
70
+ title={categoryTitle}
71
+ description={categoryDescription}
72
+ config={data.config}
73
+ />
74
+ </MobileDocLayout>
75
+ <MdxHotReload />
76
+ <HotReloadIndicator />
77
+ <DevModeBadge />
78
+ {:else if data.isNotFound}
79
+ <!-- Not found -->
80
+ <MobileDocLayout
81
+ docs={allDocsCompat}
82
+ version={data.version}
83
+ config={data.config}
84
+ >
85
+ {#snippet header()}
86
+ <Header currentVersion={data.version} versions={data.versions} versionsMeta={data.versionsMeta} versionBanner={data.versionBanner} config={data.config} products={data.products}>
87
+ {#snippet subheader()}
88
+ {#if data.config.navigation?.tabGroups && data.config.navigation.tabGroups.length > 0}
89
+ <TabGroups
90
+ tabGroups={data.config.navigation.tabGroups}
91
+ activeTabId={data.categoryTabGroup}
92
+ docs={allDocsCompat}
93
+ version={data.version}
94
+ flush={data.config.navigation?.sidebarStyle === 'flush'}
95
+ />
96
+ {/if}
97
+ {/snippet}
98
+ </Header>
99
+ {/snippet}
100
+ <NotFoundContent version={data.version} />
101
+ </MobileDocLayout>
102
+ <MdxHotReload />
103
+ <HotReloadIndicator />
104
+ <DevModeBadge />
105
+ {:else if data.doc}
106
+ <!-- Normal doc or category with doc content -->
107
+ <MobileDocLayout
108
+ docs={allDocsCompat}
109
+ version={data.version}
110
+ config={data.config}
111
+ activeTabGroup={data.categoryTabGroup}
112
+ >
113
+ {#snippet header()}
114
+ <Header currentVersion={data.version} versions={data.versions} versionsMeta={data.versionsMeta} versionBanner={data.versionBanner} config={data.config} products={data.products}>
115
+ {#snippet subheader()}
116
+ {#if data.config.navigation?.tabGroups && data.config.navigation.tabGroups.length > 0}
117
+ <TabGroups
118
+ tabGroups={data.config.navigation.tabGroups}
119
+ activeTabId={data.categoryTabGroup}
120
+ docs={allDocsCompat}
121
+ version={data.version}
122
+ flush={data.config.navigation?.sidebarStyle === 'flush'}
123
+ />
124
+ {/if}
125
+ {/snippet}
126
+ </Header>
127
+ {/snippet}
128
+ {#snippet toc()}
129
+ {#if !data.isCategory}
130
+ <TableOfContents items={data.toc} config={data.config} />
131
+ {/if}
132
+ {/snippet}
133
+
134
+ {#if data.isCategory}
135
+ {#snippet categoryContent()}
136
+ {#if data.doc?.contentNodes}
137
+ <MdxContent nodes={data.doc.contentNodes} components={mdxComponents} />
138
+ {:else if data.doc?.content}
139
+ {@html data.doc.content}
140
+ {/if}
141
+ {/snippet}
142
+ <CategoryIndex
143
+ categoryPath={data.slug}
144
+ version={data.version}
145
+ allDocs={allDocsCompat}
146
+ title={data.doc.meta.title}
147
+ description={data.doc.meta.description}
148
+ content={categoryContent}
149
+ config={data.config}
150
+ />
151
+ {:else}
152
+ <SearchHighlight />
153
+ <DocLayout
154
+ meta={data.doc.meta}
155
+ previousDoc={previousDoc}
156
+ nextDoc={nextDoc}
157
+ version={data.version}
158
+ slug={data.slug}
159
+ config={data.config}
160
+ >
161
+ {#if data.doc.contentNodes}
162
+ <MdxContent nodes={data.doc.contentNodes} components={mdxComponents} />
163
+ {:else}
164
+ {@html data.doc.content}
165
+ {/if}
166
+ </DocLayout>
167
+ {/if}
168
+ </MobileDocLayout>
169
+ <MdxHotReload />
170
+ <HotReloadIndicator />
171
+ <DevModeBadge />
172
+ {/if}
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="6" fill="#6366f1"/>
3
+ <text x="16" y="23" font-family="system-ui,sans-serif" font-size="20" font-weight="bold" fill="white" text-anchor="middle">S</text>
4
+ </svg>
@@ -0,0 +1,13 @@
1
+ import adapter from '@sveltejs/adapter-static';
2
+ import { specraConfig } from 'specra/svelte-config';
3
+ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
4
+
5
+ const config = specraConfig({
6
+ vitePreprocess: { vitePreprocess },
7
+ kit: {
8
+ adapter: adapter(),
9
+ prerender: { handleHttpError: 'warn', handleMissingId: 'warn', handleUnseenRoutes: 'warn' }
10
+ }
11
+ });
12
+
13
+ export default config;
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./.svelte-kit/tsconfig.json",
3
+ "compilerOptions": {
4
+ "allowJs": true,
5
+ "checkJs": true,
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "resolveJsonModule": true,
9
+ "skipLibCheck": true,
10
+ "strict": true
11
+ }
12
+ }