jamdesk 1.0.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 (435) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +323 -0
  3. package/bin/jamdesk.js +76 -0
  4. package/dist/__tests__/integration/deprecated-components.integration.test.d.ts +8 -0
  5. package/dist/__tests__/integration/deprecated-components.integration.test.d.ts.map +1 -0
  6. package/dist/__tests__/integration/deprecated-components.integration.test.js +165 -0
  7. package/dist/__tests__/integration/deprecated-components.integration.test.js.map +1 -0
  8. package/dist/__tests__/integration/migrate.integration.test.d.ts +2 -0
  9. package/dist/__tests__/integration/migrate.integration.test.d.ts.map +1 -0
  10. package/dist/__tests__/integration/migrate.integration.test.js +64 -0
  11. package/dist/__tests__/integration/migrate.integration.test.js.map +1 -0
  12. package/dist/__tests__/integration/prepublish.integration.test.d.ts +2 -0
  13. package/dist/__tests__/integration/prepublish.integration.test.d.ts.map +1 -0
  14. package/dist/__tests__/integration/prepublish.integration.test.js +27 -0
  15. package/dist/__tests__/integration/prepublish.integration.test.js.map +1 -0
  16. package/dist/__tests__/integration/validate.integration.test.d.ts +2 -0
  17. package/dist/__tests__/integration/validate.integration.test.d.ts.map +1 -0
  18. package/dist/__tests__/integration/validate.integration.test.js +56 -0
  19. package/dist/__tests__/integration/validate.integration.test.js.map +1 -0
  20. package/dist/__tests__/unit/deploy-templates.test.d.ts +2 -0
  21. package/dist/__tests__/unit/deploy-templates.test.d.ts.map +1 -0
  22. package/dist/__tests__/unit/deploy-templates.test.js +124 -0
  23. package/dist/__tests__/unit/deploy-templates.test.js.map +1 -0
  24. package/dist/__tests__/unit/deprecated-components-sync.test.d.ts +2 -0
  25. package/dist/__tests__/unit/deprecated-components-sync.test.d.ts.map +1 -0
  26. package/dist/__tests__/unit/deprecated-components-sync.test.js +69 -0
  27. package/dist/__tests__/unit/deprecated-components-sync.test.js.map +1 -0
  28. package/dist/__tests__/unit/deps-sync.test.d.ts +14 -0
  29. package/dist/__tests__/unit/deps-sync.test.d.ts.map +1 -0
  30. package/dist/__tests__/unit/deps-sync.test.js +166 -0
  31. package/dist/__tests__/unit/deps-sync.test.js.map +1 -0
  32. package/dist/__tests__/unit/docs-config.test.d.ts +2 -0
  33. package/dist/__tests__/unit/docs-config.test.d.ts.map +1 -0
  34. package/dist/__tests__/unit/docs-config.test.js +288 -0
  35. package/dist/__tests__/unit/docs-config.test.js.map +1 -0
  36. package/dist/__tests__/unit/errors.test.d.ts +2 -0
  37. package/dist/__tests__/unit/errors.test.d.ts.map +1 -0
  38. package/dist/__tests__/unit/errors.test.js +27 -0
  39. package/dist/__tests__/unit/errors.test.js.map +1 -0
  40. package/dist/__tests__/unit/extract-hooks.test.d.ts +5 -0
  41. package/dist/__tests__/unit/extract-hooks.test.d.ts.map +1 -0
  42. package/dist/__tests__/unit/extract-hooks.test.js +205 -0
  43. package/dist/__tests__/unit/extract-hooks.test.js.map +1 -0
  44. package/dist/__tests__/unit/frontmatter-sync.test.d.ts +8 -0
  45. package/dist/__tests__/unit/frontmatter-sync.test.d.ts.map +1 -0
  46. package/dist/__tests__/unit/frontmatter-sync.test.js +26 -0
  47. package/dist/__tests__/unit/frontmatter-sync.test.js.map +1 -0
  48. package/dist/__tests__/unit/mdx-validator.test.d.ts +2 -0
  49. package/dist/__tests__/unit/mdx-validator.test.d.ts.map +1 -0
  50. package/dist/__tests__/unit/mdx-validator.test.js +264 -0
  51. package/dist/__tests__/unit/mdx-validator.test.js.map +1 -0
  52. package/dist/__tests__/unit/migrate-convert.test.d.ts +2 -0
  53. package/dist/__tests__/unit/migrate-convert.test.d.ts.map +1 -0
  54. package/dist/__tests__/unit/migrate-convert.test.js +297 -0
  55. package/dist/__tests__/unit/migrate-convert.test.js.map +1 -0
  56. package/dist/__tests__/unit/migrate-detect.test.d.ts +2 -0
  57. package/dist/__tests__/unit/migrate-detect.test.d.ts.map +1 -0
  58. package/dist/__tests__/unit/migrate-detect.test.js +35 -0
  59. package/dist/__tests__/unit/migrate-detect.test.js.map +1 -0
  60. package/dist/__tests__/unit/migrate-mdx.test.d.ts +2 -0
  61. package/dist/__tests__/unit/migrate-mdx.test.d.ts.map +1 -0
  62. package/dist/__tests__/unit/migrate-mdx.test.js +158 -0
  63. package/dist/__tests__/unit/migrate-mdx.test.js.map +1 -0
  64. package/dist/__tests__/unit/openapi.test.d.ts +2 -0
  65. package/dist/__tests__/unit/openapi.test.d.ts.map +1 -0
  66. package/dist/__tests__/unit/openapi.test.js +52 -0
  67. package/dist/__tests__/unit/openapi.test.js.map +1 -0
  68. package/dist/__tests__/unit/package-config.test.d.ts +2 -0
  69. package/dist/__tests__/unit/package-config.test.d.ts.map +1 -0
  70. package/dist/__tests__/unit/package-config.test.js +63 -0
  71. package/dist/__tests__/unit/package-config.test.js.map +1 -0
  72. package/dist/__tests__/unit/port.test.d.ts +2 -0
  73. package/dist/__tests__/unit/port.test.d.ts.map +1 -0
  74. package/dist/__tests__/unit/port.test.js +20 -0
  75. package/dist/__tests__/unit/port.test.js.map +1 -0
  76. package/dist/__tests__/unit/vendored-sync.test.d.ts +14 -0
  77. package/dist/__tests__/unit/vendored-sync.test.d.ts.map +1 -0
  78. package/dist/__tests__/unit/vendored-sync.test.js +90 -0
  79. package/dist/__tests__/unit/vendored-sync.test.js.map +1 -0
  80. package/dist/commands/broken-links.d.ts +11 -0
  81. package/dist/commands/broken-links.d.ts.map +1 -0
  82. package/dist/commands/broken-links.js +95 -0
  83. package/dist/commands/broken-links.js.map +1 -0
  84. package/dist/commands/clean.d.ts +7 -0
  85. package/dist/commands/clean.d.ts.map +1 -0
  86. package/dist/commands/clean.js +59 -0
  87. package/dist/commands/clean.js.map +1 -0
  88. package/dist/commands/deploy/cloudflare.d.ts +12 -0
  89. package/dist/commands/deploy/cloudflare.d.ts.map +1 -0
  90. package/dist/commands/deploy/cloudflare.js +409 -0
  91. package/dist/commands/deploy/cloudflare.js.map +1 -0
  92. package/dist/commands/deploy/templates.d.ts +23 -0
  93. package/dist/commands/deploy/templates.d.ts.map +1 -0
  94. package/dist/commands/deploy/templates.js +179 -0
  95. package/dist/commands/deploy/templates.js.map +1 -0
  96. package/dist/commands/deploy/types.d.ts +19 -0
  97. package/dist/commands/deploy/types.d.ts.map +1 -0
  98. package/dist/commands/deploy/types.js +5 -0
  99. package/dist/commands/deploy/types.js.map +1 -0
  100. package/dist/commands/dev.d.ts +14 -0
  101. package/dist/commands/dev.d.ts.map +1 -0
  102. package/dist/commands/dev.js +817 -0
  103. package/dist/commands/dev.js.map +1 -0
  104. package/dist/commands/doctor.d.ts +7 -0
  105. package/dist/commands/doctor.d.ts.map +1 -0
  106. package/dist/commands/doctor.js +159 -0
  107. package/dist/commands/doctor.js.map +1 -0
  108. package/dist/commands/init.d.ts +7 -0
  109. package/dist/commands/init.d.ts.map +1 -0
  110. package/dist/commands/init.js +96 -0
  111. package/dist/commands/init.js.map +1 -0
  112. package/dist/commands/migrate/convert-mdx.d.ts +50 -0
  113. package/dist/commands/migrate/convert-mdx.d.ts.map +1 -0
  114. package/dist/commands/migrate/convert-mdx.js +108 -0
  115. package/dist/commands/migrate/convert-mdx.js.map +1 -0
  116. package/dist/commands/migrate/convert.d.ts +80 -0
  117. package/dist/commands/migrate/convert.d.ts.map +1 -0
  118. package/dist/commands/migrate/convert.js +158 -0
  119. package/dist/commands/migrate/convert.js.map +1 -0
  120. package/dist/commands/migrate/detect.d.ts +31 -0
  121. package/dist/commands/migrate/detect.d.ts.map +1 -0
  122. package/dist/commands/migrate/detect.js +62 -0
  123. package/dist/commands/migrate/detect.js.map +1 -0
  124. package/dist/commands/migrate/extract-hooks.d.ts +71 -0
  125. package/dist/commands/migrate/extract-hooks.d.ts.map +1 -0
  126. package/dist/commands/migrate/extract-hooks.js +473 -0
  127. package/dist/commands/migrate/extract-hooks.js.map +1 -0
  128. package/dist/commands/migrate/index.d.ts +17 -0
  129. package/dist/commands/migrate/index.d.ts.map +1 -0
  130. package/dist/commands/migrate/index.js +282 -0
  131. package/dist/commands/migrate/index.js.map +1 -0
  132. package/dist/commands/migrate/prompts.d.ts +22 -0
  133. package/dist/commands/migrate/prompts.d.ts.map +1 -0
  134. package/dist/commands/migrate/prompts.js +67 -0
  135. package/dist/commands/migrate/prompts.js.map +1 -0
  136. package/dist/commands/migrate/types.d.ts +22 -0
  137. package/dist/commands/migrate/types.d.ts.map +1 -0
  138. package/dist/commands/migrate/types.js +26 -0
  139. package/dist/commands/migrate/types.js.map +1 -0
  140. package/dist/commands/openapi-check.d.ts +11 -0
  141. package/dist/commands/openapi-check.d.ts.map +1 -0
  142. package/dist/commands/openapi-check.js +88 -0
  143. package/dist/commands/openapi-check.js.map +1 -0
  144. package/dist/commands/rename.d.ts +10 -0
  145. package/dist/commands/rename.d.ts.map +1 -0
  146. package/dist/commands/rename.js +125 -0
  147. package/dist/commands/rename.js.map +1 -0
  148. package/dist/commands/update.d.ts +10 -0
  149. package/dist/commands/update.d.ts.map +1 -0
  150. package/dist/commands/update.js +57 -0
  151. package/dist/commands/update.js.map +1 -0
  152. package/dist/commands/validate.d.ts +12 -0
  153. package/dist/commands/validate.d.ts.map +1 -0
  154. package/dist/commands/validate.js +163 -0
  155. package/dist/commands/validate.js.map +1 -0
  156. package/dist/index.d.ts +9 -0
  157. package/dist/index.d.ts.map +1 -0
  158. package/dist/index.js +334 -0
  159. package/dist/index.js.map +1 -0
  160. package/dist/lib/config.d.ts +7 -0
  161. package/dist/lib/config.d.ts.map +1 -0
  162. package/dist/lib/config.js +18 -0
  163. package/dist/lib/config.js.map +1 -0
  164. package/dist/lib/deprecated-components.d.ts +72 -0
  165. package/dist/lib/deprecated-components.d.ts.map +1 -0
  166. package/dist/lib/deprecated-components.js +138 -0
  167. package/dist/lib/deprecated-components.js.map +1 -0
  168. package/dist/lib/deps.d.ts +17 -0
  169. package/dist/lib/deps.d.ts.map +1 -0
  170. package/dist/lib/deps.js +186 -0
  171. package/dist/lib/deps.js.map +1 -0
  172. package/dist/lib/docs-config.d.ts +67 -0
  173. package/dist/lib/docs-config.d.ts.map +1 -0
  174. package/dist/lib/docs-config.js +294 -0
  175. package/dist/lib/docs-config.js.map +1 -0
  176. package/dist/lib/errors.d.ts +23 -0
  177. package/dist/lib/errors.d.ts.map +1 -0
  178. package/dist/lib/errors.js +32 -0
  179. package/dist/lib/errors.js.map +1 -0
  180. package/dist/lib/frontmatter-utils.d.ts +25 -0
  181. package/dist/lib/frontmatter-utils.d.ts.map +1 -0
  182. package/dist/lib/frontmatter-utils.js +64 -0
  183. package/dist/lib/frontmatter-utils.js.map +1 -0
  184. package/dist/lib/mdx-validator.d.ts +27 -0
  185. package/dist/lib/mdx-validator.d.ts.map +1 -0
  186. package/dist/lib/mdx-validator.js +148 -0
  187. package/dist/lib/mdx-validator.js.map +1 -0
  188. package/dist/lib/navigation-validator.d.ts +31 -0
  189. package/dist/lib/navigation-validator.d.ts.map +1 -0
  190. package/dist/lib/navigation-validator.js +75 -0
  191. package/dist/lib/navigation-validator.js.map +1 -0
  192. package/dist/lib/normalize-config.d.ts +57 -0
  193. package/dist/lib/normalize-config.d.ts.map +1 -0
  194. package/dist/lib/normalize-config.js +63 -0
  195. package/dist/lib/normalize-config.js.map +1 -0
  196. package/dist/lib/openapi/cache.d.ts +40 -0
  197. package/dist/lib/openapi/cache.d.ts.map +1 -0
  198. package/dist/lib/openapi/cache.js +76 -0
  199. package/dist/lib/openapi/cache.js.map +1 -0
  200. package/dist/lib/openapi/errors.d.ts +36 -0
  201. package/dist/lib/openapi/errors.d.ts.map +1 -0
  202. package/dist/lib/openapi/errors.js +162 -0
  203. package/dist/lib/openapi/errors.js.map +1 -0
  204. package/dist/lib/openapi/index.d.ts +10 -0
  205. package/dist/lib/openapi/index.d.ts.map +1 -0
  206. package/dist/lib/openapi/index.js +12 -0
  207. package/dist/lib/openapi/index.js.map +1 -0
  208. package/dist/lib/openapi/types.d.ts +198 -0
  209. package/dist/lib/openapi/types.d.ts.map +1 -0
  210. package/dist/lib/openapi/types.js +8 -0
  211. package/dist/lib/openapi/types.js.map +1 -0
  212. package/dist/lib/openapi/validator.d.ts +45 -0
  213. package/dist/lib/openapi/validator.d.ts.map +1 -0
  214. package/dist/lib/openapi/validator.js +128 -0
  215. package/dist/lib/openapi/validator.js.map +1 -0
  216. package/dist/lib/openapi.d.ts +7 -0
  217. package/dist/lib/openapi.d.ts.map +1 -0
  218. package/dist/lib/openapi.js +7 -0
  219. package/dist/lib/openapi.js.map +1 -0
  220. package/dist/lib/output.d.ts +14 -0
  221. package/dist/lib/output.d.ts.map +1 -0
  222. package/dist/lib/output.js +19 -0
  223. package/dist/lib/output.js.map +1 -0
  224. package/dist/lib/path-security.d.ts +23 -0
  225. package/dist/lib/path-security.d.ts.map +1 -0
  226. package/dist/lib/path-security.js +35 -0
  227. package/dist/lib/path-security.js.map +1 -0
  228. package/dist/lib/port.d.ts +18 -0
  229. package/dist/lib/port.d.ts.map +1 -0
  230. package/dist/lib/port.js +65 -0
  231. package/dist/lib/port.js.map +1 -0
  232. package/dist/lib/spinner.d.ts +4 -0
  233. package/dist/lib/spinner.d.ts.map +1 -0
  234. package/dist/lib/spinner.js +16 -0
  235. package/dist/lib/spinner.js.map +1 -0
  236. package/dist/lib/version.d.ts +2 -0
  237. package/dist/lib/version.d.ts.map +1 -0
  238. package/dist/lib/version.js +49 -0
  239. package/dist/lib/version.js.map +1 -0
  240. package/dist/utils/update-checker.d.ts +34 -0
  241. package/dist/utils/update-checker.d.ts.map +1 -0
  242. package/dist/utils/update-checker.js +142 -0
  243. package/dist/utils/update-checker.js.map +1 -0
  244. package/package.json +125 -0
  245. package/templates/docs.json +11 -0
  246. package/templates/introduction.mdx +19 -0
  247. package/templates/quickstart.mdx +20 -0
  248. package/vendored/app/[[...slug]]/error.tsx +103 -0
  249. package/vendored/app/[[...slug]]/page.tsx +690 -0
  250. package/vendored/app/api/assets/[...path]/route.ts +78 -0
  251. package/vendored/app/api/ev/route.ts +61 -0
  252. package/vendored/app/api/isr-health/route.ts +66 -0
  253. package/vendored/app/api/mcp/[project]/route.ts +435 -0
  254. package/vendored/app/api/og/route.tsx +167 -0
  255. package/vendored/app/api/r2/[project]/[...path]/route.ts +214 -0
  256. package/vendored/app/api/revalidate/route.ts +76 -0
  257. package/vendored/app/globals.css +37 -0
  258. package/vendored/app/layout.tsx +571 -0
  259. package/vendored/app/not-found.tsx +47 -0
  260. package/vendored/components/CodeBlockCopyButton.tsx +146 -0
  261. package/vendored/components/HeaderLinkCopy.tsx +135 -0
  262. package/vendored/components/errors/NotFoundContent.tsx +147 -0
  263. package/vendored/components/layout/LayoutWrapper.tsx +128 -0
  264. package/vendored/components/mdx/Accordion.tsx +91 -0
  265. package/vendored/components/mdx/ApiCodePanel.tsx +51 -0
  266. package/vendored/components/mdx/ApiEndpoint.tsx +104 -0
  267. package/vendored/components/mdx/ApiPage.tsx +379 -0
  268. package/vendored/components/mdx/Badge.tsx +169 -0
  269. package/vendored/components/mdx/Callouts.tsx +140 -0
  270. package/vendored/components/mdx/Card.tsx +214 -0
  271. package/vendored/components/mdx/CodeGroup.tsx +136 -0
  272. package/vendored/components/mdx/Color.tsx +244 -0
  273. package/vendored/components/mdx/Columns.tsx +37 -0
  274. package/vendored/components/mdx/Expandable.tsx +37 -0
  275. package/vendored/components/mdx/Frame.tsx +51 -0
  276. package/vendored/components/mdx/Icon.tsx +132 -0
  277. package/vendored/components/mdx/Latex.tsx +75 -0
  278. package/vendored/components/mdx/MDXComponents.tsx +414 -0
  279. package/vendored/components/mdx/Mermaid.tsx +35 -0
  280. package/vendored/components/mdx/MermaidInner.tsx +342 -0
  281. package/vendored/components/mdx/OpenApiEndpoint.tsx +971 -0
  282. package/vendored/components/mdx/Panel.tsx +26 -0
  283. package/vendored/components/mdx/PanelWrapper.tsx +100 -0
  284. package/vendored/components/mdx/ParamField.tsx +75 -0
  285. package/vendored/components/mdx/RequestExample.tsx +91 -0
  286. package/vendored/components/mdx/ResponseExample.tsx +145 -0
  287. package/vendored/components/mdx/ResponseField.tsx +109 -0
  288. package/vendored/components/mdx/Steps.tsx +173 -0
  289. package/vendored/components/mdx/Table.tsx +352 -0
  290. package/vendored/components/mdx/Tabs.tsx +147 -0
  291. package/vendored/components/mdx/Tile.tsx +127 -0
  292. package/vendored/components/mdx/Tooltip.tsx +111 -0
  293. package/vendored/components/mdx/Tree.tsx +484 -0
  294. package/vendored/components/mdx/Update.tsx +90 -0
  295. package/vendored/components/mdx/View.tsx +354 -0
  296. package/vendored/components/mdx/YouTube.tsx +35 -0
  297. package/vendored/components/mdx/ZoomableImage.tsx +83 -0
  298. package/vendored/components/navigation/Breadcrumb.tsx +241 -0
  299. package/vendored/components/navigation/DefaultLogo.tsx +81 -0
  300. package/vendored/components/navigation/Header.tsx +512 -0
  301. package/vendored/components/navigation/LanguageSelector.tsx +249 -0
  302. package/vendored/components/navigation/PageNavigation.tsx +174 -0
  303. package/vendored/components/navigation/Sidebar.tsx +713 -0
  304. package/vendored/components/navigation/SocialFooter.tsx +186 -0
  305. package/vendored/components/navigation/TableOfContents.tsx +435 -0
  306. package/vendored/components/navigation/TabsNav.tsx +182 -0
  307. package/vendored/components/search/LazySearchModal.tsx +19 -0
  308. package/vendored/components/search/SearchModal.tsx +573 -0
  309. package/vendored/components/snippets/ProjectSnippets.tsx +4 -0
  310. package/vendored/components/theme/ThemeProvider.tsx +31 -0
  311. package/vendored/components/theme/ThemeToggle.tsx +134 -0
  312. package/vendored/components/ui/CodePanel.tsx +517 -0
  313. package/vendored/components/ui/CodePanelModal.tsx +342 -0
  314. package/vendored/contexts/TabSyncContext.tsx +30 -0
  315. package/vendored/hooks/useFocusTrap.ts +42 -0
  316. package/vendored/hooks/useHashNavigation.ts +39 -0
  317. package/vendored/hooks/useShikiHighlight.ts +101 -0
  318. package/vendored/lib/analytics-client.ts +77 -0
  319. package/vendored/lib/build/cache.ts +138 -0
  320. package/vendored/lib/build/error-parser.ts +690 -0
  321. package/vendored/lib/build/estimation.ts +113 -0
  322. package/vendored/lib/build/index.ts +17 -0
  323. package/vendored/lib/build/page-file-map.ts +48 -0
  324. package/vendored/lib/build/r2-upload.ts +179 -0
  325. package/vendored/lib/cache-keys.ts +117 -0
  326. package/vendored/lib/code-utils.ts +42 -0
  327. package/vendored/lib/content-loader.ts +176 -0
  328. package/vendored/lib/deprecated-components.ts +185 -0
  329. package/vendored/lib/docs-isr.ts +180 -0
  330. package/vendored/lib/docs-types.ts +874 -0
  331. package/vendored/lib/docs.ts +203 -0
  332. package/vendored/lib/domain-helpers.ts +107 -0
  333. package/vendored/lib/email-notifier.ts +102 -0
  334. package/vendored/lib/email-templates/build-failure.tsx +193 -0
  335. package/vendored/lib/email-templates/components/base-layout.tsx +150 -0
  336. package/vendored/lib/email-templates/components/error-box.tsx +88 -0
  337. package/vendored/lib/email-templates/components/info-row.tsx +63 -0
  338. package/vendored/lib/email-templates/index.ts +13 -0
  339. package/vendored/lib/empty-polyfill.js +3 -0
  340. package/vendored/lib/extract-highlights.ts +124 -0
  341. package/vendored/lib/fonts.ts +227 -0
  342. package/vendored/lib/frontmatter-utils.ts +77 -0
  343. package/vendored/lib/fs-utils.ts +20 -0
  344. package/vendored/lib/git-utils.ts +87 -0
  345. package/vendored/lib/health-checks.ts +224 -0
  346. package/vendored/lib/icon-utils.ts +492 -0
  347. package/vendored/lib/infer-page-type.ts +14 -0
  348. package/vendored/lib/isr-build-executor.ts +185 -0
  349. package/vendored/lib/language-icons.ts +152 -0
  350. package/vendored/lib/language-utils.ts +338 -0
  351. package/vendored/lib/latex-config.ts +64 -0
  352. package/vendored/lib/link-prefix-context.tsx +32 -0
  353. package/vendored/lib/logger.ts +63 -0
  354. package/vendored/lib/mcp-search.ts +255 -0
  355. package/vendored/lib/mdx-inline-components.ts +155 -0
  356. package/vendored/lib/mdx.ts +100 -0
  357. package/vendored/lib/middleware-helpers.ts +519 -0
  358. package/vendored/lib/navigation-resolver.ts +621 -0
  359. package/vendored/lib/navigation-utils.ts +103 -0
  360. package/vendored/lib/normalize-config.ts +94 -0
  361. package/vendored/lib/openapi/cache.ts +92 -0
  362. package/vendored/lib/openapi/code-examples.ts +389 -0
  363. package/vendored/lib/openapi/errors.ts +253 -0
  364. package/vendored/lib/openapi/generator.ts +230 -0
  365. package/vendored/lib/openapi/index.ts +84 -0
  366. package/vendored/lib/openapi/parser.ts +474 -0
  367. package/vendored/lib/openapi/types.ts +232 -0
  368. package/vendored/lib/openapi/validator.ts +156 -0
  369. package/vendored/lib/openapi-isr.ts +121 -0
  370. package/vendored/lib/page-isr-helpers.ts +137 -0
  371. package/vendored/lib/path-safety.ts +130 -0
  372. package/vendored/lib/paths.ts +35 -0
  373. package/vendored/lib/preprocess-mdx.ts +951 -0
  374. package/vendored/lib/process-mdx-with-exports.ts +75 -0
  375. package/vendored/lib/project-resolver.ts +165 -0
  376. package/vendored/lib/r2-content.ts +60 -0
  377. package/vendored/lib/r2-manifest.ts +84 -0
  378. package/vendored/lib/recent-searches.ts +41 -0
  379. package/vendored/lib/recma-compound-components.ts +84 -0
  380. package/vendored/lib/redirect-compiler.ts +160 -0
  381. package/vendored/lib/redirect-matcher.ts +296 -0
  382. package/vendored/lib/redis.ts +23 -0
  383. package/vendored/lib/rehype-class-to-classname.ts +31 -0
  384. package/vendored/lib/rehype-code-meta.ts +275 -0
  385. package/vendored/lib/rehype-nozoom-to-data.ts +45 -0
  386. package/vendored/lib/remark-extract-exports.ts +104 -0
  387. package/vendored/lib/resilience.ts +260 -0
  388. package/vendored/lib/revalidation-helpers.ts +200 -0
  389. package/vendored/lib/revalidation-trigger.ts +150 -0
  390. package/vendored/lib/screenshot-capture.ts +229 -0
  391. package/vendored/lib/search-client.ts +91 -0
  392. package/vendored/lib/search-suggestions.ts +38 -0
  393. package/vendored/lib/search.ts +158 -0
  394. package/vendored/lib/seo.ts +264 -0
  395. package/vendored/lib/shiki-client.ts +131 -0
  396. package/vendored/lib/shiki-config.ts +289 -0
  397. package/vendored/lib/shiki-css-theme.ts +46 -0
  398. package/vendored/lib/shiki-highlighter.ts +62 -0
  399. package/vendored/lib/shiki-transformers.ts +337 -0
  400. package/vendored/lib/slack-notifier.ts +248 -0
  401. package/vendored/lib/snippet-compiler-isr.ts +114 -0
  402. package/vendored/lib/snippet-loader-isr.ts +276 -0
  403. package/vendored/lib/static-artifacts.ts +375 -0
  404. package/vendored/lib/static-file-route.ts +72 -0
  405. package/vendored/lib/tracking-script.ts +19 -0
  406. package/vendored/lib/typography-config.ts +42 -0
  407. package/vendored/lib/validate-config.ts +268 -0
  408. package/vendored/next.config.js +45 -0
  409. package/vendored/postcss.config.js +6 -0
  410. package/vendored/schema/README.md +28 -0
  411. package/vendored/schema/docs-schema.json +4631 -0
  412. package/vendored/scripts/build-project.cjs +174 -0
  413. package/vendored/scripts/build-search-index.cjs +347 -0
  414. package/vendored/scripts/compile-snippets.cjs +488 -0
  415. package/vendored/scripts/copy-files.cjs +295 -0
  416. package/vendored/scripts/dev-project.cjs +534 -0
  417. package/vendored/scripts/enhance-navigation.cjs +354 -0
  418. package/vendored/scripts/validate-links.cjs +423 -0
  419. package/vendored/shared/constants.ts +6 -0
  420. package/vendored/shared/index.ts +19 -0
  421. package/vendored/shared/logger.ts +62 -0
  422. package/vendored/shared/memory-monitor.ts +190 -0
  423. package/vendored/shared/navigation-validator.ts +101 -0
  424. package/vendored/shared/path-security.ts +39 -0
  425. package/vendored/shared/status-reporter.ts +199 -0
  426. package/vendored/shared/timer.ts +51 -0
  427. package/vendored/shared/types.ts +102 -0
  428. package/vendored/tailwind.config.ts +39 -0
  429. package/vendored/themes/base.css +1311 -0
  430. package/vendored/themes/index.ts +119 -0
  431. package/vendored/themes/jam/variables.css +835 -0
  432. package/vendored/themes/nebula/variables.css +282 -0
  433. package/vendored/themes/pulsar/variables.css +1009 -0
  434. package/vendored/themes/types.ts +89 -0
  435. package/vendored/tsconfig.json +48 -0
