zudoku 0.3.0-dev.30 → 0.3.0-dev.32

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 (224) hide show
  1. package/dist/app/App.d.ts +1 -0
  2. package/dist/app/App.js +2 -0
  3. package/dist/app/App.js.map +1 -0
  4. package/dist/app/demo.js +15 -11
  5. package/dist/app/demo.js.map +1 -1
  6. package/dist/app/entry.client.d.ts +5 -0
  7. package/dist/app/entry.client.js +28 -0
  8. package/dist/app/entry.client.js.map +1 -0
  9. package/dist/app/entry.server.d.ts +10 -0
  10. package/dist/app/entry.server.js +107 -0
  11. package/dist/app/entry.server.js.map +1 -0
  12. package/dist/app/main.d.ts +26 -2
  13. package/dist/app/main.js +39 -15
  14. package/dist/app/main.js.map +1 -1
  15. package/dist/app/standalone.js +14 -10
  16. package/dist/app/standalone.js.map +1 -1
  17. package/dist/app/tailwind.d.ts +1 -1
  18. package/dist/app/tailwind.js +0 -4
  19. package/dist/app/tailwind.js.map +1 -1
  20. package/dist/app/zudoku-manifest.d.ts +1 -0
  21. package/dist/app/zudoku-manifest.js +20 -0
  22. package/dist/app/zudoku-manifest.js.map +1 -0
  23. package/dist/cli/cmds/dev.js +5 -0
  24. package/dist/cli/cmds/dev.js.map +1 -1
  25. package/dist/cli/dev/handler.d.ts +1 -0
  26. package/dist/cli/dev/handler.js +3 -1
  27. package/dist/cli/dev/handler.js.map +1 -1
  28. package/dist/config/config.d.ts +5 -0
  29. package/dist/lib/authentication/providers/clerk.js +2 -0
  30. package/dist/lib/authentication/providers/clerk.js.map +1 -1
  31. package/dist/lib/components/DevPortal.d.ts +5 -2
  32. package/dist/lib/components/DevPortal.js +11 -10
  33. package/dist/lib/components/DevPortal.js.map +1 -1
  34. package/dist/lib/components/ErrorPage.d.ts +6 -0
  35. package/dist/lib/components/ErrorPage.js +9 -0
  36. package/dist/lib/components/ErrorPage.js.map +1 -0
  37. package/dist/lib/components/InlineCode.d.ts +5 -0
  38. package/dist/lib/components/InlineCode.js +4 -0
  39. package/dist/lib/components/InlineCode.js.map +1 -0
  40. package/dist/lib/components/Layout.js +2 -1
  41. package/dist/lib/components/Layout.js.map +1 -1
  42. package/dist/lib/components/NotFoundPage.d.ts +1 -0
  43. package/dist/lib/components/NotFoundPage.js +12 -0
  44. package/dist/lib/components/NotFoundPage.js.map +1 -0
  45. package/dist/lib/components/SyntaxHighlight.d.ts +3 -2
  46. package/dist/lib/components/SyntaxHighlight.js +20 -22
  47. package/dist/lib/components/SyntaxHighlight.js.map +1 -1
  48. package/dist/lib/components/index.d.ts +4 -1
  49. package/dist/lib/errors/ErrorAlert.d.ts +3 -0
  50. package/dist/lib/errors/ErrorAlert.js +8 -0
  51. package/dist/lib/errors/ErrorAlert.js.map +1 -0
  52. package/dist/lib/errors/RouterError.d.ts +1 -0
  53. package/dist/lib/errors/RouterError.js +12 -0
  54. package/dist/lib/errors/RouterError.js.map +1 -0
  55. package/dist/lib/errors/ServerError.d.ts +3 -0
  56. package/dist/lib/errors/ServerError.js +6 -0
  57. package/dist/lib/errors/ServerError.js.map +1 -0
  58. package/dist/lib/errors/TopLevelError.d.ts +2 -0
  59. package/dist/lib/errors/TopLevelError.js +7 -0
  60. package/dist/lib/errors/TopLevelError.js.map +1 -0
  61. package/dist/lib/oas/parser/index.d.ts +1 -1
  62. package/dist/lib/oas/parser/index.js +38 -14
  63. package/dist/lib/oas/parser/index.js.map +1 -1
  64. package/dist/lib/plugins/api-keys/index.js +3 -8
  65. package/dist/lib/plugins/api-keys/index.js.map +1 -1
  66. package/dist/lib/plugins/markdown/generateRoutes.js.map +1 -1
  67. package/dist/lib/plugins/markdown/index.js +3 -7
  68. package/dist/lib/plugins/markdown/index.js.map +1 -1
  69. package/dist/lib/plugins/openapi/OperationList.js +11 -1
  70. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  71. package/dist/lib/plugins/openapi/OperationListItem.js +2 -1
  72. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  73. package/dist/lib/plugins/openapi/SchemaListView.js +2 -1
  74. package/dist/lib/plugins/openapi/SchemaListView.js.map +1 -1
  75. package/dist/lib/plugins/openapi/client/createMemoryClient.js +1 -1
  76. package/dist/lib/plugins/openapi/client/createMemoryClient.js.map +1 -1
  77. package/dist/lib/plugins/openapi/index.js +9 -1
  78. package/dist/lib/plugins/openapi/index.js.map +1 -1
  79. package/dist/lib/plugins/redirect/index.js +2 -3
  80. package/dist/lib/plugins/redirect/index.js.map +1 -1
  81. package/dist/lib/ui/Callout.js +1 -1
  82. package/dist/lib/ui/Callout.js.map +1 -1
  83. package/dist/lib/ui/button-variants.d.ts +1 -1
  84. package/dist/lib/util/MdxComponents.js +2 -2
  85. package/dist/lib/util/MdxComponents.js.map +1 -1
  86. package/dist/lib/util/groupBy.d.ts +1 -6
  87. package/dist/lib/util/groupBy.js +10 -8
  88. package/dist/lib/util/groupBy.js.map +1 -1
  89. package/dist/vite/build.js +23 -10
  90. package/dist/vite/build.js.map +1 -1
  91. package/dist/vite/config.d.ts +2 -2
  92. package/dist/vite/config.js +36 -8
  93. package/dist/vite/config.js.map +1 -1
  94. package/dist/vite/dev-server.d.ts +1 -1
  95. package/dist/vite/dev-server.js +15 -9
  96. package/dist/vite/dev-server.js.map +1 -1
  97. package/dist/vite/html.js +5 -4
  98. package/dist/vite/html.js.map +1 -1
  99. package/dist/vite/plugin-custom-css.d.ts +6 -0
  100. package/dist/vite/plugin-custom-css.js +55 -0
  101. package/dist/vite/plugin-custom-css.js.map +1 -0
  102. package/dist/vite/plugin-docs.js +14 -5
  103. package/dist/vite/plugin-docs.js.map +1 -1
  104. package/dist/vite/plugin-openapi-worker.js +4 -1
  105. package/dist/vite/plugin-openapi-worker.js.map +1 -1
  106. package/dist/vite/plugin.js +2 -0
  107. package/dist/vite/plugin.js.map +1 -1
  108. package/dist/vite/prerender.d.ts +1 -0
  109. package/dist/vite/prerender.js +57 -0
  110. package/dist/vite/prerender.js.map +1 -0
  111. package/lib/{DevPortalProvider--xZTs0RJ.js → DevPortalProvider-BlxLX6GG.js} +230 -250
  112. package/lib/DevPortalProvider-BlxLX6GG.js.map +1 -0
  113. package/lib/{Markdown-oJFqm0uk.js → Markdown-CL8KPvJN.js} +8 -9
  114. package/lib/{Markdown-oJFqm0uk.js.map → Markdown-CL8KPvJN.js.map} +1 -1
  115. package/lib/MdxComponents-Ev_hBHb2.js +5885 -0
  116. package/lib/MdxComponents-Ev_hBHb2.js.map +1 -0
  117. package/lib/{MdxPage-BV_9ncEk.js → MdxPage-Z3HKNTrj.js} +92 -89
  118. package/lib/MdxPage-Z3HKNTrj.js.map +1 -0
  119. package/lib/{OperationList-DfG_E0Xa.js → OperationList-KoITgfDT.js} +1967 -1785
  120. package/lib/OperationList-KoITgfDT.js.map +1 -0
  121. package/lib/Route-Bf1_D_vC.js +13 -0
  122. package/lib/{Route-CHqr53jb.js.map → Route-Bf1_D_vC.js.map} +1 -1
  123. package/lib/Select-DSa3bN4t.js +4770 -0
  124. package/lib/Select-DSa3bN4t.js.map +1 -0
  125. package/lib/assets/{worker-BXS8hiSM.js → worker-BjPv-hjP.js} +3100 -2720
  126. package/lib/assets/worker-BjPv-hjP.js.map +1 -0
  127. package/lib/hook-CTmJ6CWq.js +35 -0
  128. package/lib/hook-CTmJ6CWq.js.map +1 -0
  129. package/lib/index-BdWBDosx.js +74 -0
  130. package/lib/index-BdWBDosx.js.map +1 -0
  131. package/lib/{index-B2qLeglF.js → index-BoWzKb_9.js} +57 -41
  132. package/lib/index-BoWzKb_9.js.map +1 -0
  133. package/lib/{AnchorLink-BtVKbEwm.js → index.esm-CPEExBJE.js} +156 -168
  134. package/lib/index.esm-CPEExBJE.js.map +1 -0
  135. package/lib/jsx-runtime-CM0TzjGp.js +866 -0
  136. package/lib/jsx-runtime-CM0TzjGp.js.map +1 -0
  137. package/lib/mutation-91kw0lHb.js +208 -0
  138. package/lib/mutation-91kw0lHb.js.map +1 -0
  139. package/lib/router-CcYTwKjf.js +183 -0
  140. package/lib/router-CcYTwKjf.js.map +1 -0
  141. package/lib/zudoku.auth-clerk.js.map +1 -1
  142. package/lib/zudoku.auth-openid.js +588 -441
  143. package/lib/zudoku.auth-openid.js.map +1 -1
  144. package/lib/zudoku.components.js +330 -543
  145. package/lib/zudoku.components.js.map +1 -1
  146. package/lib/zudoku.openapi-worker.js +18 -18
  147. package/lib/zudoku.plugin-api-keys.js +143 -98
  148. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  149. package/lib/zudoku.plugin-markdown.js +2 -49
  150. package/lib/zudoku.plugin-markdown.js.map +1 -1
  151. package/lib/zudoku.plugin-openapi.js +5 -3
  152. package/lib/zudoku.plugin-openapi.js.map +1 -1
  153. package/lib/zudoku.plugin-redirect.js +6 -7
  154. package/lib/zudoku.plugin-redirect.js.map +1 -1
  155. package/package.json +3 -1
  156. package/src/app/App.tsx +0 -0
  157. package/src/app/demo.tsx +18 -13
  158. package/src/app/entry.client.tsx +51 -0
  159. package/src/app/entry.server.tsx +158 -0
  160. package/src/app/main.tsx +65 -41
  161. package/src/app/standalone.tsx +15 -11
  162. package/src/app/tailwind.ts +2 -6
  163. package/src/app/zudoku-manifest.ts +22 -0
  164. package/src/lib/authentication/providers/clerk.tsx +1 -0
  165. package/src/lib/components/DevPortal.tsx +34 -33
  166. package/src/lib/components/ErrorPage.tsx +28 -0
  167. package/src/lib/components/InlineCode.tsx +19 -0
  168. package/src/lib/components/Layout.tsx +7 -4
  169. package/src/lib/components/NotFoundPage.tsx +39 -0
  170. package/src/lib/components/SyntaxHighlight.tsx +26 -22
  171. package/src/lib/errors/ErrorAlert.tsx +21 -0
  172. package/src/lib/errors/RouterError.tsx +13 -0
  173. package/src/lib/errors/ServerError.tsx +5 -0
  174. package/src/lib/errors/TopLevelError.tsx +8 -0
  175. package/src/lib/oas/parser/index.ts +41 -22
  176. package/src/lib/plugins/api-keys/index.tsx +4 -16
  177. package/src/lib/plugins/markdown/generateRoutes.tsx +1 -1
  178. package/src/lib/plugins/markdown/index.tsx +3 -7
  179. package/src/lib/plugins/openapi/OperationList.tsx +30 -0
  180. package/src/lib/plugins/openapi/OperationListItem.tsx +3 -1
  181. package/src/lib/plugins/openapi/SchemaListView.tsx +8 -10
  182. package/src/lib/plugins/openapi/client/createMemoryClient.ts +1 -1
  183. package/src/lib/plugins/openapi/index.tsx +18 -1
  184. package/src/lib/plugins/redirect/index.tsx +2 -2
  185. package/src/lib/ui/Callout.tsx +2 -2
  186. package/src/lib/util/MdxComponents.tsx +2 -11
  187. package/src/lib/util/groupBy.ts +7 -12
  188. package/dist/lib/components/Error.d.ts +0 -1
  189. package/dist/lib/components/Error.js +0 -10
  190. package/dist/lib/components/Error.js.map +0 -1
  191. package/dist/lib/components/Router.d.ts +0 -4
  192. package/dist/lib/components/Router.js +0 -21
  193. package/dist/lib/components/Router.js.map +0 -1
  194. package/lib/AnchorLink-BtVKbEwm.js.map +0 -1
  195. package/lib/DevPortalProvider--xZTs0RJ.js.map +0 -1
  196. package/lib/MdxComponents-CsU8yR42.js +0 -3019
  197. package/lib/MdxComponents-CsU8yR42.js.map +0 -1
  198. package/lib/MdxPage-BV_9ncEk.js.map +0 -1
  199. package/lib/OperationList-DfG_E0Xa.js.map +0 -1
  200. package/lib/Route-CHqr53jb.js +0 -14
  201. package/lib/Select-CNmXi4JU.js +0 -4572
  202. package/lib/Select-CNmXi4JU.js.map +0 -1
  203. package/lib/Spinner-By5opWs5.js +0 -182
  204. package/lib/Spinner-By5opWs5.js.map +0 -1
  205. package/lib/assets/worker-BXS8hiSM.js.map +0 -1
  206. package/lib/cn-DpqTslo9.js +0 -2342
  207. package/lib/cn-DpqTslo9.js.map +0 -1
  208. package/lib/hook-kVJ4gpk5.js +0 -25
  209. package/lib/hook-kVJ4gpk5.js.map +0 -1
  210. package/lib/index-B2qLeglF.js.map +0 -1
  211. package/lib/index-CUIxJAeE.js +0 -713
  212. package/lib/index-CUIxJAeE.js.map +0 -1
  213. package/lib/index-Cr3hgaqt.js +0 -412
  214. package/lib/index-Cr3hgaqt.js.map +0 -1
  215. package/lib/index-fXFJf9Ua.js +0 -464
  216. package/lib/index-fXFJf9Ua.js.map +0 -1
  217. package/lib/jsx-runtime-D7DwziLW.js +0 -3009
  218. package/lib/jsx-runtime-D7DwziLW.js.map +0 -1
  219. package/lib/loglevel-CA34MiFn.js +0 -153
  220. package/lib/loglevel-CA34MiFn.js.map +0 -1
  221. package/lib/util-DnDPBx_j.js +0 -41
  222. package/lib/util-DnDPBx_j.js.map +0 -1
  223. package/src/lib/components/Error.tsx +0 -15
  224. package/src/lib/components/Router.tsx +0 -23
