@brainfish-ai/devdoc 0.1.41 → 0.1.43

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 (400) hide show
  1. package/ai-agents/.claude/skills/bootstrap-docs/SKILL.md +710 -79
  2. package/ai-agents/.claude/skills/check-docs/SKILL.md +83 -8
  3. package/ai-agents/.claude/skills/create-doc/SKILL.md +267 -55
  4. package/ai-agents/.claude/skills/update-doc/SKILL.md +162 -63
  5. package/ai-agents/.cursor/rules/devdoc-bootstrap.mdc +145 -15
  6. package/ai-agents/.cursor/rules/devdoc-create.mdc +108 -57
  7. package/ai-agents/.cursor/rules/devdoc-update.mdc +93 -70
  8. package/ai-agents/.cursor/rules/devdoc.mdc +21 -0
  9. package/ai-agents/schemas/docs.schema.json +332 -0
  10. package/ai-agents/schemas/theme.schema.json +243 -0
  11. package/dist/cli/commands/create.js +4 -9
  12. package/dist/cli/commands/deploy.js +50 -25
  13. package/dist/cli/commands/dev.js +19 -10
  14. package/package.json +3 -2
  15. package/renderer/app/api/assets/[...path]/route.js +108 -0
  16. package/renderer/app/api/assets/route.js +114 -0
  17. package/renderer/app/api/assets/upload/route.js +163 -0
  18. package/renderer/app/api/auth-schemes/route.js +58 -0
  19. package/renderer/app/api/chat/route.js +759 -0
  20. package/renderer/app/api/codegen/route.js +52 -0
  21. package/renderer/app/api/collections/route.js +675 -0
  22. package/renderer/app/api/debug/route.js +47 -0
  23. package/renderer/app/api/deploy/route.js +199 -0
  24. package/renderer/app/api/device/route.js +36 -0
  25. package/renderer/app/api/docs/route.js +205 -0
  26. package/renderer/app/api/domains/add/route.js +121 -0
  27. package/renderer/app/api/domains/lookup/route.js +43 -0
  28. package/renderer/app/api/domains/remove/route.js +89 -0
  29. package/renderer/app/api/domains/status/route.js +140 -0
  30. package/renderer/app/api/domains/verify/route.js +168 -0
  31. package/renderer/app/api/keys/regenerate/route.js +71 -0
  32. package/renderer/app/api/local-assets/[...path]/route.js +108 -0
  33. package/renderer/app/api/openapi-spec/route.js +73 -0
  34. package/renderer/app/api/projects/[slug]/route.js +129 -0
  35. package/renderer/app/api/projects/[slug]/stats/route.js +80 -0
  36. package/renderer/app/api/projects/register/route.js +176 -0
  37. package/renderer/app/api/proxy/route.js +139 -0
  38. package/renderer/app/api/proxy-stream/route.js +156 -0
  39. package/renderer/app/api/redirects/route.js +35 -0
  40. package/renderer/app/api/schema/route.js +85 -0
  41. package/renderer/app/api/subdomains/check/route.js +158 -0
  42. package/renderer/app/api/suggestions/route.js +175 -0
  43. package/renderer/app/layout.js +47 -0
  44. package/renderer/app/llms-full.txt/route.js +257 -0
  45. package/renderer/app/llms.txt/route.js +219 -0
  46. package/renderer/app/page.js +12 -0
  47. package/renderer/app/robots.txt/route.js +66 -0
  48. package/renderer/app/sitemap.xml/route.js +145 -0
  49. package/renderer/components/docs/index.js +8 -0
  50. package/renderer/components/docs/mdx/accordion.js +113 -0
  51. package/renderer/components/docs/mdx/badge.js +72 -0
  52. package/renderer/components/docs/mdx/callouts.js +137 -0
  53. package/renderer/components/docs/mdx/cards.js +175 -0
  54. package/renderer/components/docs/mdx/changelog.js +100 -0
  55. package/renderer/components/docs/mdx/code-block.js +147 -0
  56. package/renderer/components/docs/mdx/code-group.js +287 -0
  57. package/renderer/components/docs/mdx/file-embeds.js +82 -0
  58. package/renderer/components/docs/mdx/frame.js +59 -0
  59. package/renderer/components/docs/mdx/highlight.js +90 -0
  60. package/renderer/components/docs/mdx/iframe.js +69 -0
  61. package/renderer/components/docs/mdx/image.js +135 -0
  62. package/renderer/components/docs/mdx/index.js +134 -0
  63. package/renderer/components/docs/mdx/landing.js +315 -0
  64. package/renderer/components/docs/mdx/mermaid.js +212 -0
  65. package/renderer/components/docs/mdx/param-field.js +112 -0
  66. package/renderer/components/docs/mdx/steps.js +74 -0
  67. package/renderer/components/docs/mdx/tabs.js +50 -0
  68. package/renderer/components/docs/mdx-renderer.js +77 -0
  69. package/renderer/components/docs/navigation/breadcrumbs.js +64 -0
  70. package/renderer/components/docs/navigation/index.js +6 -0
  71. package/renderer/components/docs/navigation/page-nav.js +57 -0
  72. package/renderer/components/docs/navigation/sidebar.js +375 -0
  73. package/renderer/components/docs/navigation/toc.js +89 -0
  74. package/renderer/components/docs/notice.js +77 -0
  75. package/renderer/components/docs-header.js +202 -0
  76. package/renderer/components/docs-viewer/agent/agent-chat.js +1930 -0
  77. package/renderer/components/docs-viewer/agent/cards/debug-context-card.js +107 -0
  78. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.js +57 -0
  79. package/renderer/components/docs-viewer/agent/cards/index.js +45 -0
  80. package/renderer/components/docs-viewer/agent/cards/response-options-card.js +154 -0
  81. package/renderer/components/docs-viewer/agent/cards/types.js +22 -0
  82. package/renderer/components/docs-viewer/agent/chat-message.js +2 -0
  83. package/renderer/components/docs-viewer/agent/index.js +4 -0
  84. package/renderer/components/docs-viewer/agent/messages/assistant-message.js +108 -0
  85. package/renderer/components/docs-viewer/agent/messages/chat-message.js +34 -0
  86. package/renderer/components/docs-viewer/agent/messages/index.js +6 -0
  87. package/renderer/components/docs-viewer/agent/messages/tool-call-display.js +1065 -0
  88. package/renderer/components/docs-viewer/agent/messages/types.js +2 -0
  89. package/renderer/components/docs-viewer/agent/messages/typing-indicator.js +26 -0
  90. package/renderer/components/docs-viewer/agent/messages/user-message.js +37 -0
  91. package/renderer/components/docs-viewer/code-editor/{index.tsx → index.js} +1 -1
  92. package/renderer/components/docs-viewer/code-editor/notes-mode.js +1338 -0
  93. package/renderer/components/docs-viewer/content/changelog-page.js +297 -0
  94. package/renderer/components/docs-viewer/content/doc-page.js +264 -0
  95. package/renderer/components/docs-viewer/content/documentation-viewer.js +14 -0
  96. package/renderer/components/docs-viewer/content/index.js +29 -0
  97. package/renderer/components/docs-viewer/content/not-found-page.js +300 -0
  98. package/renderer/components/docs-viewer/content/request-details.js +528 -0
  99. package/renderer/components/docs-viewer/content/sections/auth.js +108 -0
  100. package/renderer/components/docs-viewer/content/sections/body.js +80 -0
  101. package/renderer/components/docs-viewer/content/sections/headers.js +64 -0
  102. package/renderer/components/docs-viewer/content/sections/overview.js +56 -0
  103. package/renderer/components/docs-viewer/content/sections/parameters.js +64 -0
  104. package/renderer/components/docs-viewer/content/sections/responses.js +91 -0
  105. package/renderer/components/docs-viewer/global-auth-modal.js +427 -0
  106. package/renderer/components/docs-viewer/index.js +1552 -0
  107. package/renderer/components/docs-viewer/playground/auth-editor.js +418 -0
  108. package/renderer/components/docs-viewer/playground/body-editor.js +240 -0
  109. package/renderer/components/docs-viewer/playground/code-editor.js +135 -0
  110. package/renderer/components/docs-viewer/playground/code-snippet.js +393 -0
  111. package/renderer/components/docs-viewer/playground/graphql-playground.js +734 -0
  112. package/renderer/components/docs-viewer/playground/index.js +682 -0
  113. package/renderer/components/docs-viewer/playground/key-value-editor.js +317 -0
  114. package/renderer/components/docs-viewer/playground/method-selector.js +65 -0
  115. package/renderer/components/docs-viewer/playground/request-builder.js +181 -0
  116. package/renderer/components/docs-viewer/playground/request-tabs.js +240 -0
  117. package/renderer/components/docs-viewer/playground/response-cards/idle-card.js +42 -0
  118. package/renderer/components/docs-viewer/playground/response-cards/index.js +72 -0
  119. package/renderer/components/docs-viewer/playground/response-cards/loading-card.js +24 -0
  120. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.js +28 -0
  121. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.js +308 -0
  122. package/renderer/components/docs-viewer/playground/response-cards/types.js +9 -0
  123. package/renderer/components/docs-viewer/playground/response-viewer.js +18 -0
  124. package/renderer/components/docs-viewer/search/index.js +2 -0
  125. package/renderer/components/docs-viewer/search/search-dialog.js +367 -0
  126. package/renderer/components/docs-viewer/search/use-search.js +89 -0
  127. package/renderer/components/docs-viewer/shared/markdown-renderer.js +423 -0
  128. package/renderer/components/docs-viewer/shared/method-badge.js +23 -0
  129. package/renderer/components/docs-viewer/shared/schema-viewer.js +321 -0
  130. package/renderer/components/docs-viewer/sidebar/collection-tree.js +222 -0
  131. package/renderer/components/docs-viewer/sidebar/endpoint-options.js +512 -0
  132. package/renderer/components/docs-viewer/sidebar/index.js +196 -0
  133. package/renderer/components/docs-viewer/sidebar/right-sidebar.js +163 -0
  134. package/renderer/components/docs-viewer/sidebar/sidebar-group.js +87 -0
  135. package/renderer/components/docs-viewer/sidebar/sidebar-item.js +172 -0
  136. package/renderer/components/docs-viewer/sidebar/sidebar-section.js +31 -0
  137. package/renderer/components/theme-provider.js +10 -0
  138. package/renderer/components/theme-toggle.js +106 -0
  139. package/renderer/components/ui/badge.js +29 -0
  140. package/renderer/components/ui/button.js +40 -0
  141. package/renderer/components/ui/dialog.js +50 -0
  142. package/renderer/components/ui/dropdown-menu.js +143 -0
  143. package/renderer/components/ui/input.js +12 -0
  144. package/renderer/components/ui/label.js +13 -0
  145. package/renderer/components/ui/navigation-menu.js +83 -0
  146. package/renderer/components/ui/select.js +116 -0
  147. package/renderer/components/ui/spinner.js +92 -0
  148. package/renderer/components/ui/tabs.js +34 -0
  149. package/renderer/components/ui/tooltip.js +43 -0
  150. package/renderer/hooks/use-code-copy.js +76 -0
  151. package/renderer/hooks/use-openapi-title.js +33 -0
  152. package/renderer/lib/api-docs/agent/index.js +4 -0
  153. package/renderer/lib/api-docs/agent/indexer.js +254 -0
  154. package/renderer/lib/api-docs/agent/spec-summary.js +227 -0
  155. package/renderer/lib/api-docs/agent/types.js +5 -0
  156. package/renderer/lib/api-docs/auth/auth-context.js +157 -0
  157. package/renderer/lib/api-docs/auth/auth-storage.js +66 -0
  158. package/renderer/lib/api-docs/auth/crypto.js +64 -0
  159. package/renderer/lib/api-docs/auth/index.js +3 -0
  160. package/renderer/lib/api-docs/code-editor/db.js +145 -0
  161. package/renderer/lib/api-docs/code-editor/hooks.js +254 -0
  162. package/renderer/lib/api-docs/code-editor/{index.ts → index.js} +3 -4
  163. package/renderer/lib/api-docs/code-editor/mode-context.js +154 -0
  164. package/renderer/lib/api-docs/code-editor/types.js +53 -0
  165. package/renderer/lib/api-docs/codegen/definitions.js +258 -0
  166. package/renderer/lib/api-docs/codegen/har.js +171 -0
  167. package/renderer/lib/api-docs/codegen/index.js +118 -0
  168. package/renderer/lib/api-docs/factories.js +136 -0
  169. package/renderer/lib/api-docs/{index.ts → index.js} +5 -10
  170. package/renderer/lib/api-docs/mobile-context.js +79 -0
  171. package/renderer/lib/api-docs/navigation-context.js +62 -0
  172. package/renderer/lib/api-docs/parsers/graphql/index.js +50 -0
  173. package/renderer/lib/api-docs/parsers/graphql/parser.js +350 -0
  174. package/renderer/lib/api-docs/parsers/graphql/transformer.js +215 -0
  175. package/renderer/lib/api-docs/parsers/graphql/types.js +46 -0
  176. package/renderer/lib/api-docs/parsers/openapi/dereferencer.js +43 -0
  177. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.js +486 -0
  178. package/renderer/lib/api-docs/parsers/openapi/extractors/body.js +295 -0
  179. package/renderer/lib/api-docs/parsers/openapi/extractors/index.js +132 -0
  180. package/renderer/lib/api-docs/parsers/openapi/index.js +127 -0
  181. package/renderer/lib/api-docs/parsers/openapi/transformer.js +192 -0
  182. package/renderer/lib/api-docs/parsers/openapi/validator.js +24 -0
  183. package/renderer/lib/api-docs/playground/context.js +65 -0
  184. package/renderer/lib/api-docs/playground/navigation-context.js +74 -0
  185. package/renderer/lib/api-docs/playground/request-builder.js +163 -0
  186. package/renderer/lib/api-docs/playground/request-runner.js +224 -0
  187. package/renderer/lib/api-docs/playground/types.js +5 -0
  188. package/renderer/lib/api-docs/types.js +23 -0
  189. package/renderer/lib/api-docs/utils.js +212 -0
  190. package/renderer/lib/cache.js +157 -0
  191. package/renderer/lib/docs/config/domain-schema.js +161 -0
  192. package/renderer/lib/docs/config/index.js +5 -0
  193. package/renderer/lib/docs/config/loader.js +113 -0
  194. package/renderer/lib/docs/config/schema.js +269 -0
  195. package/renderer/lib/docs/index.js +8 -0
  196. package/renderer/lib/docs/mdx/compiler.js +128 -0
  197. package/renderer/lib/docs/mdx/frontmatter.js +73 -0
  198. package/renderer/lib/docs/mdx/index.js +8 -0
  199. package/renderer/lib/docs/navigation/generator.js +269 -0
  200. package/renderer/lib/docs/navigation/index.js +4 -0
  201. package/renderer/lib/docs/navigation/types.js +9 -0
  202. package/renderer/lib/docs-navigation-context.js +40 -0
  203. package/renderer/lib/multi-tenant/context.js +80 -0
  204. package/renderer/lib/storage/blob.js +767 -0
  205. package/renderer/lib/utils/icons.js +30 -0
  206. package/renderer/lib/utils.js +5 -0
  207. package/renderer/next.config.js +62 -0
  208. package/renderer/tsconfig.json +23 -5
  209. package/renderer/app/api/assets/[...path]/route.ts +0 -123
  210. package/renderer/app/api/assets/route.ts +0 -124
  211. package/renderer/app/api/assets/upload/route.ts +0 -177
  212. package/renderer/app/api/auth-schemes/route.ts +0 -77
  213. package/renderer/app/api/chat/route.ts +0 -858
  214. package/renderer/app/api/codegen/route.ts +0 -72
  215. package/renderer/app/api/collections/route.ts +0 -1002
  216. package/renderer/app/api/debug/route.ts +0 -53
  217. package/renderer/app/api/deploy/route.ts +0 -234
  218. package/renderer/app/api/device/route.ts +0 -42
  219. package/renderer/app/api/docs/route.ts +0 -201
  220. package/renderer/app/api/domains/add/route.ts +0 -132
  221. package/renderer/app/api/domains/lookup/route.ts +0 -43
  222. package/renderer/app/api/domains/remove/route.ts +0 -100
  223. package/renderer/app/api/domains/status/route.ts +0 -158
  224. package/renderer/app/api/domains/verify/route.ts +0 -181
  225. package/renderer/app/api/keys/regenerate/route.ts +0 -80
  226. package/renderer/app/api/local-assets/[...path]/route.ts +0 -122
  227. package/renderer/app/api/openapi-spec/route.ts +0 -151
  228. package/renderer/app/api/projects/[slug]/route.ts +0 -153
  229. package/renderer/app/api/projects/[slug]/stats/route.ts +0 -96
  230. package/renderer/app/api/projects/register/route.ts +0 -152
  231. package/renderer/app/api/proxy/route.ts +0 -149
  232. package/renderer/app/api/proxy-stream/route.ts +0 -168
  233. package/renderer/app/api/redirects/route.ts +0 -47
  234. package/renderer/app/api/schema/route.ts +0 -73
  235. package/renderer/app/api/subdomains/check/route.ts +0 -172
  236. package/renderer/app/api/suggestions/route.ts +0 -144
  237. package/renderer/app/layout.tsx +0 -54
  238. package/renderer/app/llms-full.txt/route.ts +0 -346
  239. package/renderer/app/llms.txt/route.ts +0 -279
  240. package/renderer/app/page.tsx +0 -14
  241. package/renderer/app/robots.txt/route.ts +0 -84
  242. package/renderer/app/sitemap.xml/route.ts +0 -199
  243. package/renderer/components/docs/index.ts +0 -12
  244. package/renderer/components/docs/mdx/accordion.tsx +0 -169
  245. package/renderer/components/docs/mdx/badge.tsx +0 -132
  246. package/renderer/components/docs/mdx/callouts.tsx +0 -154
  247. package/renderer/components/docs/mdx/cards.tsx +0 -241
  248. package/renderer/components/docs/mdx/changelog.tsx +0 -120
  249. package/renderer/components/docs/mdx/code-block.tsx +0 -186
  250. package/renderer/components/docs/mdx/code-group.tsx +0 -421
  251. package/renderer/components/docs/mdx/file-embeds.tsx +0 -105
  252. package/renderer/components/docs/mdx/frame.tsx +0 -112
  253. package/renderer/components/docs/mdx/highlight.tsx +0 -151
  254. package/renderer/components/docs/mdx/iframe.tsx +0 -134
  255. package/renderer/components/docs/mdx/image.tsx +0 -235
  256. package/renderer/components/docs/mdx/index.ts +0 -237
  257. package/renderer/components/docs/mdx/landing.tsx +0 -684
  258. package/renderer/components/docs/mdx/mermaid.tsx +0 -240
  259. package/renderer/components/docs/mdx/param-field.tsx +0 -200
  260. package/renderer/components/docs/mdx/steps.tsx +0 -113
  261. package/renderer/components/docs/mdx/tabs.tsx +0 -86
  262. package/renderer/components/docs/mdx-renderer.tsx +0 -100
  263. package/renderer/components/docs/navigation/breadcrumbs.tsx +0 -76
  264. package/renderer/components/docs/navigation/index.ts +0 -8
  265. package/renderer/components/docs/navigation/page-nav.tsx +0 -64
  266. package/renderer/components/docs/navigation/sidebar.tsx +0 -515
  267. package/renderer/components/docs/navigation/toc.tsx +0 -113
  268. package/renderer/components/docs/notice.tsx +0 -105
  269. package/renderer/components/docs-header.tsx +0 -278
  270. package/renderer/components/docs-viewer/agent/agent-chat.tsx +0 -2076
  271. package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +0 -90
  272. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +0 -49
  273. package/renderer/components/docs-viewer/agent/cards/index.tsx +0 -50
  274. package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +0 -212
  275. package/renderer/components/docs-viewer/agent/cards/types.ts +0 -84
  276. package/renderer/components/docs-viewer/agent/chat-message.tsx +0 -17
  277. package/renderer/components/docs-viewer/agent/index.tsx +0 -6
  278. package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +0 -119
  279. package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +0 -46
  280. package/renderer/components/docs-viewer/agent/messages/index.ts +0 -17
  281. package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +0 -721
  282. package/renderer/components/docs-viewer/agent/messages/types.ts +0 -61
  283. package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +0 -24
  284. package/renderer/components/docs-viewer/agent/messages/user-message.tsx +0 -51
  285. package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +0 -1283
  286. package/renderer/components/docs-viewer/content/changelog-page.tsx +0 -331
  287. package/renderer/components/docs-viewer/content/doc-page.tsx +0 -367
  288. package/renderer/components/docs-viewer/content/documentation-viewer.tsx +0 -17
  289. package/renderer/components/docs-viewer/content/index.tsx +0 -29
  290. package/renderer/components/docs-viewer/content/not-found-page.tsx +0 -330
  291. package/renderer/components/docs-viewer/content/request-details.tsx +0 -330
  292. package/renderer/components/docs-viewer/content/sections/auth.tsx +0 -69
  293. package/renderer/components/docs-viewer/content/sections/body.tsx +0 -66
  294. package/renderer/components/docs-viewer/content/sections/headers.tsx +0 -43
  295. package/renderer/components/docs-viewer/content/sections/overview.tsx +0 -40
  296. package/renderer/components/docs-viewer/content/sections/parameters.tsx +0 -43
  297. package/renderer/components/docs-viewer/content/sections/responses.tsx +0 -87
  298. package/renderer/components/docs-viewer/global-auth-modal.tsx +0 -352
  299. package/renderer/components/docs-viewer/index.tsx +0 -1662
  300. package/renderer/components/docs-viewer/playground/auth-editor.tsx +0 -280
  301. package/renderer/components/docs-viewer/playground/body-editor.tsx +0 -221
  302. package/renderer/components/docs-viewer/playground/code-editor.tsx +0 -224
  303. package/renderer/components/docs-viewer/playground/code-snippet.tsx +0 -387
  304. package/renderer/components/docs-viewer/playground/graphql-playground.tsx +0 -745
  305. package/renderer/components/docs-viewer/playground/index.tsx +0 -671
  306. package/renderer/components/docs-viewer/playground/key-value-editor.tsx +0 -261
  307. package/renderer/components/docs-viewer/playground/method-selector.tsx +0 -60
  308. package/renderer/components/docs-viewer/playground/request-builder.tsx +0 -179
  309. package/renderer/components/docs-viewer/playground/request-tabs.tsx +0 -237
  310. package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +0 -21
  311. package/renderer/components/docs-viewer/playground/response-cards/index.tsx +0 -93
  312. package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +0 -16
  313. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +0 -23
  314. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +0 -268
  315. package/renderer/components/docs-viewer/playground/response-cards/types.ts +0 -82
  316. package/renderer/components/docs-viewer/playground/response-viewer.tsx +0 -43
  317. package/renderer/components/docs-viewer/search/index.ts +0 -2
  318. package/renderer/components/docs-viewer/search/search-dialog.tsx +0 -331
  319. package/renderer/components/docs-viewer/search/use-search.ts +0 -117
  320. package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +0 -431
  321. package/renderer/components/docs-viewer/shared/method-badge.tsx +0 -41
  322. package/renderer/components/docs-viewer/shared/schema-viewer.tsx +0 -349
  323. package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +0 -259
  324. package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +0 -316
  325. package/renderer/components/docs-viewer/sidebar/index.tsx +0 -282
  326. package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +0 -202
  327. package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +0 -118
  328. package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +0 -212
  329. package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +0 -38
  330. package/renderer/components/theme-provider.tsx +0 -11
  331. package/renderer/components/theme-toggle.tsx +0 -76
  332. package/renderer/components/ui/badge.tsx +0 -46
  333. package/renderer/components/ui/button.tsx +0 -59
  334. package/renderer/components/ui/dialog.tsx +0 -118
  335. package/renderer/components/ui/dropdown-menu.tsx +0 -257
  336. package/renderer/components/ui/input.tsx +0 -21
  337. package/renderer/components/ui/label.tsx +0 -24
  338. package/renderer/components/ui/navigation-menu.tsx +0 -168
  339. package/renderer/components/ui/select.tsx +0 -190
  340. package/renderer/components/ui/spinner.tsx +0 -114
  341. package/renderer/components/ui/tabs.tsx +0 -66
  342. package/renderer/components/ui/tooltip.tsx +0 -61
  343. package/renderer/hooks/use-code-copy.ts +0 -88
  344. package/renderer/hooks/use-openapi-title.ts +0 -44
  345. package/renderer/lib/api-docs/agent/index.ts +0 -6
  346. package/renderer/lib/api-docs/agent/indexer.ts +0 -323
  347. package/renderer/lib/api-docs/agent/spec-summary.ts +0 -335
  348. package/renderer/lib/api-docs/agent/types.ts +0 -116
  349. package/renderer/lib/api-docs/auth/auth-context.tsx +0 -225
  350. package/renderer/lib/api-docs/auth/auth-storage.ts +0 -87
  351. package/renderer/lib/api-docs/auth/crypto.ts +0 -89
  352. package/renderer/lib/api-docs/auth/index.ts +0 -4
  353. package/renderer/lib/api-docs/code-editor/db.ts +0 -164
  354. package/renderer/lib/api-docs/code-editor/hooks.ts +0 -266
  355. package/renderer/lib/api-docs/code-editor/mode-context.tsx +0 -207
  356. package/renderer/lib/api-docs/code-editor/types.ts +0 -105
  357. package/renderer/lib/api-docs/codegen/definitions.ts +0 -297
  358. package/renderer/lib/api-docs/codegen/har.ts +0 -251
  359. package/renderer/lib/api-docs/codegen/index.ts +0 -159
  360. package/renderer/lib/api-docs/factories.ts +0 -170
  361. package/renderer/lib/api-docs/mobile-context.tsx +0 -112
  362. package/renderer/lib/api-docs/navigation-context.tsx +0 -88
  363. package/renderer/lib/api-docs/parsers/graphql/README.md +0 -129
  364. package/renderer/lib/api-docs/parsers/graphql/index.ts +0 -91
  365. package/renderer/lib/api-docs/parsers/graphql/parser.ts +0 -491
  366. package/renderer/lib/api-docs/parsers/graphql/transformer.ts +0 -246
  367. package/renderer/lib/api-docs/parsers/graphql/types.ts +0 -283
  368. package/renderer/lib/api-docs/parsers/openapi/README.md +0 -32
  369. package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +0 -60
  370. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +0 -574
  371. package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +0 -403
  372. package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +0 -232
  373. package/renderer/lib/api-docs/parsers/openapi/index.ts +0 -171
  374. package/renderer/lib/api-docs/parsers/openapi/transformer.ts +0 -278
  375. package/renderer/lib/api-docs/parsers/openapi/validator.ts +0 -31
  376. package/renderer/lib/api-docs/playground/context.tsx +0 -107
  377. package/renderer/lib/api-docs/playground/navigation-context.tsx +0 -124
  378. package/renderer/lib/api-docs/playground/request-builder.ts +0 -223
  379. package/renderer/lib/api-docs/playground/request-runner.ts +0 -282
  380. package/renderer/lib/api-docs/playground/types.ts +0 -35
  381. package/renderer/lib/api-docs/types.ts +0 -269
  382. package/renderer/lib/api-docs/utils.ts +0 -311
  383. package/renderer/lib/cache.ts +0 -193
  384. package/renderer/lib/docs/config/domain-schema.ts +0 -260
  385. package/renderer/lib/docs/config/index.ts +0 -43
  386. package/renderer/lib/docs/config/loader.ts +0 -142
  387. package/renderer/lib/docs/config/schema.ts +0 -308
  388. package/renderer/lib/docs/index.ts +0 -12
  389. package/renderer/lib/docs/mdx/compiler.ts +0 -176
  390. package/renderer/lib/docs/mdx/frontmatter.ts +0 -91
  391. package/renderer/lib/docs/mdx/index.ts +0 -26
  392. package/renderer/lib/docs/navigation/generator.ts +0 -348
  393. package/renderer/lib/docs/navigation/index.ts +0 -12
  394. package/renderer/lib/docs/navigation/types.ts +0 -123
  395. package/renderer/lib/docs-navigation-context.tsx +0 -80
  396. package/renderer/lib/multi-tenant/context.ts +0 -105
  397. package/renderer/lib/storage/blob.ts +0 -1083
  398. package/renderer/lib/utils/icons.ts +0 -48
  399. package/renderer/lib/utils.ts +0 -6
  400. package/renderer/next.config.ts +0 -76