@@ -0,0 +1,713 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
4
+ import Link from 'next/link';
5
+ import Image from 'next/image';
6
+ import { usePathname, useRouter } from 'next/navigation';
7
+ // Icons use Font Awesome CSS classes for lightweight rendering
8
+ import type {
9
+ DocsConfig,
10
+ NavigationPage,
11
+ NavigationConfig,
12
+ AnchorConfig,
13
+ TabConfig,
14
+ GroupConfig,
15
+ } from '@/lib/docs-types';
16
+ import {
17
+ normalizeNavPage,
18
+ normalizeLogo,
19
+ getIconName,
20
+ } from '@/lib/docs-types';
21
+ import type { TabsPosition } from '@/lib/docs-types';
22
+ import { resolveNavigation, type ResolvedGroup, type ResolvedPage, type ResolvedAnchor, type ResolvedTab, type ResolvedExternalAnchor, type ResolvedNavItem } from '@/lib/navigation-resolver';
23
+ import type { LayoutVariant } from '@/themes';
24
+ import { getTheme } from '@/themes';
25
+ import { DefaultLogo, DefaultLogoCompact } from './DefaultLogo';
26
+ import { LanguageSelector } from './LanguageSelector';
27
+ import { LazySearchModal as SearchModal } from '@/components/search/LazySearchModal';
28
+ import { ThemeToggle, ThemeToggleCycle } from '@/components/theme/ThemeToggle';
29
+ import { getIconClass } from '@/lib/icon-utils';
30
+ import { getTabsFromConfig } from '@/lib/navigation-utils';
31
+ import { useLinkPrefix } from '@/lib/link-prefix-context';
32
+
33
+ interface SidebarProps {
34
+ config: DocsConfig;
35
+ layout?: LayoutVariant;
36
+ tabsPosition?: TabsPosition;
37
+ isOpen?: boolean;
38
+ onClose?: () => void;
39
+ }
40
+
41
+ // HTTP method badge colors
42
+ const methodColors: Record<string, { bg: string; text: string }> = {
43
+ GET: { bg: 'bg-emerald-500/15', text: 'text-emerald-600 dark:text-emerald-400' },
44
+ POST: { bg: 'bg-blue-500/15', text: 'text-blue-600 dark:text-blue-400' },
45
+ PUT: { bg: 'bg-amber-500/15', text: 'text-amber-600 dark:text-amber-400' },
46
+ PATCH: { bg: 'bg-orange-500/15', text: 'text-orange-600 dark:text-orange-400' },
47
+ DELETE: { bg: 'bg-red-500/15', text: 'text-red-600 dark:text-red-400' },
48
+ };
49
+
50
+ // Memoized navigation page component to prevent re-renders when only other pages change
51
+ interface NavPageProps {
52
+ page: ResolvedPage;
53
+ pathname: string | null;
54
+ layout: LayoutVariant;
55
+ onPrefetch: (path: string) => void;
56
+ linkPrefix?: string; // e.g., '/docs' when hostAtDocs is true
57
+ }
58
+
59
+ export const NavPage = React.memo(function NavPage({ page, pathname, layout, onPrefetch, linkPrefix = '' }: NavPageProps) {
60
+ const href = `${linkPrefix}/${page.path}`;
61
+ const isActive = pathname === href;
62
+ const colors = page.method ? methodColors[page.method] : null;
63
+
64
+ return (
65
+ <li>
66
+ <Link
67
+ href={href}
68
+ prefetch={false}
69
+ onMouseEnter={() => onPrefetch(page.path)}
70
+ className={`flex items-center gap-2 ${layout === 'header-logo' ? 'pr-3' : 'px-3'} py-1.5 rounded-lg text-sm transition-colors ${
71
+ isActive
72
+ ? 'text-[var(--color-primary)] nav-active'
73
+ : 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]'
74
+ }`}
75
+ >
76
+ {page.method && colors && (
77
+ <span className={`w-12 text-center px-1.5 py-0.5 text-[10px] font-bold rounded flex-shrink-0 ${colors.bg} ${colors.text}`}>
78
+ {page.method === 'DELETE' ? 'DEL' : page.method}
79
+ </span>
80
+ )}
81
+ {page.icon && !page.method && (
82
+ <i className={`${getIconClass(page.icon)} text-[14px] flex-shrink-0 opacity-60`} aria-hidden="true" />
83
+ )}
84
+ <span className="flex-1 min-w-0">{page.title}</span>
85
+ {page.tag && (
86
+ <span className="px-1.5 py-0.5 text-[10px] font-semibold rounded bg-[var(--color-accent)]/15 text-[var(--color-accent)]">
87
+ {page.tag}
88
+ </span>
89
+ )}
90
+ </Link>
91
+ </li>
92
+ );
93
+ });
94
+
95
+ /** Find all groups in the path from root to the page containing currentPath */
96
+ function findGroupsContainingPath(groups: ResolvedGroup[], currentPath: string, parentNames: string[] = []): Set<string> {
97
+ const result = new Set<string>();
98
+ for (const group of groups) {
99
+ const groupPath = group.name ? [...parentNames, group.name] : parentNames;
100
+
101
+ if (group.pages.some(page => page.path === currentPath)) {
102
+ groupPath.forEach(name => result.add(name));
103
+ }
104
+
105
+ if (group.nested) {
106
+ const nestedResults = findGroupsContainingPath(group.nested, currentPath, groupPath);
107
+ nestedResults.forEach(name => result.add(name));
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+
113
+ export function Sidebar({ config, layout = 'header-logo', tabsPosition: tabsPositionProp, isOpen = false, onClose }: SidebarProps) {
114
+ const pathname = usePathname();
115
+ const router = useRouter();
116
+ const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
117
+ const [logoError, setLogoError] = useState(false);
118
+ const [isDark, setIsDark] = useState(false);
119
+ const [isSearchOpen, setIsSearchOpen] = useState(false);
120
+ const sidebarRef = useRef<HTMLElement>(null);
121
+ const linkPrefix = useLinkPrefix();
122
+
123
+ // Stable callback for prefetching pages on hover
124
+ const handlePrefetch = useCallback((path: string) => {
125
+ router.prefetch(`${linkPrefix}/${path}`);
126
+ }, [router, linkPrefix]);
127
+
128
+ // Determine effective tabsPosition:
129
+ // 1. Use config.tabsPosition if specified
130
+ // 2. Otherwise use prop if specified
131
+ // 3. Otherwise use theme default
132
+ const themeConfig = getTheme(config.theme);
133
+ const effectiveTabsPosition: TabsPosition = config.tabsPosition || tabsPositionProp || themeConfig.defaultTabsPosition;
134
+ const showTabsInSidebar = effectiveTabsPosition === 'left';
135
+
136
+ // In sidebar-logo layout, logo and search are in sidebar
137
+ const showLogoInSidebar = layout === 'sidebar-logo';
138
+ const showSearchInSidebar = layout === 'sidebar-logo';
139
+
140
+ // Logo configuration
141
+ const logoConfig = normalizeLogo(config.logo);
142
+ const logoHref = logoConfig?.href || '/';
143
+ const logoSrc = logoConfig ? (isDark && logoConfig.dark ? logoConfig.dark : logoConfig.light) : null;
144
+ const showLogoImage = logoSrc && !logoError;
145
+
146
+ // Resolve navigation using the new resolver
147
+ const resolvedNav = useMemo(() => {
148
+ return resolveNavigation(config, pathname || '/docs');
149
+ }, [config, pathname]);
150
+
151
+ // Track if this is the initial mount (for resetting expanded state on refresh)
152
+ const isInitialMount = useRef(true);
153
+
154
+ // Memoize the groups that should be expanded for the current page
155
+ const groupsForCurrentPage = useMemo(() => {
156
+ const currentPath = pathname?.replace(/^\/(?:docs\/)?/, '') || '';
157
+ return findGroupsContainingPath(resolvedNav.groups, currentPath);
158
+ }, [resolvedNav.groups, pathname]);
159
+
160
+ // Expand groups containing current page
161
+ // On initial mount: reset to only current page's groups
162
+ // On navigation: add current page's groups to existing expanded set (preserve manually opened)
163
+ useEffect(() => {
164
+ if (isInitialMount.current) {
165
+ // Initial mount (page load/refresh): only expand current page's groups
166
+ isInitialMount.current = false;
167
+ setExpandedGroups(groupsForCurrentPage);
168
+ } else {
169
+ // Navigation: add current page's groups to existing expanded set
170
+ setExpandedGroups(prev => {
171
+ const newSet = new Set(prev);
172
+ groupsForCurrentPage.forEach(g => newSet.add(g));
173
+ return newSet;
174
+ });
175
+ }
176
+ }, [groupsForCurrentPage]);
177
+
178
+ // Detect dark mode for logo switching
179
+ useEffect(() => {
180
+ const checkDarkMode = () => {
181
+ setIsDark(document.documentElement.classList.contains('dark'));
182
+ };
183
+ checkDarkMode();
184
+ const observer = new MutationObserver(checkDarkMode);
185
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
186
+ return () => observer.disconnect();
187
+ }, []);
188
+
189
+ // Keyboard shortcut for search (Cmd+K / Ctrl+K) - only in sidebar-logo layout
190
+ useEffect(() => {
191
+ if (!showSearchInSidebar) return;
192
+
193
+ const down = (e: KeyboardEvent) => {
194
+ if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
195
+ e.preventDefault();
196
+ setIsSearchOpen((open) => !open);
197
+ }
198
+ };
199
+
200
+ document.addEventListener('keydown', down);
201
+ return () => document.removeEventListener('keydown', down);
202
+ }, [showSearchInSidebar]);
203
+
204
+ // Close sidebar on route change (mobile)
205
+ useEffect(() => {
206
+ onClose?.();
207
+ // eslint-disable-next-line react-hooks/exhaustive-deps
208
+ }, [pathname]); // Intentionally excluding onClose - we only want to trigger on pathname changes
209
+
210
+ // Scroll the active navigation link into view when pathname changes
211
+ useEffect(() => {
212
+ // Double RAF ensures DOM is painted before measuring
213
+ let rafId = requestAnimationFrame(() => {
214
+ rafId = requestAnimationFrame(() => {
215
+ const scrollContainer = sidebarRef.current?.querySelector('.sidebar-scroll');
216
+ const activeLink = scrollContainer?.querySelector('.nav-active');
217
+ if (!scrollContainer || !activeLink) return;
218
+
219
+ const containerRect = scrollContainer.getBoundingClientRect();
220
+ const linkRect = activeLink.getBoundingClientRect();
221
+ const isVisible = linkRect.top >= containerRect.top && linkRect.bottom <= containerRect.bottom;
222
+
223
+ if (!isVisible) {
224
+ activeLink.scrollIntoView({ block: 'center', behavior: 'instant' });
225
+ }
226
+ });
227
+ });
228
+
229
+ return () => cancelAnimationFrame(rafId);
230
+ }, [pathname]);
231
+
232
+ // Prevent body scroll when sidebar is open on mobile
233
+ useEffect(() => {
234
+ if (isOpen) {
235
+ document.body.style.overflow = 'hidden';
236
+ } else {
237
+ document.body.style.overflow = '';
238
+ }
239
+ return () => {
240
+ document.body.style.overflow = '';
241
+ };
242
+ }, [isOpen]);
243
+
244
+ // Find the first page in a group (including nested groups)
245
+ function findFirstPageInGroup(g: ResolvedGroup): string | null {
246
+ if (g.pages.length > 0) return g.pages[0].path;
247
+ for (const nested of g.nested || []) {
248
+ const nestedFirst = findFirstPageInGroup(nested);
249
+ if (nestedFirst) return nestedFirst;
250
+ }
251
+ return null;
252
+ }
253
+
254
+ // Toggle group expansion; if expanding, navigate to first page
255
+ const handleGroupClick = useCallback((group: ResolvedGroup) => {
256
+ setExpandedGroups(prev => {
257
+ const newSet = new Set(prev);
258
+ if (prev.has(group.name)) {
259
+ newSet.delete(group.name);
260
+ } else {
261
+ const firstPagePath = findFirstPageInGroup(group);
262
+ if (firstPagePath) router.push(`${linkPrefix}/${firstPagePath}`);
263
+ newSet.add(group.name);
264
+ }
265
+ return newSet;
266
+ });
267
+ }, [router, linkPrefix]);
268
+
269
+ // Render a navigation group (supports nesting)
270
+ function renderGroup(group: ResolvedGroup, level: number = 0) {
271
+ const isExpanded = expandedGroups.has(group.name);
272
+
273
+ // Skip rendering if group has no name and no content
274
+ if (!group.name && group.pages.length === 0 && !group.nested?.length && !group.items?.length) {
275
+ return null;
276
+ }
277
+
278
+ return (
279
+ <div key={group.name || `group-${level}`} className={level === 0 ? 'ml-6' : level === 1 ? '' : 'ml-3'}>
280
+ {group.name && (
281
+ level === 0 ? (
282
+ <button
283
+ onClick={() => handleGroupClick(group)}
284
+ className="flex items-center justify-between w-full text-left mb-2 cursor-pointer"
285
+ >
286
+ <span className="text-sm font-semibold text-[var(--color-text-tertiary)] flex items-center gap-2">
287
+ {group.icon && (
288
+ <i className={`${getIconClass(group.icon)} text-[14px]`} aria-hidden="true" />
289
+ )}
290
+ {group.name}
291
+ {group.tag && (
292
+ <span className="px-1.5 py-0.5 text-[9px] font-semibold rounded bg-[var(--color-accent)]/15 text-[var(--color-accent)] normal-case tracking-normal">
293
+ {group.tag}
294
+ </span>
295
+ )}
296
+ </span>
297
+ </button>
298
+ ) : (
299
+ <button
300
+ onClick={() => handleGroupClick(group)}
301
+ className={`nav-group-l1 flex items-center gap-1.5 ${layout === 'header-logo' ? 'pr-3' : 'px-3'} py-1.5 rounded-lg text-sm transition-colors w-full text-left text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]`}
302
+ >
303
+ <span className="truncate">{group.name}</span>
304
+ <i
305
+ className={`fa-solid fa-chevron-right text-[8px] text-[var(--color-text-muted)] transition-transform ${isExpanded ? 'rotate-90' : ''}`}
306
+ aria-hidden="true"
307
+ />
308
+ {group.tag && (
309
+ <span className="px-1.5 py-0.5 text-[10px] font-semibold rounded bg-[var(--color-accent)]/15 text-[var(--color-accent)]">
310
+ {group.tag}
311
+ </span>
312
+ )}
313
+ </button>
314
+ )
315
+ )}
316
+
317
+ {(level === 0 || isExpanded || !group.name) && (
318
+ <>
319
+ {group.items ? (
320
+ // Use ordered items array when available (preserves page/group ordering)
321
+ <div className={level === 1 ? 'ml-3' : ''}>
322
+ {(() => {
323
+ // Group consecutive pages into single ul elements for continuous border
324
+ const elements: React.ReactNode[] = [];
325
+ let currentPages: typeof group.items = [];
326
+
327
+ const flushPages = () => {
328
+ if (currentPages.length > 0) {
329
+ elements.push(
330
+ <ul key={`pages-${elements.length}`}>
331
+ {currentPages.map((p) => p.type === 'page' && (
332
+ <NavPage
333
+ key={p.page.path}
334
+ page={p.page}
335
+ pathname={pathname}
336
+ layout={layout}
337
+ onPrefetch={handlePrefetch}
338
+ linkPrefix={linkPrefix}
339
+ />
340
+ ))}
341
+ </ul>
342
+ );
343
+ currentPages = [];
344
+ }
345
+ };
346
+
347
+ group.items.forEach((item, idx) => {
348
+ if (item.type === 'page') {
349
+ currentPages.push(item);
350
+ } else {
351
+ flushPages();
352
+ elements.push(
353
+ <div key={item.group.name || `nested-${idx}`} className={level === 0 ? '' : level === 1 ? '' : 'mt-4'}>
354
+ {renderGroup(item.group, level + 1)}
355
+ </div>
356
+ );
357
+ }
358
+ });
359
+ flushPages();
360
+
361
+ return elements;
362
+ })()}
363
+ </div>
364
+ ) : (
365
+ // Fallback to legacy pages + nested (for backwards compatibility)
366
+ <>
367
+ {group.pages.length > 0 && (
368
+ <ul className={level === 1 ? 'ml-3' : ''}>
369
+ {group.pages.map((page) => (
370
+ <NavPage
371
+ key={page.path}
372
+ page={page}
373
+ pathname={pathname}
374
+ layout={layout}
375
+ onPrefetch={handlePrefetch}
376
+ linkPrefix={linkPrefix}
377
+ />
378
+ ))}
379
+ </ul>
380
+ )}
381
+ {group.nested && group.nested.length > 0 && (
382
+ <div className={level === 0 ? '' : level === 1 ? 'ml-3' : 'mt-4'}>
383
+ {group.nested.map((nested) => renderGroup(nested, level + 1))}
384
+ </div>
385
+ )}
386
+ </>
387
+ )}
388
+ </>
389
+ )}
390
+ </div>
391
+ );
392
+ }
393
+
394
+ // Render anchors (deprecated - for backwards compatibility)
395
+ function renderAnchors() {
396
+ if (resolvedNav.anchors.length === 0) return null;
397
+
398
+ return (
399
+ <div className="flex flex-col gap-1 mb-6">
400
+ {resolvedNav.anchors.map((anchor) => {
401
+ const isCurrentAnchor = anchor.name === resolvedNav.activeAnchor;
402
+
403
+ if (anchor.isExternal && anchor.href) {
404
+ return (
405
+ <a
406
+ key={anchor.name}
407
+ href={anchor.href}
408
+ target="_blank"
409
+ rel="noopener noreferrer"
410
+ data-nav-anchor
411
+ className="flex items-center gap-2.5 ml-6 pr-3 py-1.5 text-sm font-medium transition-colors text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]"
412
+ >
413
+ <i className={`${getIconClass(anchor.icon)} text-[14px]`} aria-hidden="true" />
414
+ <span className="flex-1">{anchor.name}</span>
415
+ <i className="fa-solid fa-arrow-up-right-from-square text-[14px] opacity-50" aria-hidden="true" />
416
+ </a>
417
+ );
418
+ }
419
+
420
+ // Find the first page in this anchor's groups
421
+ const findFirstPage = (groups: ResolvedGroup[]): string | null => {
422
+ for (const group of groups) {
423
+ if (group.pages.length > 0) {
424
+ return group.pages[0].path;
425
+ }
426
+ if (group.nested) {
427
+ const nestedFirst = findFirstPage(group.nested);
428
+ if (nestedFirst) return nestedFirst;
429
+ }
430
+ }
431
+ return null;
432
+ };
433
+ const firstPagePath = findFirstPage(anchor.groups);
434
+
435
+ return (
436
+ <button
437
+ key={anchor.name}
438
+ data-nav-anchor
439
+ data-active={isCurrentAnchor ? 'true' : 'false'}
440
+ onClick={() => {
441
+ if (firstPagePath) {
442
+ router.push(`${linkPrefix}/${firstPagePath}`);
443
+ }
444
+ }}
445
+ className={`flex items-center gap-2.5 ml-6 pr-3 py-1.5 text-sm font-medium transition-colors cursor-pointer ${
446
+ isCurrentAnchor
447
+ ? 'text-[var(--color-primary)]'
448
+ : 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]'
449
+ }`}
450
+ >
451
+ <i className={`${getIconClass(anchor.icon)} text-[14px] ${isCurrentAnchor ? 'text-[var(--color-primary)]' : ''}`} aria-hidden="true" />
452
+ {anchor.name}
453
+ </button>
454
+ );
455
+ })}
456
+ </div>
457
+ );
458
+ }
459
+
460
+ // Render external anchors (top-level config.anchors)
461
+ function renderExternalAnchors() {
462
+ if (resolvedNav.externalAnchors.length === 0) return null;
463
+
464
+ return (
465
+ <div className="flex flex-col gap-1 px-4 pt-4">
466
+ {resolvedNav.externalAnchors.map((anchor) => (
467
+ <a
468
+ key={anchor.name}
469
+ href={anchor.href}
470
+ target="_blank"
471
+ rel="noopener noreferrer"
472
+ className="flex items-center gap-2.5 px-2 py-1.5 text-sm font-medium transition-colors text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] rounded-lg"
473
+ >
474
+ {anchor.icon && (
475
+ <i className={`${getIconClass(anchor.icon)} text-[14px]`} aria-hidden="true" />
476
+ )}
477
+ <span className="flex-1">{anchor.name}</span>
478
+ <i className="fa-solid fa-arrow-up-right-from-square text-[10px] opacity-50" aria-hidden="true" />
479
+ </a>
480
+ ))}
481
+ </div>
482
+ );
483
+ }
484
+
485
+ // Find first page in a tab's groups
486
+ function findFirstPageInTab(tab: ResolvedTab): string | null {
487
+ // Find the corresponding tab config to get its groups (language-aware)
488
+ const tabs = getTabsFromConfig(config, pathname || undefined);
489
+ const tabConfig = tabs.find(t => t.tab === tab.name);
490
+ if (!tabConfig) return null;
491
+
492
+ // Check groups in tab
493
+ if (tabConfig.groups) {
494
+ for (const group of tabConfig.groups) {
495
+ if (group.pages && group.pages.length > 0) {
496
+ const firstPage = group.pages[0];
497
+ if (typeof firstPage === 'string') return firstPage;
498
+ if ('page' in firstPage) return firstPage.page;
499
+ }
500
+ }
501
+ }
502
+
503
+ // Check direct pages in tab
504
+ if (tabConfig.pages && tabConfig.pages.length > 0) {
505
+ const firstPage = tabConfig.pages[0];
506
+ if (typeof firstPage === 'string') return firstPage;
507
+ if ('page' in firstPage) return (firstPage as { page: string }).page;
508
+ }
509
+
510
+ return null;
511
+ }
512
+
513
+ // Render sidebar tabs (always on mobile, desktop only when tabsPosition === 'left')
514
+ function renderSidebarTabs() {
515
+ if (resolvedNav.tabs.length <= 1) return null;
516
+
517
+ // On mobile: always show tabs in sidebar
518
+ // On desktop: only show if tabsPosition is 'left' (showTabsInSidebar)
519
+ const visibilityClass = showTabsInSidebar ? '' : 'lg:hidden';
520
+
521
+ return (
522
+ <div className={`sidebar-tabs flex flex-col gap-2 px-4 pt-2 pb-6 ${visibilityClass}`}>
523
+ {resolvedNav.tabs.map((tab) => {
524
+ const isActive = tab.name === resolvedNav.activeTab;
525
+
526
+ // External tab link
527
+ if (tab.isExternal && tab.href) {
528
+ return (
529
+ <a
530
+ key={tab.name}
531
+ href={tab.href}
532
+ target="_blank"
533
+ rel="noopener noreferrer"
534
+ className="nav-tab-link flex items-center gap-2 px-3 py-1.5 text-sm font-medium transition-colors text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-tertiary)]"
535
+ >
536
+ {tab.icon && (
537
+ <i className={`${getIconClass(tab.icon)} text-[14px]`} aria-hidden="true" />
538
+ )}
539
+ <span className="flex-1">{tab.name}</span>
540
+ <i className="fa-solid fa-arrow-up-right-from-square text-[10px] opacity-50" aria-hidden="true" />
541
+ </a>
542
+ );
543
+ }
544
+
545
+ // Internal tab - navigate to first page
546
+ const firstPagePath = findFirstPageInTab(tab);
547
+
548
+ return (
549
+ <button
550
+ key={tab.name}
551
+ onClick={() => {
552
+ if (firstPagePath) {
553
+ router.push(`${linkPrefix}/${firstPagePath}`);
554
+ }
555
+ }}
556
+ className={`nav-tab-link flex items-center gap-2 px-3 py-1.5 text-sm transition-colors cursor-pointer ${
557
+ isActive
558
+ ? 'text-[var(--color-primary)] font-semibold bg-[var(--color-bg-tertiary)]'
559
+ : 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-tertiary)]'
560
+ }`}
561
+ >
562
+ {tab.icon && (
563
+ <i className={`${getIconClass(tab.icon)} text-[14px]`} aria-hidden="true" />
564
+ )}
565
+ <span>{tab.name}</span>
566
+ </button>
567
+ );
568
+ })}
569
+ </div>
570
+ );
571
+ }
572
+
573
+ const showTopDivider = resolvedNav.externalAnchors.length > 0;
574
+ const hasTopTabsBar = getTabsFromConfig(config, pathname || undefined).length > 1 && !showTabsInSidebar;
575
+ const desktopSidebarClasses = layout === 'sidebar-logo'
576
+ ? 'lg:fixed lg:top-0 lg:left-0 lg:bottom-0 lg:z-40'
577
+ : 'lg:sticky lg:self-start lg:z-30 lg:flex-shrink-0';
578
+
579
+ return (
580
+ <>
581
+ {/* Mobile overlay - z-50 to cover header (z-40) */}
582
+ {isOpen && (
583
+ <div
584
+ className="fixed inset-0 z-50 bg-black/50 lg:hidden"
585
+ onClick={onClose}
586
+ />
587
+ )}
588
+
589
+ {/* Sidebar */}
590
+ <aside
591
+ ref={sidebarRef}
592
+ className={`
593
+ w-[283px] lg:w-[295px] flex flex-col
594
+ ${layout === 'sidebar-logo' ? 'bg-[var(--color-bg-sidebar,var(--color-bg-primary))]' : 'bg-[var(--color-bg-primary)]'}
595
+ border-r border-[var(--color-border)]
596
+ transition-transform duration-300 ease-in-out
597
+ fixed top-0 left-0 bottom-0 z-[60]
598
+ ${desktopSidebarClasses}
599
+ ${isOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
600
+ `}
601
+ >
602
+ {/* Mobile header with logo, theme toggle, and close button - fixed at top */}
603
+ <div className="flex-shrink-0 flex items-center justify-between p-4 border-b border-[var(--color-border)] lg:hidden bg-[var(--color-bg-primary)]">
604
+ <div className="flex items-center gap-1">
605
+ <Link href={logoHref} prefetch={false} className="flex items-center">
606
+ {showLogoImage ? (
607
+ <div className="flex items-center gap-2">
608
+ <Image
609
+ src={logoSrc}
610
+ alt={`${config.name} logo`}
611
+ width={100}
612
+ height={28}
613
+ className="h-7 w-auto max-h-7 max-w-[170px] object-contain"
614
+ onError={() => setLogoError(true)}
615
+ />
616
+ </div>
617
+ ) : (
618
+ <DefaultLogoCompact name={config.name} />
619
+ )}
620
+ </Link>
621
+ <ThemeToggleCycle />
622
+ </div>
623
+ <button
624
+ onClick={onClose}
625
+ className="p-1.5 text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-secondary)] rounded-lg transition-colors"
626
+ aria-label="Close sidebar"
627
+ >
628
+ <i className="fa-solid fa-xmark text-[18px]" aria-hidden="true" />
629
+ </button>
630
+ </div>
631
+
632
+ {/* Scrollable content area - everything below mobile header scrolls */}
633
+ <div className="flex-1 overflow-y-auto sidebar-scroll" data-has-tabs={hasTopTabsBar ? 'true' : 'false'}>
634
+
635
+ {/* Logo in sidebar - for sidebar-logo layout on desktop */}
636
+ {showLogoInSidebar && (
637
+ <div className="hidden lg:flex items-center justify-between p-4">
638
+ <Link href={logoHref} prefetch={false} className="flex items-center">
639
+ {showLogoImage ? (
640
+ <div className="flex items-center gap-2">
641
+ <Image
642
+ src={logoSrc}
643
+ alt={`${config.name} logo`}
644
+ width={120}
645
+ height={32}
646
+ className="h-8 w-auto max-h-8 max-w-[170px] object-contain"
647
+ onError={() => setLogoError(true)}
648
+ />
649
+ </div>
650
+ ) : (
651
+ <DefaultLogo name={config.name} showBadge={false} />
652
+ )}
653
+ </Link>
654
+ <ThemeToggle />
655
+ </div>
656
+ )}
657
+
658
+ {/* Search button in sidebar - for sidebar-logo layout (desktop only, mobile uses header search) */}
659
+ {showSearchInSidebar && (
660
+ <div className="hidden lg:block px-4 pt-4 pb-4">
661
+ <button
662
+ onClick={() => setIsSearchOpen(true)}
663
+ className="w-full flex items-center gap-2.5 px-3 py-2 bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded-lg text-[var(--color-text-muted)] text-sm hover:border-[var(--color-border-hover)] hover:bg-[var(--color-bg-tertiary)] transition-colors"
664
+ >
665
+ <i className="fa-solid fa-magnifying-glass text-[14px] flex-shrink-0" aria-hidden="true" />
666
+ <span className="flex-1 text-left">Search...</span>
667
+ <kbd className="hidden sm:inline-flex px-1.5 py-0.5 text-[10px] text-[var(--color-text-muted)] bg-[var(--color-bg-primary)] border border-[var(--color-border)] rounded items-center gap-[2px]">
668
+ <span className="text-xs leading-none">⌘</span>
669
+ <span>K</span>
670
+ </kbd>
671
+ </button>
672
+ </div>
673
+ )}
674
+
675
+ <nav className="flex-1">
676
+ {/* External anchors - appear at top of sidebar on all pages */}
677
+ {renderExternalAnchors()}
678
+
679
+ {/* Sidebar tabs - when tabsPosition === 'left' */}
680
+ {renderSidebarTabs()}
681
+
682
+ {/* Divider between top section and navigation groups */}
683
+ {showTopDivider && (
684
+ <div className="mx-4 my-4 border-t border-[var(--color-border)]" />
685
+ )}
686
+
687
+ {/* Legacy anchors (deprecated - for backwards compatibility) */}
688
+ {renderAnchors()}
689
+
690
+ {/* Navigation Groups */}
691
+ <div className="sidebar-nav-groups">
692
+ {resolvedNav.groups.map((group) => renderGroup(group))}
693
+ </div>
694
+ </nav>
695
+
696
+ {/* Language Selector - for sidebar-logo layout */}
697
+ {layout === 'sidebar-logo' && resolvedNav.languages.length > 1 && (
698
+ <div className="px-4 py-3 border-t border-[var(--color-border)]">
699
+ <LanguageSelector
700
+ languages={resolvedNav.languages}
701
+ position="up"
702
+ defaultLanguage={resolvedNav.languages.find((l) => l.isDefault)?.code}
703
+ />
704
+ </div>
705
+ )}
706
+ </div>{/* End scrollable content area */}
707
+ </aside>
708
+
709
+ {/* Search Modal - shown when search is opened from sidebar */}
710
+ <SearchModal isOpen={isSearchOpen} onClose={() => setIsSearchOpen(false)} />
711
+ </>
712
+ );
713
+ }