@@ -1 +1 @@
1
- {"version":3,"file":"zudoku.plugin-markdown.js","sources":["../src/lib/plugins/markdown/generateRoutes.tsx","../src/lib/plugins/markdown/index.tsx"],"sourcesContent":["import { Navigate, type RouteObject } from \"react-router-dom\";\nimport { useTopNavigationItem } from \"../../components/context/DevPortalProvider.js\";\nimport { isPathItem } from \"../../components/navigation/util.js\";\nimport { traverseNavigation } from \"../../util/traverseNavigation.js\";\n\nimport {\n MarkdownPluginDefaultOptions,\n MarkdownPluginOptions,\n} from \"./index.js\";\n\nexport const generateRoutes = (\n markdownFiles: MarkdownPluginOptions[\"markdownFiles\"],\n defaultOptions?: MarkdownPluginDefaultOptions,\n): RouteObject[] => {\n const routes = Object.entries(markdownFiles).flatMap(\n ([file, importPromise]) => {\n // @todo we can pass in the folder name and then filter the markdown files based on that path\n const match = file.match(/pages\\/(.*).mdx?$/);\n const path = match?.at(1);\n\n if (!path) return [];\n\n const pathSegments = path.split(\"/\");\n const isIndexFile = pathSegments.at(-1) === \"index\";\n const routePath = isIndexFile\n ? pathSegments.slice(0, -1).join(\"/\")\n : path;\n\n return {\n path: routePath,\n lazy: async () => {\n const { MdxPage } = await import(\"./MdxPage.js\");\n const { default: Component, ...props } = await importPromise();\n return {\n element: (\n <MdxPage\n mdxComponent={Component}\n {...props}\n defaultOptions={defaultOptions}\n />\n ),\n };\n },\n } satisfies RouteObject;\n },\n );\n\n const rootRoutes = Array.from(\n new Set(routes.map((route) => route.path.split(\"/\").at(0))),\n ).map((dir) => ({\n path: `/${dir}`,\n element: <Redirect />,\n }));\n\n return [...routes, ...rootRoutes];\n};\n\nconst Redirect = () => {\n const navItem = useTopNavigationItem();\n\n if (!navItem) return null;\n\n return traverseNavigation(navItem, (node, fullPath) => {\n if (\"children\" in node || !isPathItem(node)) return;\n return <Navigate to={fullPath} replace />;\n });\n};\n","import type { Toc } from \"@stefanprobst/rehype-extract-toc\";\nimport type { MDXProps } from \"mdx/types.js\";\nimport type { DevPortalPlugin } from \"../../core/plugins.js\";\nimport { generateRoutes } from \"./generateRoutes.js\";\n\nexport type MarkdownPluginOptions = {\n markdownFiles: Record<string, () => Promise<MDXImport>>;\n defaultOptions?: MarkdownPluginDefaultOptions;\n};\nexport type MarkdownPluginDefaultOptions = Pick<\n Frontmatter,\n \"toc\" | \"disablePager\"\n>;\n\nexport type Frontmatter = {\n title?: string;\n description?: string;\n category?: string;\n toc?: boolean;\n disablePager?: boolean;\n};\n\nexport type MDXImport = {\n tableOfContents: Toc;\n frontmatter: Frontmatter;\n default: (props: MDXProps) => JSX.Element;\n};\n\nexport const markdownPlugin = ({\n markdownFiles,\n defaultOptions,\n}: MarkdownPluginOptions): DevPortalPlugin => {\n return {\n getRoutes() {\n return generateRoutes(markdownFiles, defaultOptions);\n },\n };\n};\n"],"names":["generateRoutes","markdownFiles","defaultOptions","routes","file","importPromise","match","path","pathSegments","MdxPage","Component","props","jsx","rootRoutes","route","dir","Redirect","navItem","useTopNavigationItem","traverseNavigation","node","fullPath","isPathItem","Navigate","markdownPlugin"],"mappings":";;;;AAUa,MAAAA,IAAiB,CAC5BC,GACAC,MACkB;AAClB,QAAMC,IAAS,OAAO,QAAQF,CAAa,EAAE;AAAA,IAC3C,CAAC,CAACG,GAAMC,CAAa,MAAM;AAEnB,YAAAC,IAAQF,EAAK,MAAM,mBAAmB,GACtCG,IAAOD,KAAA,gBAAAA,EAAO,GAAG;AAEnB,UAAA,CAACC,EAAM,QAAO;AAEZ,YAAAC,IAAeD,EAAK,MAAM,GAAG;AAM5B,aAAA;AAAA,QACL,MANkBC,EAAa,GAAG,EAAE,MAAM,UAExCA,EAAa,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG,IAClCD;AAAA,QAIF,MAAM,YAAY;AAChB,gBAAM,EAAE,SAAAE,EAAA,IAAY,MAAM,OAAO,uBAAc,GACzC,EAAE,SAASC,GAAW,GAAGC,EAAM,IAAI,MAAMN;AACxC,iBAAA;AAAA,YACL,SACEO,gBAAAA,EAAA;AAAA,cAACH;AAAA,cAAA;AAAA,gBACC,cAAcC;AAAA,gBACb,GAAGC;AAAA,gBACJ,gBAAAT;AAAA,cAAA;AAAA,YACF;AAAA,UAAA;AAAA,QAGN;AAAA,MAAA;AAAA,IAEJ;AAAA,EAAA,GAGIW,IAAa,MAAM;AAAA,IACvB,IAAI,IAAIV,EAAO,IAAI,CAACW,MAAUA,EAAM,KAAK,MAAM,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAAA,EAAA,EAC1D,IAAI,CAACC,OAAS;AAAA,IACd,MAAM,IAAIA,CAAG;AAAA,IACb,+BAAUC,GAAS,EAAA;AAAA,EACnB,EAAA;AAEF,SAAO,CAAC,GAAGb,GAAQ,GAAGU,CAAU;AAClC,GAEMG,IAAW,MAAM;AACrB,QAAMC,IAAUC;AAEZ,SAACD,IAEEE,EAAmBF,GAAS,CAACG,GAAMC,MAAa;AACrD,QAAI,gBAAcD,KAAQ,CAACE,EAAWF,CAAI;AAC1C,aAAQR,gBAAAA,EAAAA,IAAAW,GAAA,EAAS,IAAIF,GAAU,SAAO,GAAC,CAAA;AAAA,EAAA,CACxC,IALoB;AAMvB,GCtCaG,IAAiB,CAAC;AAAA,EAC7B,eAAAvB;AAAA,EACA,gBAAAC;AACF,OACS;AAAA,EACL,YAAY;AACH,WAAAF,EAAeC,GAAeC,CAAc;AAAA,EACrD;AAAA;"}
1
+ {"version":3,"file":"zudoku.plugin-markdown.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -1,8 +1,10 @@
1
- import "./jsx-runtime-D7DwziLW.js";
2
- import { o as t } from "./index-B2qLeglF.js";
1
+ import "./jsx-runtime-CM0TzjGp.js";
2
+ import { o as n } from "./index-BoWzKb_9.js";
3
3
  import "./urql-DMlBWUKL.js";
4
4
  import "virtual:zudoku-openapi-worker";
5
+ import "./MdxComponents-Ev_hBHb2.js";
6
+ import "./router-CcYTwKjf.js";
5
7
  export {
6
- t as openApiPlugin
8
+ n as openApiPlugin
7
9
  };
8
10
  //# sourceMappingURL=zudoku.plugin-openapi.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"zudoku.plugin-openapi.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
1
+ {"version":3,"file":"zudoku.plugin-openapi.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
@@ -1,12 +1,11 @@
1
- import { j as s } from "./jsx-runtime-D7DwziLW.js";
2
- import { N as i } from "./index-fXFJf9Ua.js";
3
- const p = (t) => ({
4
- getRoutes: () => t.redirects.map(({ from: e, to: r, replace: o }) => ({
5
- path: e,
6
- element: /* @__PURE__ */ s.jsx(i, { to: r, replace: o })
1
+ import { b as o } from "./router-CcYTwKjf.js";
2
+ const i = (e) => ({
3
+ getRoutes: () => e.redirects.map(({ from: r, to: t, replace: a }) => ({
4
+ path: r,
5
+ loader: () => o(t)
7
6
  }))
8
7
  });
9
8
  export {
10
- p as redirectPlugin
9
+ i as redirectPlugin
11
10
  };
12
11
  //# sourceMappingURL=zudoku.plugin-redirect.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"zudoku.plugin-redirect.js","sources":["../src/lib/plugins/redirect/index.tsx"],"sourcesContent":["import { Navigate } from \"react-router-dom\";\nimport type { DevPortalPlugin } from \"../../core/plugins.js\";\n\nexport type Redirect = {\n from: string;\n to: string;\n replace?: boolean;\n};\n\nexport const redirectPlugin = (options: {\n redirects: Redirect[];\n}): DevPortalPlugin => {\n return {\n getRoutes: () =>\n options.redirects.map(({ from, to, replace }) => ({\n path: from,\n element: <Navigate to={to} replace={replace} />,\n })),\n };\n};\n"],"names":["redirectPlugin","options","from","to","replace","jsx","Navigate"],"mappings":";;AASa,MAAAA,IAAiB,CAACC,OAGtB;AAAA,EACL,WAAW,MACTA,EAAQ,UAAU,IAAI,CAAC,EAAE,MAAAC,GAAM,IAAAC,GAAI,SAAAC,SAAe;AAAA,IAChD,MAAMF;AAAA,IACN,SAASG,gBAAAA,EAAAA,IAACC,GAAS,EAAA,IAAAH,GAAQ,SAAAC,EAAkB,CAAA;AAAA,EAAA,EAC7C;AAAA;"}
1
+ {"version":3,"file":"zudoku.plugin-redirect.js","sources":["../src/lib/plugins/redirect/index.tsx"],"sourcesContent":["import { redirect } from \"react-router-dom\";\nimport type { DevPortalPlugin } from \"../../core/plugins.js\";\n\nexport type Redirect = {\n from: string;\n to: string;\n replace?: boolean;\n};\n\nexport const redirectPlugin = (options: {\n redirects: Redirect[];\n}): DevPortalPlugin => {\n return {\n getRoutes: () =>\n options.redirects.map(({ from, to, replace }) => ({\n path: from,\n loader: () => redirect(to),\n })),\n };\n};\n"],"names":["redirectPlugin","options","from","to","replace","redirect"],"mappings":";AASa,MAAAA,IAAiB,CAACC,OAGtB;AAAA,EACL,WAAW,MACTA,EAAQ,UAAU,IAAI,CAAC,EAAE,MAAAC,GAAM,IAAAC,GAAI,SAAAC,SAAe;AAAA,IAChD,MAAMF;AAAA,IACN,QAAQ,MAAMG,EAASF,CAAE;AAAA,EAAA,EACzB;AAAA;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.3.0-dev.30",
3
+ "version": "0.3.0-dev.32",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -64,6 +64,7 @@
64
64
  "dependencies": {
65
65
  "@envelop/core": "5.0.1",
66
66
  "@graphql-typed-document-node/core": "3.2.0",
67
+ "@hiogawa/vite-plugin-ssr-css": "0.0.0",
67
68
  "@lekoarts/rehype-meta-as-attributes": "3.0.1",
68
69
  "@mdx-js/react": "3.0.1",
69
70
  "@mdx-js/rollup": "3.0.1",
@@ -94,6 +95,7 @@
94
95
  "prismjs": "1.29.0",
95
96
  "react": "18.3.1",
96
97
  "react-dom": "18.3.1",
98
+ "react-error-boundary": "^4.0.13",
97
99
  "react-helmet-async": "2.0.5",
98
100
  "react-hook-form": "^7.52.1",
99
101
  "react-markdown": "9.0.1",
File without changes
package/src/app/demo.tsx CHANGED
@@ -1,26 +1,25 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+
4
+ // Styles
1
5
  import "./main.css";
2
6
 
7
+ // Logger
8
+ import logger from "loglevel";
3
9
  import "../lib/util/logInit.js";
4
10
 
5
11
  // Base React Component
6
12
  import { DevPortal } from "../lib/components/DevPortal.js";
7
13
 
8
- import logger from "loglevel";
9
- import { StrictMode } from "react";
10
- import { createRoot } from "react-dom/client";
11
- import type { NavigationItem } from "../lib/core/DevPortalContext.js";
14
+ // Plugins
12
15
  import { openApiPlugin } from "../lib/plugins/openapi/index.js";
13
16
 
14
- const navigation: NavigationItem[] = [
15
- {
16
- label: "API Reference",
17
- path: "/demo",
18
- categories: [],
19
- },
20
- ];
21
-
22
17
  const apiUrl = new URL(window.location.href).searchParams.get("api-url");
23
18
  logger.info(`API URL: ${apiUrl}`);
19
+
20
+ // IMPORTANT: This component must not contain tailwind classes
21
+ // This directory is not processed by the tailwind plugin
22
+
24
23
  createRoot(document.getElementById("root")!).render(
25
24
  <StrictMode>
26
25
  <DevPortal
@@ -28,7 +27,13 @@ createRoot(document.getElementById("root")!).render(
28
27
  logo: "https://cdn.zudoku.dev/logos/icon.svg",
29
28
  pageTitle: "Developer Portal",
30
29
  }}
31
- navigation={navigation}
30
+ navigation={[
31
+ {
32
+ label: "API Reference",
33
+ path: "/demo",
34
+ categories: [],
35
+ },
36
+ ]}
32
37
  plugins={[openApiPlugin({ type: "url", input: apiUrl!, path: "/demo" })]}
33
38
  />
34
39
  </StrictMode>,
@@ -0,0 +1,51 @@
1
+ import { StrictMode } from "react";
2
+ import { hydrateRoot } from "react-dom/client";
3
+ import { HelmetProvider } from "react-helmet-async";
4
+ import {
5
+ createBrowserRouter,
6
+ matchRoutes,
7
+ type RouteObject,
8
+ RouterProvider,
9
+ } from "react-router-dom";
10
+ import "virtual:vite-zudoku-custom-css.css";
11
+ import config from "virtual:zudoku-config";
12
+
13
+ import "virtual:vite-zudoku-custom-css.css";
14
+ import "./main.css";
15
+
16
+ import { getRoutesByConfig } from "./main.js";
17
+
18
+ void hydrate();
19
+
20
+ export async function hydrateLazyRoutes(routes: RouteObject[]) {
21
+ const lazyMatches = matchRoutes(routes, window.location)?.filter(
22
+ (m) => m.route.lazy,
23
+ );
24
+
25
+ if (lazyMatches?.length) {
26
+ await Promise.all(
27
+ lazyMatches.map(async (m) => {
28
+ const routeModule = await m.route.lazy!();
29
+ Object.assign(m.route, { ...routeModule, lazy: undefined });
30
+ }),
31
+ );
32
+ }
33
+ }
34
+
35
+ async function hydrate() {
36
+ const routes = getRoutesByConfig(config);
37
+
38
+ await hydrateLazyRoutes(routes);
39
+
40
+ const router = createBrowserRouter(routes);
41
+ const root = document.getElementById("root")!;
42
+
43
+ hydrateRoot(
44
+ root,
45
+ <StrictMode>
46
+ <HelmetProvider>
47
+ <RouterProvider router={router} />
48
+ </HelmetProvider>
49
+ </StrictMode>,
50
+ );
51
+ }
@@ -0,0 +1,158 @@
1
+ import type express from "express";
2
+ import logger from "loglevel";
3
+ import { Transform } from "node:stream";
4
+ import { StrictMode } from "react";
5
+ import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
6
+ import { HelmetData, HelmetProvider } from "react-helmet-async";
7
+ import { isRouteErrorResponse } from "react-router-dom";
8
+ import {
9
+ createStaticHandler,
10
+ createStaticRouter,
11
+ StaticRouterProvider,
12
+ } from "react-router-dom/server.js";
13
+ import "virtual:vite-zudoku-custom-css.css";
14
+ import config from "virtual:zudoku-config";
15
+ import { ServerError } from "../lib/errors/ServerError.js";
16
+ import "./main.css";
17
+ import { getRoutesByConfig } from "./main.js";
18
+
19
+ export const render = async ({
20
+ template,
21
+ expressRequest,
22
+ fetchRequest,
23
+ response,
24
+ }: {
25
+ template: string;
26
+ expressRequest?: express.Request;
27
+ fetchRequest?: Request;
28
+ response: express.Response;
29
+ }) => {
30
+ const routes = getRoutesByConfig(config);
31
+ const { query, dataRoutes } = createStaticHandler(routes);
32
+
33
+ const request = expressRequest
34
+ ? createFetchRequest(expressRequest, response)
35
+ : fetchRequest;
36
+
37
+ if (!request) {
38
+ throw new Error("Either fetchRequest or expressRequest must be provided");
39
+ }
40
+ const context = await query(request);
41
+ let status = 200;
42
+
43
+ if (context instanceof Response) {
44
+ if ([301, 302, 303, 307, 308].includes(context.status)) {
45
+ return response.redirect(
46
+ context.status,
47
+ context.headers.get("Location")!,
48
+ );
49
+ }
50
+
51
+ throw context;
52
+ } else if (context.errors) {
53
+ // when throwing a Response from a loader it will be caught here
54
+ // unfortunately it is not `instanceof Response` for some reason
55
+ const firstError = Object.values(context.errors).find(isRouteErrorResponse);
56
+
57
+ if (firstError?.status) {
58
+ status = firstError.status;
59
+ }
60
+ }
61
+
62
+ const router = createStaticRouter(dataRoutes, context);
63
+ const helmetContext = {} as HelmetData["context"];
64
+
65
+ const { pipe } = renderToPipeableStream(
66
+ <StrictMode>
67
+ <HelmetProvider context={helmetContext}>
68
+ <StaticRouterProvider router={router} context={context} />
69
+ </HelmetProvider>
70
+ </StrictMode>,
71
+ {
72
+ onShellError(error) {
73
+ response.status(500);
74
+ response.set({ "Content-Type": "text/html" });
75
+
76
+ const html = renderToStaticMarkup(<ServerError error={error} />);
77
+
78
+ response.send(html);
79
+ },
80
+ // for SSG we could use onAllReady instead of onShellReady
81
+ // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
82
+ onShellReady() {
83
+ response.set({ "Content-Type": "text/html" });
84
+ response.status(status);
85
+
86
+ const transformStream = new Transform({
87
+ transform(chunk, encoding, callback) {
88
+ response.write(chunk, encoding);
89
+ callback();
90
+ },
91
+ });
92
+
93
+ const [htmlStart, htmlEnd] = template.split("<!--app-html-->");
94
+
95
+ response.write(
96
+ htmlStart.replace(
97
+ "<!--app-helmet-->",
98
+ [
99
+ helmetContext.helmet.title.toString(),
100
+ helmetContext.helmet.meta.toString(),
101
+ helmetContext.helmet.link.toString(),
102
+ helmetContext.helmet.style.toString(),
103
+ helmetContext.helmet.script.toString(),
104
+ ].join("\n"),
105
+ ),
106
+ );
107
+
108
+ transformStream.on("finish", () => {
109
+ response.end(htmlEnd);
110
+ });
111
+
112
+ pipe(transformStream);
113
+ },
114
+ onError(error) {
115
+ status = 500;
116
+ logger.error(error);
117
+ },
118
+ },
119
+ );
120
+ };
121
+
122
+ export function createFetchRequest(
123
+ req: express.Request,
124
+ res: express.Response,
125
+ ): Request {
126
+ const origin = `${req.protocol}://${req.get("host")}`;
127
+ // Note: This had to take originalUrl into account for presumably vite's proxying
128
+ const url = new URL(req.originalUrl || req.url, origin);
129
+
130
+ const controller = new AbortController();
131
+ res.on("close", () => controller.abort());
132
+
133
+ const headers = new Headers();
134
+
135
+ for (const [key, values] of Object.entries(req.headers)) {
136
+ if (values) {
137
+ if (Array.isArray(values)) {
138
+ for (const value of values) {
139
+ headers.append(key, value);
140
+ }
141
+ } else {
142
+ headers.set(key, values);
143
+ }
144
+ }
145
+ }
146
+
147
+ const init: RequestInit = {
148
+ method: req.method,
149
+ headers,
150
+ signal: controller.signal,
151
+ };
152
+
153
+ if (req.method !== "GET" && req.method !== "HEAD") {
154
+ init.body = req.body;
155
+ }
156
+
157
+ return new Request(url.href, init);
158
+ }
package/src/app/main.tsx CHANGED
@@ -1,49 +1,73 @@
1
- import { StrictMode } from "react";
2
- import { createRoot } from "react-dom/client";
3
-
4
- import "./main.css";
5
-
6
- import "../lib/util/logInit.js";
7
-
8
- // Virtual config
9
- import config from "virtual:zudoku-config";
10
-
11
- // Virtual Plugins
1
+ import { type RouteObject } from "react-router-dom";
12
2
  import { configuredApiKeysPlugin } from "virtual:zudoku-api-keys-plugin";
13
3
  import { configuredApiPlugins } from "virtual:zudoku-api-plugins";
14
4
  import { configuredAuthProvider } from "virtual:zudoku-auth";
15
5
  import { configuredDocsPlugins } from "virtual:zudoku-docs-plugins";
16
6
  import { configuredRedirectPlugin } from "virtual:zudoku-redirect-plugin";
17
-
18
- // Base React Component
19
7
  import { DevPortal } from "zudoku/components";
8
+ import type { ZudokuConfig } from "../config/config.js";
9
+ import { Layout } from "../lib/components/Layout.js";
10
+ import { isNavigationPlugin } from "../lib/core/plugins.js";
11
+ import { RouterError } from "../lib/errors/RouterError.js";
12
+
13
+ import "virtual:vite-zudoku-custom-css.css";
14
+
15
+ export const convertZudokuConfigToOptions = (config: ZudokuConfig) => {
16
+ return {
17
+ page: {
18
+ logo: config.page?.logo ?? "https://cdn.zudoku.dev/logos/icon.svg",
19
+ pageTitle: "Developer Portal",
20
+ ...config.page,
21
+ },
22
+ metadata: {
23
+ favicon: "https://cdn.zudoku.dev/logos/icon.svg",
24
+ title: "%s | Developer Portal",
25
+ ...config.metadata,
26
+ },
27
+ navigation: config.navigation ?? [],
28
+ authentication: configuredAuthProvider,
29
+ plugins: [
30
+ ...configuredDocsPlugins,
31
+ ...configuredApiPlugins,
32
+ ...(configuredRedirectPlugin ? [configuredRedirectPlugin] : []),
33
+ ...(configuredApiKeysPlugin ? [configuredApiKeysPlugin] : []),
34
+ ...(configuredAuthProvider ? [configuredAuthProvider] : []),
35
+ ],
36
+ };
37
+ };
38
+
39
+ export const getRoutesByConfig = (config: ZudokuConfig): RouteObject[] => {
40
+ const options = convertZudokuConfigToOptions(config);
41
+
42
+ const allPlugins = [
43
+ ...options.plugins,
44
+ ...(options.authentication ? [options.authentication] : []),
45
+ ];
20
46
 
21
- // IMPORTANT: This component must not contain tailwind classes
22
- // This directory is not processed by the tailwind plugin
47
+ const routes = allPlugins
48
+ .flatMap((plugin) => (isNavigationPlugin(plugin) ? plugin.getRoutes() : []))
49
+ .concat({
50
+ path: "*",
51
+ loader: () => {
52
+ throw new Response("Not Found", { status: 404 });
53
+ },
54
+ });
23
55
 
24
- createRoot(document.getElementById("root")!).render(
25
- <StrictMode>
26
- <DevPortal
27
- page={{
28
- logo: config.page?.logo ?? "https://cdn.zudoku.dev/logos/icon.svg",
29
- pageTitle: "Developer Portal",
30
- ...config.page,
31
- }}
32
- metadata={{
33
- favicon: "https://cdn.zudoku.dev/logos/icon.svg",
34
- title: "%s | Developer Portal",
35
- ...config.metadata,
36
- }}
37
- navigation={config.navigation ?? []}
38
- authentication={configuredAuthProvider}
39
- mdxComponents={config.mdx?.components}
40
- plugins={[
41
- ...configuredDocsPlugins,
42
- ...configuredApiPlugins,
43
- configuredRedirectPlugin,
44
- ...(configuredApiKeysPlugin ? [configuredApiKeysPlugin] : []),
45
- ...(configuredAuthProvider ? [configuredAuthProvider] : []),
46
- ]}
47
- />
48
- </StrictMode>,
49
- );
56
+ return [
57
+ {
58
+ element: (
59
+ <DevPortal {...options}>
60
+ <Layout />
61
+ </DevPortal>
62
+ ),
63
+ errorElement: (
64
+ <DevPortal {...options}>
65
+ <Layout>
66
+ <RouterError />
67
+ </Layout>
68
+ </DevPortal>
69
+ ),
70
+ children: routes,
71
+ },
72
+ ];
73
+ };
@@ -1,13 +1,16 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+
4
+ // Styles
1
5
  import "./main.css";
2
6
 
7
+ // Logger
3
8
  import "../lib/util/logInit.js";
4
9
 
5
10
  // Base React Component
6
11
  import { DevPortal } from "../lib/components/DevPortal.js";
7
12
 
8
- import { StrictMode } from "react";
9
- import { createRoot } from "react-dom/client";
10
- import type { NavigationItem } from "../lib/core/DevPortalContext.js";
13
+ // Plugins
11
14
  import { openApiPlugin } from "../lib/plugins/openapi/index.js";
12
15
 
13
16
  const root = document.querySelector("[data-api-url]");
@@ -19,13 +22,8 @@ const apiUrl = root.getAttribute("data-api-url");
19
22
  const pageTitle = document.getElementsByTagName("title")[0].innerText;
20
23
  const logoUrl = root.getAttribute("data-logo-url");
21
24
 
22
- const navigation: NavigationItem[] = [
23
- {
24
- label: "API Reference",
25
- path: "/",
26
- categories: [],
27
- },
28
- ];
25
+ // IMPORTANT: This component must not contain tailwind classes
26
+ // This directory is not processed by the tailwind plugin
29
27
 
30
28
  createRoot(root).render(
31
29
  <StrictMode>
@@ -35,7 +33,13 @@ createRoot(root).render(
35
33
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
36
34
  pageTitle: pageTitle ?? "Developer Portal",
37
35
  }}
38
- navigation={navigation}
36
+ navigation={[
37
+ {
38
+ label: "API Reference",
39
+ path: "/",
40
+ categories: [],
41
+ },
42
+ ]}
39
43
  plugins={[openApiPlugin({ type: "url", input: apiUrl!, path: "/" })]}
40
44
  />
41
45
  </StrictMode>,
@@ -1,17 +1,13 @@
1
1
  import typographyPlugin from "@tailwindcss/typography";
2
2
  import type { Config } from "tailwindcss";
3
3
  import defaultTheme from "tailwindcss/defaultTheme.js";
4
+
4
5
  const content = [
5
6
  "./src/**/*.{js,ts,jsx,tsx,md,mdx}",
6
7
  "./node_modules/zudoku/dist/**/*.{js,ts,jsx,tsx,md,mdx}",
7
8
  ];
8
9
 
9
- // ,
10
- // if (process.env.NODE_ENV === "development") {
11
- // content.push("../../plugins/*/src/**/*.{js,ts,jsx,tsx}");
12
- // }
13
-
14
- const config: Config = {
10
+ const config: Omit<Config, "content"> = {
15
11
  darkMode: "selector",
16
12
  content,
17
13
  theme: {
@@ -0,0 +1,22 @@
1
+ import type { RouteObject } from "react-router-dom";
2
+ import config from "virtual:zudoku-config";
3
+ import { getRoutesByConfig } from "./main.js";
4
+
5
+ const routes = getRoutesByConfig(config);
6
+
7
+ const flattenPaths = (routes: RouteObject[], basePath = "") => {
8
+ return routes.reduce<string[]>((acc, route) => {
9
+ if (route.path) {
10
+ const fullPath = `${basePath}/${route.path}`.replace(/\/+/g, "/");
11
+ route.path !== "/" && acc.push(fullPath);
12
+ if (route.children) {
13
+ acc = acc.concat(flattenPaths(route.children, fullPath));
14
+ }
15
+ } else if (route.children) {
16
+ acc = acc.concat(flattenPaths(route.children, basePath));
17
+ }
18
+ return acc;
19
+ }, []);
20
+ };
21
+
22
+ export const paths = flattenPaths(routes);
@@ -9,6 +9,7 @@ const clerkAuth: AuthenticationProviderInitializer<
9
9
  let clerkApi: Clerk;
10
10
 
11
11
  const ensureLoaded = (async () => {
12
+ if (import.meta.env.SSR) return;
12
13
  const { Clerk } = await import("@clerk/clerk-js");
13
14
  clerkApi = new Clerk(clerkPubKey);
14
15
 
@@ -1,13 +1,21 @@
1
1
  import { MDXProvider } from "@mdx-js/react";
2
2
  import { QueryClientProvider } from "@tanstack/react-query";
3
- import { Fragment, memo, Suspense, useEffect, useMemo } from "react";
4
- import { Helmet, HelmetProvider } from "react-helmet-async";
3
+ import {
4
+ Fragment,
5
+ memo,
6
+ type PropsWithChildren,
7
+ useEffect,
8
+ useMemo,
9
+ } from "react";
10
+ import { ErrorBoundary } from "react-error-boundary";
11
+ import { Helmet } from "react-helmet-async";
5
12
  import {
6
13
  DevPortalContext,
7
14
  queryClient,
8
15
  ZudokuContextOptions,
9
16
  } from "../core/DevPortalContext.js";
10
17
  import { hasHead } from "../core/plugins.js";
18
+ import { TopLevelError } from "../errors/TopLevelError.js";
11
19
  import { MdxComponents } from "../util/MdxComponents.js";
12
20
  import {
13
21
  ComponentsProvider,
@@ -16,9 +24,8 @@ import {
16
24
  import { DevPortalProvider } from "./context/DevPortalProvider.js";
17
25
  import { ThemeProvider } from "./context/ThemeContext.js";
18
26
  import { ViewportAnchorProvider } from "./context/ViewportAnchorContext.js";
19
- import { Router } from "./Router.js";
20
27
 
21
- const DevPortalSystemPaths = {
28
+ export const DevPortalSystemPaths = {
22
29
  Settings: "/settings",
23
30
  } as const;
24
31
 
@@ -26,7 +33,10 @@ export type DevPortalPath =
26
33
  | string
27
34
  | (typeof DevPortalSystemPaths)[keyof typeof DevPortalSystemPaths];
28
35
 
29
- const DevPortalInner = (props: ZudokuContextOptions) => {
36
+ const DevPortalInner = ({
37
+ children,
38
+ ...props
39
+ }: PropsWithChildren<ZudokuContextOptions>) => {
30
40
  const components = useMemo(
31
41
  () => ({ ...DEFAULT_COMPONENTS, ...props.overrides }),
32
42
  [props.overrides],
@@ -50,38 +60,29 @@ const DevPortalInner = (props: ZudokuContextOptions) => {
50
60
 
51
61
  return (
52
62
  <QueryClientProvider client={queryClient}>
53
- <HelmetProvider>
54
- <Helmet>{heads}</Helmet>
55
- <DevPortalProvider value={devPortalContext}>
56
- <MDXProvider components={mdxComponents}>
57
- <ThemeProvider>
58
- <ComponentsProvider value={components}>
59
- <ViewportAnchorProvider>
60
- <Suspense
61
- fallback={
62
- <div className="grid place-items-center h-full">
63
- Loading...
64
- </div>
65
- }
66
- >
67
- <Router
68
- plugins={[
69
- ...(props.plugins ?? []),
70
- ...(props.authentication ? [props.authentication] : []),
71
- ]}
72
- />
73
- </Suspense>
74
- </ViewportAnchorProvider>
75
- </ComponentsProvider>
76
- </ThemeProvider>
77
- </MDXProvider>
78
- </DevPortalProvider>
79
- </HelmetProvider>
63
+ <Helmet>{heads}</Helmet>
64
+ <DevPortalProvider value={devPortalContext}>
65
+ <MDXProvider components={mdxComponents}>
66
+ <ThemeProvider>
67
+ <ComponentsProvider value={components}>
68
+ <ViewportAnchorProvider>{children}</ViewportAnchorProvider>
69
+ </ComponentsProvider>
70
+ </ThemeProvider>
71
+ </MDXProvider>
72
+ </DevPortalProvider>
80
73
  </QueryClientProvider>
81
74
  );
82
75
  };
83
76
 
84
- const DevPortal = memo(DevPortalInner);
77
+ const Inner = memo(DevPortalInner);
78
+
79
+ const DevPortal = (props: ZudokuContextOptions) => {
80
+ return (
81
+ <ErrorBoundary FallbackComponent={TopLevelError}>
82
+ <Inner {...props} />
83
+ </ErrorBoundary>
84
+ );
85
+ };
85
86
  DevPortal.displayName = "DevPortal";
86
87
 
87
88
  export { DevPortal };