@@ -1,1083 +0,0 @@
1
- import { put, list, del, head } from '@vercel/blob'
2
- import fs from 'fs'
3
- import path from 'path'
4
-
5
- // Check if we're in local development mode (no blob token)
6
- const IS_LOCAL_DEV = !process.env.BLOB_READ_WRITE_TOKEN
7
-
8
- // Local storage directory for development
9
- const LOCAL_STORAGE_DIR = path.join(process.cwd(), '.devdoc-storage')
10
-
11
- /**
12
- * Project content structure stored in Vercel Blob
13
- */
14
- export interface ProjectContent {
15
- slug: string
16
- name: string
17
- docsJson: string // Stringified docs.json
18
- files: ProjectFile[]
19
- createdAt: string
20
- updatedAt: string
21
- }
22
-
23
- export interface ProjectFile {
24
- path: string // e.g., "index.mdx", "guides/overview.mdx"
25
- content: string
26
- }
27
-
28
- export interface ProjectMetadata {
29
- slug: string
30
- name: string
31
- createdAt: string
32
- updatedAt: string
33
- blobUrl: string
34
- }
35
-
36
- /**
37
- * Generate a unique project slug
38
- */
39
- export function generateProjectSlug(name: string): string {
40
- const base = name
41
- .toLowerCase()
42
- .replace(/[^a-z0-9]+/g, '-')
43
- .replace(/^-|-$/g, '')
44
- .substring(0, 30)
45
-
46
- const suffix = Math.random().toString(36).substring(2, 8)
47
- return `${base}-${suffix}`
48
- }
49
-
50
- /**
51
- * Get the blob path for a project's content
52
- */
53
- function getProjectBlobPath(slug: string): string {
54
- return `projects/${slug}/content.json`
55
- }
56
-
57
- /**
58
- * Get the blob path for a project's metadata
59
- */
60
- function getProjectMetadataPath(slug: string): string {
61
- return `projects/${slug}/metadata.json`
62
- }
63
-
64
- /**
65
- * Get the blob path for individual files (reserved for future use)
66
- */
67
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
68
- function _getFileBlobPath(slug: string, filePath: string): string {
69
- return `projects/${slug}/files/${filePath}`
70
- }
71
-
72
- /**
73
- * Store project content in Vercel Blob (or local filesystem in dev)
74
- */
75
- export async function storeProjectContent(
76
- slug: string,
77
- name: string,
78
- docsJson: object,
79
- files: ProjectFile[]
80
- ): Promise<{ url: string; slug: string }> {
81
- const now = new Date().toISOString()
82
-
83
- const content: ProjectContent = {
84
- slug,
85
- name,
86
- docsJson: JSON.stringify(docsJson),
87
- files,
88
- createdAt: now,
89
- updatedAt: now,
90
- }
91
-
92
- // Use local filesystem in development
93
- if (IS_LOCAL_DEV) {
94
- const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug)
95
- fs.mkdirSync(projectDir, { recursive: true })
96
-
97
- const contentPath = path.join(projectDir, 'content.json')
98
- fs.writeFileSync(contentPath, JSON.stringify(content, null, 2))
99
-
100
- const metadata: ProjectMetadata = {
101
- slug,
102
- name,
103
- createdAt: now,
104
- updatedAt: now,
105
- blobUrl: `file://${contentPath}`,
106
- }
107
-
108
- fs.writeFileSync(
109
- path.join(projectDir, 'metadata.json'),
110
- JSON.stringify(metadata, null, 2)
111
- )
112
-
113
- return { url: `file://${contentPath}`, slug }
114
- }
115
-
116
- // Store main content bundle in Vercel Blob
117
- const contentBlob = await put(
118
- getProjectBlobPath(slug),
119
- JSON.stringify(content),
120
- {
121
- access: 'public',
122
- contentType: 'application/json',
123
- allowOverwrite: true,
124
- }
125
- )
126
-
127
- // Store metadata separately for quick lookups
128
- const metadata: ProjectMetadata = {
129
- slug,
130
- name,
131
- createdAt: now,
132
- updatedAt: now,
133
- blobUrl: contentBlob.url,
134
- }
135
-
136
- await put(
137
- getProjectMetadataPath(slug),
138
- JSON.stringify(metadata),
139
- {
140
- access: 'public',
141
- contentType: 'application/json',
142
- allowOverwrite: true,
143
- }
144
- )
145
-
146
- return { url: contentBlob.url, slug }
147
- }
148
-
149
- /**
150
- * Update existing project content
151
- */
152
- export async function updateProjectContent(
153
- slug: string,
154
- docsJson: object,
155
- files: ProjectFile[]
156
- ): Promise<{ url: string }> {
157
- // Get existing content to preserve createdAt
158
- const existing = await getProjectContent(slug)
159
- const now = new Date().toISOString()
160
-
161
- const content: ProjectContent = {
162
- slug,
163
- name: existing?.name || slug,
164
- docsJson: JSON.stringify(docsJson),
165
- files,
166
- createdAt: existing?.createdAt || now,
167
- updatedAt: now,
168
- }
169
-
170
- // Use local filesystem in development
171
- if (IS_LOCAL_DEV) {
172
- const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug)
173
- fs.mkdirSync(projectDir, { recursive: true })
174
-
175
- const contentPath = path.join(projectDir, 'content.json')
176
- fs.writeFileSync(contentPath, JSON.stringify(content, null, 2))
177
-
178
- const metadata: ProjectMetadata = {
179
- slug,
180
- name: content.name,
181
- createdAt: content.createdAt,
182
- updatedAt: now,
183
- blobUrl: `file://${contentPath}`,
184
- }
185
-
186
- fs.writeFileSync(
187
- path.join(projectDir, 'metadata.json'),
188
- JSON.stringify(metadata, null, 2)
189
- )
190
-
191
- return { url: `file://${contentPath}` }
192
- }
193
-
194
- // Overwrite content in Vercel Blob
195
- const contentBlob = await put(
196
- getProjectBlobPath(slug),
197
- JSON.stringify(content),
198
- {
199
- access: 'public',
200
- contentType: 'application/json',
201
- allowOverwrite: true, // Updates overwrite existing content
202
- }
203
- )
204
-
205
- // Update metadata
206
- const metadata: ProjectMetadata = {
207
- slug,
208
- name: content.name,
209
- createdAt: content.createdAt,
210
- updatedAt: now,
211
- blobUrl: contentBlob.url,
212
- }
213
-
214
- await put(
215
- getProjectMetadataPath(slug),
216
- JSON.stringify(metadata),
217
- {
218
- access: 'public',
219
- contentType: 'application/json',
220
- allowOverwrite: true, // Updates overwrite existing metadata
221
- }
222
- )
223
-
224
- return { url: contentBlob.url }
225
- }
226
-
227
- /**
228
- * Get project content from Vercel Blob (or local filesystem in dev)
229
- */
230
- export async function getProjectContent(slug: string): Promise<ProjectContent | null> {
231
- try {
232
- // Use local filesystem in development
233
- if (IS_LOCAL_DEV) {
234
- const contentPath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'content.json')
235
- if (!fs.existsSync(contentPath)) {
236
- return null
237
- }
238
- const data = fs.readFileSync(contentPath, 'utf-8')
239
- return JSON.parse(data) as ProjectContent
240
- }
241
-
242
- const blobPath = getProjectBlobPath(slug)
243
- const blobInfo = await head(blobPath)
244
-
245
- if (!blobInfo) {
246
- return null
247
- }
248
-
249
- const response = await fetch(blobInfo.url)
250
- if (!response.ok) {
251
- return null
252
- }
253
-
254
- const content: ProjectContent = await response.json()
255
- return content
256
- } catch (error) {
257
- console.error(`[Blob] Error fetching project ${slug}:`, error)
258
- return null
259
- }
260
- }
261
-
262
- /**
263
- * Get project metadata (quick lookup without full content)
264
- */
265
- export async function getProjectMetadata(slug: string): Promise<ProjectMetadata | null> {
266
- try {
267
- const blobPath = getProjectMetadataPath(slug)
268
- const blobInfo = await head(blobPath)
269
-
270
- if (!blobInfo) {
271
- return null
272
- }
273
-
274
- const response = await fetch(blobInfo.url)
275
- if (!response.ok) {
276
- return null
277
- }
278
-
279
- const metadata: ProjectMetadata = await response.json()
280
- return metadata
281
- } catch (error) {
282
- console.error(`[Blob] Error fetching metadata for ${slug}:`, error)
283
- return null
284
- }
285
- }
286
-
287
- /**
288
- * Get a specific file from project content
289
- */
290
- export async function getProjectFile(
291
- slug: string,
292
- filePath: string
293
- ): Promise<string | null> {
294
- const content = await getProjectContent(slug)
295
- if (!content) {
296
- return null
297
- }
298
-
299
- const file = content.files.find(f => f.path === filePath)
300
- return file?.content || null
301
- }
302
-
303
- /**
304
- * Get docs.json for a project
305
- */
306
- export async function getProjectDocsJson(slug: string): Promise<object | null> {
307
- const content = await getProjectContent(slug)
308
- if (!content) {
309
- return null
310
- }
311
-
312
- try {
313
- return JSON.parse(content.docsJson)
314
- } catch {
315
- return null
316
- }
317
- }
318
-
319
- /**
320
- * Check if a project exists
321
- */
322
- export async function projectExists(slug: string): Promise<boolean> {
323
- try {
324
- // Use local filesystem in development
325
- if (IS_LOCAL_DEV) {
326
- const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug)
327
- // Check for either metadata.json (full project) or apikey.json (registered project)
328
- const metadataPath = path.join(projectDir, 'metadata.json')
329
- const apiKeyPath = path.join(projectDir, 'apikey.json')
330
- return fs.existsSync(metadataPath) || fs.existsSync(apiKeyPath)
331
- }
332
-
333
- // In production, check both metadata and apikey paths
334
- const metadataPath = getProjectMetadataPath(slug)
335
- const apiKeyPath = getApiKeyBlobPath(slug)
336
-
337
- const [metadataInfo, apiKeyInfo] = await Promise.all([
338
- head(metadataPath).catch(() => null),
339
- head(apiKeyPath).catch(() => null),
340
- ])
341
-
342
- return metadataInfo !== null || apiKeyInfo !== null
343
- } catch {
344
- return false
345
- }
346
- }
347
-
348
- /**
349
- * Delete a project and all its content
350
- */
351
- export async function deleteProject(slug: string): Promise<void> {
352
- try {
353
- // List all blobs for this project
354
- const { blobs } = await list({ prefix: `projects/${slug}/` })
355
-
356
- // Delete all blobs
357
- for (const blob of blobs) {
358
- await del(blob.url)
359
- }
360
- } catch (error) {
361
- console.error(`[Blob] Error deleting project ${slug}:`, error)
362
- throw error
363
- }
364
- }
365
-
366
- /**
367
- * List all projects (for admin purposes)
368
- */
369
- export async function listProjects(): Promise<ProjectMetadata[]> {
370
- try {
371
- const { blobs } = await list({ prefix: 'projects/' })
372
-
373
- // Filter for metadata files only
374
- const metadataBlobs = blobs.filter(b => b.pathname.endsWith('/metadata.json'))
375
-
376
- const projects: ProjectMetadata[] = []
377
- for (const blob of metadataBlobs) {
378
- try {
379
- const response = await fetch(blob.url)
380
- if (response.ok) {
381
- const metadata: ProjectMetadata = await response.json()
382
- projects.push(metadata)
383
- }
384
- } catch {
385
- // Skip invalid entries
386
- }
387
- }
388
-
389
- return projects
390
- } catch (error) {
391
- console.error('[Blob] Error listing projects:', error)
392
- return []
393
- }
394
- }
395
-
396
- // =============================================================================
397
- // API Key Management
398
- // =============================================================================
399
-
400
- /**
401
- * API Key data structure
402
- */
403
- export interface ProjectApiKey {
404
- key: string
405
- slug: string
406
- createdAt: string
407
- lastUsedAt?: string
408
- }
409
-
410
- /**
411
- * Get the blob path for a project's API key
412
- */
413
- function getApiKeyBlobPath(slug: string): string {
414
- return `projects/${slug}/apikey.json`
415
- }
416
-
417
- // =============================================================================
418
- // Domain Registry - O(1) lookups for subdomains, API keys, and custom domains
419
- // =============================================================================
420
-
421
- /**
422
- * Status of a custom domain in the system
423
- */
424
- export type CustomDomainStatus =
425
- | 'pending' // Domain added, awaiting DNS configuration
426
- | 'dns_verified' // DNS records verified, awaiting SSL
427
- | 'ssl_provisioning' // SSL certificate being provisioned
428
- | 'active' // Domain is live and working
429
- | 'error' // Configuration error
430
-
431
- /**
432
- * Custom domain entry stored in the registry
433
- */
434
- export interface CustomDomainEntry {
435
- /** The custom domain (e.g., "docs.example.com") */
436
- customDomain: string
437
-
438
- /** The project slug this domain points to */
439
- projectSlug: string
440
-
441
- /** Current status of the domain */
442
- status: CustomDomainStatus
443
-
444
- /** Vercel domain ID (from Vercel API) */
445
- vercelDomainId?: string
446
-
447
- /** TXT record value for domain ownership verification */
448
- verificationToken?: string
449
-
450
- /** When the domain was added */
451
- createdAt: string
452
-
453
- /** When DNS was verified */
454
- verifiedAt?: string
455
-
456
- /** Last time status was checked */
457
- lastCheckedAt?: string
458
-
459
- /** Error message if status is "error" */
460
- errorMessage?: string
461
- }
462
-
463
- /**
464
- * Registry structure - single file for all domains/projects
465
- */
466
- export interface DomainRegistry {
467
- domains: Record<string, DomainEntry> // subdomain -> entry
468
- customDomains: Record<string, CustomDomainEntry> // customDomain -> entry (NEW)
469
- projectToCustomDomain: Record<string, string> // projectSlug -> customDomain (reverse lookup)
470
- apiKeys: Record<string, string> // apiKey -> subdomain (for O(1) key validation)
471
- updatedAt: string
472
- }
473
-
474
- export interface DomainEntry {
475
- subdomain: string
476
- projectId: string
477
- name: string
478
- createdAt: string
479
- updatedAt?: string
480
- }
481
-
482
- const REGISTRY_PATH = 'registry/domains.json'
483
- const LOCAL_REGISTRY_PATH = path.join(LOCAL_STORAGE_DIR, 'registry', 'domains.json')
484
-
485
- /**
486
- * Get the domain registry (cached in memory for performance)
487
- */
488
- let registryCache: DomainRegistry | null = null
489
- let registryCacheTime = 0
490
- const CACHE_TTL = 5000 // 5 seconds
491
-
492
- async function getRegistry(): Promise<DomainRegistry> {
493
- const now = Date.now()
494
-
495
- // Return cached if fresh
496
- if (registryCache && (now - registryCacheTime) < CACHE_TTL) {
497
- return registryCache
498
- }
499
-
500
- try {
501
- if (IS_LOCAL_DEV) {
502
- if (fs.existsSync(LOCAL_REGISTRY_PATH)) {
503
- const data = fs.readFileSync(LOCAL_REGISTRY_PATH, 'utf-8')
504
- registryCache = JSON.parse(data) as DomainRegistry
505
- registryCacheTime = now
506
- return registryCache
507
- }
508
- } else {
509
- const blobInfo = await head(REGISTRY_PATH).catch(() => null)
510
- if (blobInfo) {
511
- const response = await fetch(blobInfo.url)
512
- if (response.ok) {
513
- registryCache = await response.json() as DomainRegistry
514
- registryCacheTime = now
515
- return registryCache
516
- }
517
- }
518
- }
519
- } catch (error) {
520
- console.error('[Registry] Error loading registry:', error)
521
- }
522
-
523
- // Return empty registry if not found
524
- return {
525
- domains: {},
526
- customDomains: {},
527
- projectToCustomDomain: {},
528
- apiKeys: {},
529
- updatedAt: new Date().toISOString()
530
- }
531
- }
532
-
533
- /**
534
- * Save the domain registry
535
- */
536
- async function saveRegistry(registry: DomainRegistry): Promise<void> {
537
- registry.updatedAt = new Date().toISOString()
538
-
539
- if (IS_LOCAL_DEV) {
540
- const dir = path.dirname(LOCAL_REGISTRY_PATH)
541
- fs.mkdirSync(dir, { recursive: true })
542
- fs.writeFileSync(LOCAL_REGISTRY_PATH, JSON.stringify(registry, null, 2))
543
- } else {
544
- await put(REGISTRY_PATH, JSON.stringify(registry), {
545
- access: 'public',
546
- contentType: 'application/json',
547
- allowOverwrite: true, // Registry is a single file that gets updated
548
- })
549
- }
550
-
551
- // Update cache
552
- registryCache = registry
553
- registryCacheTime = Date.now()
554
- }
555
-
556
- /**
557
- * Check if a subdomain is registered (O(1) lookup)
558
- */
559
- export async function isSubdomainRegistered(subdomain: string): Promise<boolean> {
560
- const registry = await getRegistry()
561
- return subdomain in registry.domains
562
- }
563
-
564
- /**
565
- * Register a new subdomain in the registry
566
- */
567
- export async function registerSubdomain(
568
- subdomain: string,
569
- projectId: string,
570
- name: string,
571
- apiKey: string
572
- ): Promise<void> {
573
- const registry = await getRegistry()
574
- const now = new Date().toISOString()
575
-
576
- registry.domains[subdomain] = {
577
- subdomain,
578
- projectId,
579
- name,
580
- createdAt: now,
581
- }
582
-
583
- registry.apiKeys[apiKey] = subdomain
584
-
585
- await saveRegistry(registry)
586
- }
587
-
588
- /**
589
- * Validate API key and get subdomain (O(1) lookup)
590
- */
591
- export async function validateApiKeyFromRegistry(apiKey: string): Promise<string | null> {
592
- if (!apiKey || !apiKey.startsWith('sk_live_') || apiKey.length !== 40) {
593
- return null
594
- }
595
-
596
- const registry = await getRegistry()
597
- return registry.apiKeys[apiKey] || null
598
- }
599
-
600
- /**
601
- * Get domain entry from registry
602
- */
603
- export async function getDomainEntry(subdomain: string): Promise<DomainEntry | null> {
604
- const registry = await getRegistry()
605
- return registry.domains[subdomain] || null
606
- }
607
-
608
- // =============================================================================
609
- // Custom Domain Management - One custom domain per project (free)
610
- // =============================================================================
611
-
612
- /**
613
- * Generate a verification token for domain ownership
614
- */
615
- export function generateVerificationToken(): string {
616
- const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
617
- let token = 'devdoc-verify='
618
- for (let i = 0; i < 24; i++) {
619
- token += chars.charAt(Math.floor(Math.random() * chars.length))
620
- }
621
- return token
622
- }
623
-
624
- /**
625
- * Check if a custom domain is already registered (O(1) lookup)
626
- */
627
- export async function isCustomDomainRegistered(customDomain: string): Promise<boolean> {
628
- const registry = await getRegistry()
629
- return customDomain in (registry.customDomains || {})
630
- }
631
-
632
- /**
633
- * Check if a project already has a custom domain (O(1) lookup)
634
- */
635
- export async function getProjectCustomDomain(projectSlug: string): Promise<CustomDomainEntry | null> {
636
- const registry = await getRegistry()
637
- const customDomain = registry.projectToCustomDomain?.[projectSlug]
638
- if (!customDomain) return null
639
- return registry.customDomains?.[customDomain] || null
640
- }
641
-
642
- /**
643
- * Get custom domain entry by domain name (O(1) lookup)
644
- */
645
- export async function getCustomDomainEntry(customDomain: string): Promise<CustomDomainEntry | null> {
646
- const registry = await getRegistry()
647
- return registry.customDomains?.[customDomain] || null
648
- }
649
-
650
- /**
651
- * Look up project slug from custom domain (O(1) lookup)
652
- * Used by middleware for routing
653
- */
654
- export async function lookupCustomDomain(customDomain: string): Promise<CustomDomainEntry | null> {
655
- const registry = await getRegistry()
656
- const entry = registry.customDomains?.[customDomain]
657
-
658
- // Only return if domain is active
659
- if (entry && entry.status === 'active') {
660
- return entry
661
- }
662
-
663
- return null
664
- }
665
-
666
- /**
667
- * Add a custom domain to a project
668
- * Returns error if project already has a custom domain (limit: 1 per project)
669
- */
670
- export async function addCustomDomain(
671
- projectSlug: string,
672
- customDomain: string
673
- ): Promise<{ success: boolean; entry?: CustomDomainEntry; error?: string }> {
674
- const registry = await getRegistry()
675
-
676
- // Initialize custom domain maps if they don't exist
677
- if (!registry.customDomains) {
678
- registry.customDomains = {}
679
- }
680
- if (!registry.projectToCustomDomain) {
681
- registry.projectToCustomDomain = {}
682
- }
683
-
684
- // Check if project already has a custom domain (limit: 1 per project)
685
- const existingDomain = registry.projectToCustomDomain[projectSlug]
686
- if (existingDomain) {
687
- return {
688
- success: false,
689
- error: `Project already has a custom domain: ${existingDomain}. Remove it first before adding a new one.`,
690
- }
691
- }
692
-
693
- // Check if this domain is already registered to another project
694
- if (registry.customDomains[customDomain]) {
695
- return {
696
- success: false,
697
- error: `Domain ${customDomain} is already registered to another project.`,
698
- }
699
- }
700
-
701
- // Verify the project exists
702
- const projectExists = await getDomainEntry(projectSlug)
703
- if (!projectExists) {
704
- return {
705
- success: false,
706
- error: `Project ${projectSlug} not found. Deploy your project first.`,
707
- }
708
- }
709
-
710
- const now = new Date().toISOString()
711
- const verificationToken = generateVerificationToken()
712
-
713
- const entry: CustomDomainEntry = {
714
- customDomain,
715
- projectSlug,
716
- status: 'pending',
717
- verificationToken,
718
- createdAt: now,
719
- }
720
-
721
- // Add to both lookups
722
- registry.customDomains[customDomain] = entry
723
- registry.projectToCustomDomain[projectSlug] = customDomain
724
-
725
- await saveRegistry(registry)
726
-
727
- return { success: true, entry }
728
- }
729
-
730
- /**
731
- * Update custom domain status
732
- */
733
- export async function updateCustomDomainStatus(
734
- customDomain: string,
735
- status: CustomDomainStatus,
736
- additionalFields?: Partial<CustomDomainEntry>
737
- ): Promise<boolean> {
738
- const registry = await getRegistry()
739
-
740
- if (!registry.customDomains?.[customDomain]) {
741
- return false
742
- }
743
-
744
- registry.customDomains[customDomain] = {
745
- ...registry.customDomains[customDomain],
746
- status,
747
- lastCheckedAt: new Date().toISOString(),
748
- ...additionalFields,
749
- }
750
-
751
- // Set verifiedAt when transitioning to dns_verified
752
- if (status === 'dns_verified' && !registry.customDomains[customDomain].verifiedAt) {
753
- registry.customDomains[customDomain].verifiedAt = new Date().toISOString()
754
- }
755
-
756
- await saveRegistry(registry)
757
- return true
758
- }
759
-
760
- /**
761
- * Remove a custom domain from a project
762
- */
763
- export async function removeCustomDomain(
764
- customDomain: string,
765
- projectSlug: string
766
- ): Promise<{ success: boolean; error?: string }> {
767
- const registry = await getRegistry()
768
-
769
- // Check if domain exists and belongs to this project
770
- const entry = registry.customDomains?.[customDomain]
771
- if (!entry) {
772
- return { success: false, error: `Domain ${customDomain} not found.` }
773
- }
774
-
775
- if (entry.projectSlug !== projectSlug) {
776
- return { success: false, error: `Domain ${customDomain} does not belong to this project.` }
777
- }
778
-
779
- // Remove from both lookups
780
- delete registry.customDomains[customDomain]
781
- delete registry.projectToCustomDomain[projectSlug]
782
-
783
- await saveRegistry(registry)
784
-
785
- return { success: true }
786
- }
787
-
788
- /**
789
- * List all custom domains (for admin purposes)
790
- */
791
- export async function listCustomDomains(): Promise<CustomDomainEntry[]> {
792
- const registry = await getRegistry()
793
- return Object.values(registry.customDomains || {})
794
- }
795
-
796
- /**
797
- * Generate a secure API key
798
- */
799
- export function generateApiKey(): string {
800
- const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
801
- let key = 'sk_live_'
802
- for (let i = 0; i < 32; i++) {
803
- key += chars.charAt(Math.floor(Math.random() * chars.length))
804
- }
805
- return key
806
- }
807
-
808
- /**
809
- * Store API key for a project
810
- */
811
- export async function storeProjectApiKey(
812
- slug: string,
813
- apiKey: string
814
- ): Promise<void> {
815
- const keyData: ProjectApiKey = {
816
- key: apiKey,
817
- slug,
818
- createdAt: new Date().toISOString(),
819
- }
820
-
821
- // Use local filesystem in development
822
- if (IS_LOCAL_DEV) {
823
- const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug)
824
- fs.mkdirSync(projectDir, { recursive: true })
825
- fs.writeFileSync(
826
- path.join(projectDir, 'apikey.json'),
827
- JSON.stringify(keyData, null, 2)
828
- )
829
- return
830
- }
831
-
832
- await put(
833
- getApiKeyBlobPath(slug),
834
- JSON.stringify(keyData),
835
- {
836
- access: 'public', // Note: In production, consider private access
837
- contentType: 'application/json',
838
- allowOverwrite: true, // Allow key regeneration
839
- }
840
- )
841
- }
842
-
843
- /**
844
- * Get API key data for a project
845
- */
846
- export async function getProjectApiKey(slug: string): Promise<ProjectApiKey | null> {
847
- try {
848
- // Use local filesystem in development
849
- if (IS_LOCAL_DEV) {
850
- const keyPath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'apikey.json')
851
- if (!fs.existsSync(keyPath)) {
852
- return null
853
- }
854
- const data = fs.readFileSync(keyPath, 'utf-8')
855
- return JSON.parse(data) as ProjectApiKey
856
- }
857
-
858
- const blobPath = getApiKeyBlobPath(slug)
859
- const blobInfo = await head(blobPath)
860
-
861
- if (!blobInfo) {
862
- return null
863
- }
864
-
865
- const response = await fetch(blobInfo.url)
866
- if (!response.ok) {
867
- return null
868
- }
869
-
870
- return await response.json() as ProjectApiKey
871
- } catch (error) {
872
- console.error(`[Blob] Error fetching API key for ${slug}:`, error)
873
- return null
874
- }
875
- }
876
-
877
- /**
878
- * Validate an API key and return the associated project slug
879
- * O(1) lookup from registry
880
- */
881
- export async function validateApiKey(apiKey: string): Promise<string | null> {
882
- // API key format: sk_live_<32chars>
883
- if (!apiKey || !apiKey.startsWith('sk_live_') || apiKey.length !== 40) {
884
- return null
885
- }
886
-
887
- try {
888
- // O(1) registry lookup
889
- const slug = await validateApiKeyFromRegistry(apiKey)
890
- if (slug) {
891
- // Update last used timestamp
892
- await updateApiKeyLastUsed(slug)
893
- }
894
- return slug
895
- } catch (error) {
896
- console.error('[Blob] Error validating API key:', error)
897
- return null
898
- }
899
- }
900
-
901
- /**
902
- * Update the lastUsedAt timestamp for an API key
903
- */
904
- async function updateApiKeyLastUsed(slug: string): Promise<void> {
905
- try {
906
- if (IS_LOCAL_DEV) {
907
- const keyPath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'apikey.json')
908
- if (fs.existsSync(keyPath)) {
909
- const data = JSON.parse(fs.readFileSync(keyPath, 'utf-8')) as ProjectApiKey
910
- data.lastUsedAt = new Date().toISOString()
911
- fs.writeFileSync(keyPath, JSON.stringify(data, null, 2))
912
- }
913
- } else {
914
- const blobPath = getApiKeyBlobPath(slug)
915
- const blobInfo = await head(blobPath).catch(() => null)
916
- if (blobInfo) {
917
- const response = await fetch(blobInfo.url)
918
- if (response.ok) {
919
- const keyData: ProjectApiKey = await response.json()
920
- keyData.lastUsedAt = new Date().toISOString()
921
- await put(blobPath, JSON.stringify(keyData), {
922
- access: 'public',
923
- contentType: 'application/json',
924
- allowOverwrite: true,
925
- })
926
- }
927
- }
928
- }
929
- } catch {
930
- // Non-critical, ignore errors
931
- }
932
- }
933
-
934
- /**
935
- * Regenerate API key for a project (requires old key for auth)
936
- */
937
- export async function regenerateApiKey(
938
- slug: string,
939
- oldApiKey: string
940
- ): Promise<string | null> {
941
- // Validate old key first
942
- const validSlug = await validateApiKey(oldApiKey)
943
- if (validSlug !== slug) {
944
- return null
945
- }
946
-
947
- // Generate new key
948
- const newKey = generateApiKey()
949
-
950
- // Update registry: remove old key, add new key
951
- const registry = await getRegistry()
952
- delete registry.apiKeys[oldApiKey]
953
- registry.apiKeys[newKey] = slug
954
- await saveRegistry(registry)
955
-
956
- // Also update project's apikey.json
957
- await storeProjectApiKey(slug, newKey)
958
-
959
- return newKey
960
- }
961
-
962
- // =============================================================================
963
- // Asset Management
964
- // =============================================================================
965
-
966
- /**
967
- * Asset metadata structure
968
- */
969
- export interface ProjectAsset {
970
- path: string
971
- url: string
972
- fileName: string
973
- size: number
974
- contentType: string
975
- uploadedAt: string
976
- }
977
-
978
- /**
979
- * Get the blob path for a project's assets
980
- */
981
- export function getAssetBlobPath(slug: string, fileName: string): string {
982
- return `projects/${slug}/assets/${fileName}`
983
- }
984
-
985
- /**
986
- * List all assets for a project
987
- */
988
- export async function listProjectAssets(slug: string): Promise<ProjectAsset[]> {
989
- try {
990
- if (IS_LOCAL_DEV) {
991
- const assetsDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'assets')
992
- if (!fs.existsSync(assetsDir)) {
993
- return []
994
- }
995
-
996
- const files = fs.readdirSync(assetsDir)
997
- return files.map(fileName => {
998
- const filePath = path.join(assetsDir, fileName)
999
- const stats = fs.statSync(filePath)
1000
- return {
1001
- path: `projects/${slug}/assets/${fileName}`,
1002
- url: `file://${filePath}`,
1003
- fileName,
1004
- size: stats.size,
1005
- contentType: getContentType(fileName),
1006
- uploadedAt: stats.mtime.toISOString(),
1007
- }
1008
- })
1009
- }
1010
-
1011
- const { blobs } = await list({ prefix: `projects/${slug}/assets/` })
1012
-
1013
- return blobs.map(blob => {
1014
- const fileName = blob.pathname.split('/').pop() || ''
1015
- return {
1016
- path: blob.pathname,
1017
- url: blob.url,
1018
- fileName,
1019
- size: blob.size,
1020
- contentType: getContentType(fileName),
1021
- uploadedAt: blob.uploadedAt.toISOString(),
1022
- }
1023
- })
1024
- } catch (error) {
1025
- console.error(`[Blob] Error listing assets for ${slug}:`, error)
1026
- return []
1027
- }
1028
- }
1029
-
1030
- /**
1031
- * Delete an asset
1032
- */
1033
- export async function deleteProjectAsset(slug: string, fileName: string): Promise<boolean> {
1034
- try {
1035
- if (IS_LOCAL_DEV) {
1036
- const filePath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'assets', fileName)
1037
- if (fs.existsSync(filePath)) {
1038
- fs.unlinkSync(filePath)
1039
- return true
1040
- }
1041
- return false
1042
- }
1043
-
1044
- const blobPath = getAssetBlobPath(slug, fileName)
1045
- const blobInfo = await head(blobPath).catch(() => null)
1046
-
1047
- if (blobInfo) {
1048
- await del(blobInfo.url)
1049
- return true
1050
- }
1051
-
1052
- return false
1053
- } catch (error) {
1054
- console.error(`[Blob] Error deleting asset ${fileName} for ${slug}:`, error)
1055
- return false
1056
- }
1057
- }
1058
-
1059
- /**
1060
- * Helper to get content type from file extension
1061
- */
1062
- function getContentType(fileName: string): string {
1063
- const ext = fileName.split('.').pop()?.toLowerCase()
1064
- const types: Record<string, string> = {
1065
- 'jpg': 'image/jpeg',
1066
- 'jpeg': 'image/jpeg',
1067
- 'png': 'image/png',
1068
- 'gif': 'image/gif',
1069
- 'webp': 'image/webp',
1070
- 'svg': 'image/svg+xml',
1071
- 'ico': 'image/x-icon',
1072
- 'pdf': 'application/pdf',
1073
- 'mp4': 'video/mp4',
1074
- 'webm': 'video/webm',
1075
- 'mp3': 'audio/mpeg',
1076
- 'wav': 'audio/wav',
1077
- 'woff': 'font/woff',
1078
- 'woff2': 'font/woff2',
1079
- 'ttf': 'font/ttf',
1080
- 'otf': 'font/otf',
1081
- }
1082
- return types[ext || ''] || 'application/octet-stream'
1083
- }