@brainfish-ai/devdoc 0.1.42 → 0.1.44

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 (398) hide show
  1. package/dist/cli/commands/create.js +2 -2
  2. package/dist/cli/commands/dev.js +19 -10
  3. package/package.json +3 -2
  4. package/renderer/app/api/assets/[...path]/route.js +108 -0
  5. package/renderer/app/api/assets/route.js +114 -0
  6. package/renderer/app/api/assets/upload/route.js +163 -0
  7. package/renderer/app/api/auth-schemes/route.js +58 -0
  8. package/renderer/app/api/chat/route.js +759 -0
  9. package/renderer/app/api/codegen/route.js +52 -0
  10. package/renderer/app/api/collections/route.js +706 -0
  11. package/renderer/app/api/debug/route.js +47 -0
  12. package/renderer/app/api/deploy/route.js +199 -0
  13. package/renderer/app/api/device/route.js +36 -0
  14. package/renderer/app/api/docs/route.js +205 -0
  15. package/renderer/app/api/domains/add/route.js +121 -0
  16. package/renderer/app/api/domains/lookup/route.js +43 -0
  17. package/renderer/app/api/domains/remove/route.js +89 -0
  18. package/renderer/app/api/domains/status/route.js +140 -0
  19. package/renderer/app/api/domains/verify/route.js +168 -0
  20. package/renderer/app/api/keys/regenerate/route.js +71 -0
  21. package/renderer/app/api/local-assets/[...path]/route.js +108 -0
  22. package/renderer/app/api/openapi-spec/route.js +73 -0
  23. package/renderer/app/api/projects/[slug]/route.js +129 -0
  24. package/renderer/app/api/projects/[slug]/stats/route.js +80 -0
  25. package/renderer/app/api/projects/register/route.js +176 -0
  26. package/renderer/app/api/proxy/route.js +139 -0
  27. package/renderer/app/api/proxy-stream/route.js +156 -0
  28. package/renderer/app/api/redirects/route.js +35 -0
  29. package/renderer/app/api/schema/route.js +85 -0
  30. package/renderer/app/api/subdomains/check/route.js +158 -0
  31. package/renderer/app/api/suggestions/route.js +195 -0
  32. package/renderer/app/globals.css +69 -0
  33. package/renderer/app/layout.js +47 -0
  34. package/renderer/app/llms-full.txt/route.js +266 -0
  35. package/renderer/app/llms.txt/route.js +228 -0
  36. package/renderer/app/page.js +12 -0
  37. package/renderer/app/robots.txt/route.js +66 -0
  38. package/renderer/app/sitemap.xml/route.js +155 -0
  39. package/renderer/components/docs/index.js +8 -0
  40. package/renderer/components/docs/mdx/accordion.js +113 -0
  41. package/renderer/components/docs/mdx/badge.js +72 -0
  42. package/renderer/components/docs/mdx/callouts.js +137 -0
  43. package/renderer/components/docs/mdx/cards.js +175 -0
  44. package/renderer/components/docs/mdx/changelog.js +100 -0
  45. package/renderer/components/docs/mdx/code-block.js +147 -0
  46. package/renderer/components/docs/mdx/code-group.js +287 -0
  47. package/renderer/components/docs/mdx/file-embeds.js +82 -0
  48. package/renderer/components/docs/mdx/frame.js +59 -0
  49. package/renderer/components/docs/mdx/highlight.js +90 -0
  50. package/renderer/components/docs/mdx/iframe.js +69 -0
  51. package/renderer/components/docs/mdx/image.js +135 -0
  52. package/renderer/components/docs/mdx/index.js +134 -0
  53. package/renderer/components/docs/mdx/landing.js +317 -0
  54. package/renderer/components/docs/mdx/mermaid.js +212 -0
  55. package/renderer/components/docs/mdx/param-field.js +112 -0
  56. package/renderer/components/docs/mdx/steps.js +74 -0
  57. package/renderer/components/docs/mdx/tabs.js +50 -0
  58. package/renderer/components/docs/mdx-renderer.js +77 -0
  59. package/renderer/components/docs/navigation/breadcrumbs.js +64 -0
  60. package/renderer/components/docs/navigation/index.js +6 -0
  61. package/renderer/components/docs/navigation/page-nav.js +57 -0
  62. package/renderer/components/docs/navigation/sidebar.js +375 -0
  63. package/renderer/components/docs/navigation/toc.js +89 -0
  64. package/renderer/components/docs/notice.js +77 -0
  65. package/renderer/components/docs-header.js +202 -0
  66. package/renderer/components/docs-viewer/agent/agent-chat.js +1831 -0
  67. package/renderer/components/docs-viewer/agent/agent-popup-button.js +99 -0
  68. package/renderer/components/docs-viewer/agent/cards/debug-context-card.js +107 -0
  69. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.js +57 -0
  70. package/renderer/components/docs-viewer/agent/cards/index.js +45 -0
  71. package/renderer/components/docs-viewer/agent/cards/response-options-card.js +154 -0
  72. package/renderer/components/docs-viewer/agent/cards/types.js +22 -0
  73. package/renderer/components/docs-viewer/agent/chat-message.js +2 -0
  74. package/renderer/components/docs-viewer/agent/index.js +7 -0
  75. package/renderer/components/docs-viewer/agent/messages/assistant-message.js +108 -0
  76. package/renderer/components/docs-viewer/agent/messages/chat-message.js +34 -0
  77. package/renderer/components/docs-viewer/agent/messages/index.js +6 -0
  78. package/renderer/components/docs-viewer/agent/messages/tool-call-display.js +1065 -0
  79. package/renderer/components/docs-viewer/agent/messages/types.js +2 -0
  80. package/renderer/components/docs-viewer/agent/messages/typing-indicator.js +26 -0
  81. package/renderer/components/docs-viewer/agent/messages/user-message.js +37 -0
  82. package/renderer/components/docs-viewer/code-editor/{index.tsx → index.js} +1 -1
  83. package/renderer/components/docs-viewer/code-editor/notes-mode.js +1338 -0
  84. package/renderer/components/docs-viewer/content/changelog-page.js +297 -0
  85. package/renderer/components/docs-viewer/content/content-router.js +182 -0
  86. package/renderer/components/docs-viewer/content/doc-page.js +290 -0
  87. package/renderer/components/docs-viewer/content/documentation-viewer.js +14 -0
  88. package/renderer/components/docs-viewer/content/index.js +31 -0
  89. package/renderer/components/docs-viewer/content/not-found-page.js +300 -0
  90. package/renderer/components/docs-viewer/content/request-details.js +528 -0
  91. package/renderer/components/docs-viewer/content/sections/auth.js +108 -0
  92. package/renderer/components/docs-viewer/content/sections/body.js +80 -0
  93. package/renderer/components/docs-viewer/content/sections/headers.js +64 -0
  94. package/renderer/components/docs-viewer/content/sections/overview.js +56 -0
  95. package/renderer/components/docs-viewer/content/sections/parameters.js +64 -0
  96. package/renderer/components/docs-viewer/content/sections/responses.js +91 -0
  97. package/renderer/components/docs-viewer/global-auth-modal.js +427 -0
  98. package/renderer/components/docs-viewer/index.js +1448 -0
  99. package/renderer/components/docs-viewer/playground/auth-editor.js +418 -0
  100. package/renderer/components/docs-viewer/playground/body-editor.js +240 -0
  101. package/renderer/components/docs-viewer/playground/code-editor.js +135 -0
  102. package/renderer/components/docs-viewer/playground/code-snippet.js +393 -0
  103. package/renderer/components/docs-viewer/playground/graphql-playground.js +936 -0
  104. package/renderer/components/docs-viewer/playground/index.js +682 -0
  105. package/renderer/components/docs-viewer/playground/key-value-editor.js +317 -0
  106. package/renderer/components/docs-viewer/playground/method-selector.js +65 -0
  107. package/renderer/components/docs-viewer/playground/request-builder.js +181 -0
  108. package/renderer/components/docs-viewer/playground/request-tabs.js +240 -0
  109. package/renderer/components/docs-viewer/playground/response-cards/idle-card.js +42 -0
  110. package/renderer/components/docs-viewer/playground/response-cards/index.js +72 -0
  111. package/renderer/components/docs-viewer/playground/response-cards/loading-card.js +24 -0
  112. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.js +28 -0
  113. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.js +308 -0
  114. package/renderer/components/docs-viewer/playground/response-cards/types.js +9 -0
  115. package/renderer/components/docs-viewer/playground/response-viewer.js +18 -0
  116. package/renderer/components/docs-viewer/search/index.js +2 -0
  117. package/renderer/components/docs-viewer/search/search-dialog.js +367 -0
  118. package/renderer/components/docs-viewer/search/use-search.js +89 -0
  119. package/renderer/components/docs-viewer/shared/markdown-renderer.js +423 -0
  120. package/renderer/components/docs-viewer/shared/method-badge.js +23 -0
  121. package/renderer/components/docs-viewer/shared/schema-viewer.js +321 -0
  122. package/renderer/components/docs-viewer/sidebar/collection-tree.js +222 -0
  123. package/renderer/components/docs-viewer/sidebar/endpoint-options.js +512 -0
  124. package/renderer/components/docs-viewer/sidebar/index.js +196 -0
  125. package/renderer/components/docs-viewer/sidebar/right-sidebar.js +159 -0
  126. package/renderer/components/docs-viewer/sidebar/sidebar-group.js +87 -0
  127. package/renderer/components/docs-viewer/sidebar/sidebar-item.js +172 -0
  128. package/renderer/components/docs-viewer/sidebar/sidebar-section.js +31 -0
  129. package/renderer/components/theme-provider.js +10 -0
  130. package/renderer/components/theme-toggle.js +86 -0
  131. package/renderer/components/ui/badge.js +29 -0
  132. package/renderer/components/ui/button.js +40 -0
  133. package/renderer/components/ui/dialog.js +50 -0
  134. package/renderer/components/ui/dropdown-menu.js +143 -0
  135. package/renderer/components/ui/input.js +12 -0
  136. package/renderer/components/ui/label.js +13 -0
  137. package/renderer/components/ui/navigation-menu.js +83 -0
  138. package/renderer/components/ui/select.js +116 -0
  139. package/renderer/components/ui/spinner.js +92 -0
  140. package/renderer/components/ui/tabs.js +34 -0
  141. package/renderer/components/ui/tooltip.js +43 -0
  142. package/renderer/hooks/use-code-copy.js +76 -0
  143. package/renderer/hooks/use-openapi-title.js +33 -0
  144. package/renderer/hooks/use-route-state.js +159 -0
  145. package/renderer/lib/api-docs/agent/index.js +4 -0
  146. package/renderer/lib/api-docs/agent/indexer.js +254 -0
  147. package/renderer/lib/api-docs/agent/spec-summary.js +227 -0
  148. package/renderer/lib/api-docs/agent/types.js +5 -0
  149. package/renderer/lib/api-docs/agent/use-suggestions.js +97 -0
  150. package/renderer/lib/api-docs/auth/auth-context.js +157 -0
  151. package/renderer/lib/api-docs/auth/auth-storage.js +66 -0
  152. package/renderer/lib/api-docs/auth/crypto.js +64 -0
  153. package/renderer/lib/api-docs/auth/index.js +3 -0
  154. package/renderer/lib/api-docs/code-editor/db.js +145 -0
  155. package/renderer/lib/api-docs/code-editor/hooks.js +254 -0
  156. package/renderer/lib/api-docs/code-editor/{index.ts → index.js} +3 -4
  157. package/renderer/lib/api-docs/code-editor/mode-context.js +126 -0
  158. package/renderer/lib/api-docs/code-editor/types.js +53 -0
  159. package/renderer/lib/api-docs/codegen/definitions.js +258 -0
  160. package/renderer/lib/api-docs/codegen/har.js +171 -0
  161. package/renderer/lib/api-docs/codegen/index.js +118 -0
  162. package/renderer/lib/api-docs/factories.js +136 -0
  163. package/renderer/lib/api-docs/{index.ts → index.js} +5 -10
  164. package/renderer/lib/api-docs/mobile-context.js +116 -0
  165. package/renderer/lib/api-docs/navigation-context.js +62 -0
  166. package/renderer/lib/api-docs/parsers/graphql/index.js +50 -0
  167. package/renderer/lib/api-docs/parsers/graphql/parser.js +350 -0
  168. package/renderer/lib/api-docs/parsers/graphql/transformer.js +215 -0
  169. package/renderer/lib/api-docs/parsers/graphql/types.js +46 -0
  170. package/renderer/lib/api-docs/parsers/openapi/dereferencer.js +43 -0
  171. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.js +486 -0
  172. package/renderer/lib/api-docs/parsers/openapi/extractors/body.js +295 -0
  173. package/renderer/lib/api-docs/parsers/openapi/extractors/index.js +132 -0
  174. package/renderer/lib/api-docs/parsers/openapi/index.js +127 -0
  175. package/renderer/lib/api-docs/parsers/openapi/transformer.js +192 -0
  176. package/renderer/lib/api-docs/parsers/openapi/validator.js +24 -0
  177. package/renderer/lib/api-docs/playground/context.js +65 -0
  178. package/renderer/lib/api-docs/playground/navigation-context.js +74 -0
  179. package/renderer/lib/api-docs/playground/request-builder.js +163 -0
  180. package/renderer/lib/api-docs/playground/request-runner.js +224 -0
  181. package/renderer/lib/api-docs/playground/types.js +5 -0
  182. package/renderer/lib/api-docs/types.js +23 -0
  183. package/renderer/lib/api-docs/utils.js +212 -0
  184. package/renderer/lib/cache.js +157 -0
  185. package/renderer/lib/docs/config/domain-schema.js +161 -0
  186. package/renderer/lib/docs/config/environment.js +38 -0
  187. package/renderer/lib/docs/config/index.js +6 -0
  188. package/renderer/lib/docs/config/loader.js +113 -0
  189. package/renderer/lib/docs/config/schema.js +281 -0
  190. package/renderer/lib/docs/index.js +8 -0
  191. package/renderer/lib/docs/mdx/compiler.js +131 -0
  192. package/renderer/lib/docs/mdx/frontmatter.js +73 -0
  193. package/renderer/lib/docs/mdx/index.js +10 -0
  194. package/renderer/lib/docs/mdx/remark-mermaid.js +63 -0
  195. package/renderer/lib/docs/navigation/generator.js +269 -0
  196. package/renderer/lib/docs/navigation/index.js +3 -0
  197. package/renderer/lib/docs/navigation/types.js +11 -0
  198. package/renderer/lib/docs-navigation-context.js +40 -0
  199. package/renderer/lib/docs-navigation.js +140 -0
  200. package/renderer/lib/multi-tenant/context.js +80 -0
  201. package/renderer/lib/storage/blob.js +767 -0
  202. package/renderer/lib/utils/icons.js +30 -0
  203. package/renderer/lib/utils.js +5 -0
  204. package/renderer/next.config.js +62 -0
  205. package/renderer/package.json +1 -0
  206. package/renderer/tsconfig.json +23 -5
  207. package/renderer/app/api/assets/[...path]/route.ts +0 -123
  208. package/renderer/app/api/assets/route.ts +0 -124
  209. package/renderer/app/api/assets/upload/route.ts +0 -177
  210. package/renderer/app/api/auth-schemes/route.ts +0 -77
  211. package/renderer/app/api/chat/route.ts +0 -858
  212. package/renderer/app/api/codegen/route.ts +0 -72
  213. package/renderer/app/api/collections/route.ts +0 -1002
  214. package/renderer/app/api/debug/route.ts +0 -53
  215. package/renderer/app/api/deploy/route.ts +0 -234
  216. package/renderer/app/api/device/route.ts +0 -42
  217. package/renderer/app/api/docs/route.ts +0 -201
  218. package/renderer/app/api/domains/add/route.ts +0 -132
  219. package/renderer/app/api/domains/lookup/route.ts +0 -43
  220. package/renderer/app/api/domains/remove/route.ts +0 -100
  221. package/renderer/app/api/domains/status/route.ts +0 -158
  222. package/renderer/app/api/domains/verify/route.ts +0 -181
  223. package/renderer/app/api/keys/regenerate/route.ts +0 -80
  224. package/renderer/app/api/local-assets/[...path]/route.ts +0 -122
  225. package/renderer/app/api/openapi-spec/route.ts +0 -151
  226. package/renderer/app/api/projects/[slug]/route.ts +0 -153
  227. package/renderer/app/api/projects/[slug]/stats/route.ts +0 -96
  228. package/renderer/app/api/projects/register/route.ts +0 -152
  229. package/renderer/app/api/proxy/route.ts +0 -149
  230. package/renderer/app/api/proxy-stream/route.ts +0 -168
  231. package/renderer/app/api/redirects/route.ts +0 -47
  232. package/renderer/app/api/schema/route.ts +0 -73
  233. package/renderer/app/api/subdomains/check/route.ts +0 -172
  234. package/renderer/app/api/suggestions/route.ts +0 -144
  235. package/renderer/app/layout.tsx +0 -54
  236. package/renderer/app/llms-full.txt/route.ts +0 -346
  237. package/renderer/app/llms.txt/route.ts +0 -279
  238. package/renderer/app/page.tsx +0 -14
  239. package/renderer/app/robots.txt/route.ts +0 -84
  240. package/renderer/app/sitemap.xml/route.ts +0 -199
  241. package/renderer/components/docs/index.ts +0 -12
  242. package/renderer/components/docs/mdx/accordion.tsx +0 -169
  243. package/renderer/components/docs/mdx/badge.tsx +0 -132
  244. package/renderer/components/docs/mdx/callouts.tsx +0 -154
  245. package/renderer/components/docs/mdx/cards.tsx +0 -241
  246. package/renderer/components/docs/mdx/changelog.tsx +0 -120
  247. package/renderer/components/docs/mdx/code-block.tsx +0 -186
  248. package/renderer/components/docs/mdx/code-group.tsx +0 -421
  249. package/renderer/components/docs/mdx/file-embeds.tsx +0 -105
  250. package/renderer/components/docs/mdx/frame.tsx +0 -112
  251. package/renderer/components/docs/mdx/highlight.tsx +0 -151
  252. package/renderer/components/docs/mdx/iframe.tsx +0 -134
  253. package/renderer/components/docs/mdx/image.tsx +0 -235
  254. package/renderer/components/docs/mdx/index.ts +0 -237
  255. package/renderer/components/docs/mdx/landing.tsx +0 -684
  256. package/renderer/components/docs/mdx/mermaid.tsx +0 -240
  257. package/renderer/components/docs/mdx/param-field.tsx +0 -200
  258. package/renderer/components/docs/mdx/steps.tsx +0 -113
  259. package/renderer/components/docs/mdx/tabs.tsx +0 -86
  260. package/renderer/components/docs/mdx-renderer.tsx +0 -100
  261. package/renderer/components/docs/navigation/breadcrumbs.tsx +0 -76
  262. package/renderer/components/docs/navigation/index.ts +0 -8
  263. package/renderer/components/docs/navigation/page-nav.tsx +0 -64
  264. package/renderer/components/docs/navigation/sidebar.tsx +0 -515
  265. package/renderer/components/docs/navigation/toc.tsx +0 -113
  266. package/renderer/components/docs/notice.tsx +0 -105
  267. package/renderer/components/docs-header.tsx +0 -278
  268. package/renderer/components/docs-viewer/agent/agent-chat.tsx +0 -2076
  269. package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +0 -90
  270. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +0 -49
  271. package/renderer/components/docs-viewer/agent/cards/index.tsx +0 -50
  272. package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +0 -212
  273. package/renderer/components/docs-viewer/agent/cards/types.ts +0 -84
  274. package/renderer/components/docs-viewer/agent/chat-message.tsx +0 -17
  275. package/renderer/components/docs-viewer/agent/index.tsx +0 -6
  276. package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +0 -119
  277. package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +0 -46
  278. package/renderer/components/docs-viewer/agent/messages/index.ts +0 -17
  279. package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +0 -721
  280. package/renderer/components/docs-viewer/agent/messages/types.ts +0 -61
  281. package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +0 -24
  282. package/renderer/components/docs-viewer/agent/messages/user-message.tsx +0 -51
  283. package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +0 -1283
  284. package/renderer/components/docs-viewer/content/changelog-page.tsx +0 -331
  285. package/renderer/components/docs-viewer/content/doc-page.tsx +0 -367
  286. package/renderer/components/docs-viewer/content/documentation-viewer.tsx +0 -17
  287. package/renderer/components/docs-viewer/content/index.tsx +0 -29
  288. package/renderer/components/docs-viewer/content/not-found-page.tsx +0 -330
  289. package/renderer/components/docs-viewer/content/request-details.tsx +0 -330
  290. package/renderer/components/docs-viewer/content/sections/auth.tsx +0 -69
  291. package/renderer/components/docs-viewer/content/sections/body.tsx +0 -66
  292. package/renderer/components/docs-viewer/content/sections/headers.tsx +0 -43
  293. package/renderer/components/docs-viewer/content/sections/overview.tsx +0 -40
  294. package/renderer/components/docs-viewer/content/sections/parameters.tsx +0 -43
  295. package/renderer/components/docs-viewer/content/sections/responses.tsx +0 -87
  296. package/renderer/components/docs-viewer/global-auth-modal.tsx +0 -352
  297. package/renderer/components/docs-viewer/index.tsx +0 -1670
  298. package/renderer/components/docs-viewer/playground/auth-editor.tsx +0 -280
  299. package/renderer/components/docs-viewer/playground/body-editor.tsx +0 -221
  300. package/renderer/components/docs-viewer/playground/code-editor.tsx +0 -224
  301. package/renderer/components/docs-viewer/playground/code-snippet.tsx +0 -387
  302. package/renderer/components/docs-viewer/playground/graphql-playground.tsx +0 -745
  303. package/renderer/components/docs-viewer/playground/index.tsx +0 -671
  304. package/renderer/components/docs-viewer/playground/key-value-editor.tsx +0 -261
  305. package/renderer/components/docs-viewer/playground/method-selector.tsx +0 -60
  306. package/renderer/components/docs-viewer/playground/request-builder.tsx +0 -179
  307. package/renderer/components/docs-viewer/playground/request-tabs.tsx +0 -237
  308. package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +0 -21
  309. package/renderer/components/docs-viewer/playground/response-cards/index.tsx +0 -93
  310. package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +0 -16
  311. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +0 -23
  312. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +0 -268
  313. package/renderer/components/docs-viewer/playground/response-cards/types.ts +0 -82
  314. package/renderer/components/docs-viewer/playground/response-viewer.tsx +0 -43
  315. package/renderer/components/docs-viewer/search/index.ts +0 -2
  316. package/renderer/components/docs-viewer/search/search-dialog.tsx +0 -331
  317. package/renderer/components/docs-viewer/search/use-search.ts +0 -117
  318. package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +0 -431
  319. package/renderer/components/docs-viewer/shared/method-badge.tsx +0 -41
  320. package/renderer/components/docs-viewer/shared/schema-viewer.tsx +0 -349
  321. package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +0 -259
  322. package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +0 -316
  323. package/renderer/components/docs-viewer/sidebar/index.tsx +0 -282
  324. package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +0 -202
  325. package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +0 -118
  326. package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +0 -212
  327. package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +0 -38
  328. package/renderer/components/theme-provider.tsx +0 -11
  329. package/renderer/components/theme-toggle.tsx +0 -76
  330. package/renderer/components/ui/badge.tsx +0 -46
  331. package/renderer/components/ui/button.tsx +0 -59
  332. package/renderer/components/ui/dialog.tsx +0 -118
  333. package/renderer/components/ui/dropdown-menu.tsx +0 -257
  334. package/renderer/components/ui/input.tsx +0 -21
  335. package/renderer/components/ui/label.tsx +0 -24
  336. package/renderer/components/ui/navigation-menu.tsx +0 -168
  337. package/renderer/components/ui/select.tsx +0 -190
  338. package/renderer/components/ui/spinner.tsx +0 -114
  339. package/renderer/components/ui/tabs.tsx +0 -66
  340. package/renderer/components/ui/tooltip.tsx +0 -61
  341. package/renderer/hooks/use-code-copy.ts +0 -88
  342. package/renderer/hooks/use-openapi-title.ts +0 -44
  343. package/renderer/lib/api-docs/agent/index.ts +0 -6
  344. package/renderer/lib/api-docs/agent/indexer.ts +0 -323
  345. package/renderer/lib/api-docs/agent/spec-summary.ts +0 -335
  346. package/renderer/lib/api-docs/agent/types.ts +0 -116
  347. package/renderer/lib/api-docs/auth/auth-context.tsx +0 -225
  348. package/renderer/lib/api-docs/auth/auth-storage.ts +0 -87
  349. package/renderer/lib/api-docs/auth/crypto.ts +0 -89
  350. package/renderer/lib/api-docs/auth/index.ts +0 -4
  351. package/renderer/lib/api-docs/code-editor/db.ts +0 -164
  352. package/renderer/lib/api-docs/code-editor/hooks.ts +0 -266
  353. package/renderer/lib/api-docs/code-editor/mode-context.tsx +0 -207
  354. package/renderer/lib/api-docs/code-editor/types.ts +0 -105
  355. package/renderer/lib/api-docs/codegen/definitions.ts +0 -297
  356. package/renderer/lib/api-docs/codegen/har.ts +0 -251
  357. package/renderer/lib/api-docs/codegen/index.ts +0 -159
  358. package/renderer/lib/api-docs/factories.ts +0 -170
  359. package/renderer/lib/api-docs/mobile-context.tsx +0 -112
  360. package/renderer/lib/api-docs/navigation-context.tsx +0 -88
  361. package/renderer/lib/api-docs/parsers/graphql/README.md +0 -129
  362. package/renderer/lib/api-docs/parsers/graphql/index.ts +0 -91
  363. package/renderer/lib/api-docs/parsers/graphql/parser.ts +0 -491
  364. package/renderer/lib/api-docs/parsers/graphql/transformer.ts +0 -246
  365. package/renderer/lib/api-docs/parsers/graphql/types.ts +0 -283
  366. package/renderer/lib/api-docs/parsers/openapi/README.md +0 -32
  367. package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +0 -60
  368. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +0 -574
  369. package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +0 -403
  370. package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +0 -232
  371. package/renderer/lib/api-docs/parsers/openapi/index.ts +0 -171
  372. package/renderer/lib/api-docs/parsers/openapi/transformer.ts +0 -278
  373. package/renderer/lib/api-docs/parsers/openapi/validator.ts +0 -31
  374. package/renderer/lib/api-docs/playground/context.tsx +0 -107
  375. package/renderer/lib/api-docs/playground/navigation-context.tsx +0 -124
  376. package/renderer/lib/api-docs/playground/request-builder.ts +0 -223
  377. package/renderer/lib/api-docs/playground/request-runner.ts +0 -282
  378. package/renderer/lib/api-docs/playground/types.ts +0 -35
  379. package/renderer/lib/api-docs/types.ts +0 -269
  380. package/renderer/lib/api-docs/utils.ts +0 -311
  381. package/renderer/lib/cache.ts +0 -193
  382. package/renderer/lib/docs/config/domain-schema.ts +0 -260
  383. package/renderer/lib/docs/config/index.ts +0 -43
  384. package/renderer/lib/docs/config/loader.ts +0 -142
  385. package/renderer/lib/docs/config/schema.ts +0 -308
  386. package/renderer/lib/docs/index.ts +0 -12
  387. package/renderer/lib/docs/mdx/compiler.ts +0 -176
  388. package/renderer/lib/docs/mdx/frontmatter.ts +0 -91
  389. package/renderer/lib/docs/mdx/index.ts +0 -26
  390. package/renderer/lib/docs/navigation/generator.ts +0 -348
  391. package/renderer/lib/docs/navigation/index.ts +0 -12
  392. package/renderer/lib/docs/navigation/types.ts +0 -123
  393. package/renderer/lib/docs-navigation-context.tsx +0 -80
  394. package/renderer/lib/multi-tenant/context.ts +0 -105
  395. package/renderer/lib/storage/blob.ts +0 -1083
  396. package/renderer/lib/utils/icons.ts +0 -48
  397. package/renderer/lib/utils.ts +0 -6
  398. package/renderer/next.config.ts +0 -76
@@ -0,0 +1,767 @@
1
+ import { put, list, del, head } from '@vercel/blob';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ // Check if we're in local development mode (no blob token)
5
+ const IS_LOCAL_DEV = !process.env.BLOB_READ_WRITE_TOKEN;
6
+ // Local storage directory for development
7
+ const LOCAL_STORAGE_DIR = path.join(process.cwd(), '.devdoc-storage');
8
+ /**
9
+ * Generate a unique project slug
10
+ */ export function generateProjectSlug(name) {
11
+ const base = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').substring(0, 30);
12
+ const suffix = Math.random().toString(36).substring(2, 8);
13
+ return `${base}-${suffix}`;
14
+ }
15
+ /**
16
+ * Get the blob path for a project's content
17
+ */ function getProjectBlobPath(slug) {
18
+ return `projects/${slug}/content.json`;
19
+ }
20
+ /**
21
+ * Get the blob path for a project's metadata
22
+ */ function getProjectMetadataPath(slug) {
23
+ return `projects/${slug}/metadata.json`;
24
+ }
25
+ /**
26
+ * Get the blob path for individual files (reserved for future use)
27
+ */ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28
+ function _getFileBlobPath(slug, filePath) {
29
+ return `projects/${slug}/files/${filePath}`;
30
+ }
31
+ /**
32
+ * Store project content in Vercel Blob (or local filesystem in dev)
33
+ */ export async function storeProjectContent(slug, name, docsJson, files) {
34
+ const now = new Date().toISOString();
35
+ const content = {
36
+ slug,
37
+ name,
38
+ docsJson: JSON.stringify(docsJson),
39
+ files,
40
+ createdAt: now,
41
+ updatedAt: now
42
+ };
43
+ // Use local filesystem in development
44
+ if (IS_LOCAL_DEV) {
45
+ const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug);
46
+ fs.mkdirSync(projectDir, {
47
+ recursive: true
48
+ });
49
+ const contentPath = path.join(projectDir, 'content.json');
50
+ fs.writeFileSync(contentPath, JSON.stringify(content, null, 2));
51
+ const metadata = {
52
+ slug,
53
+ name,
54
+ createdAt: now,
55
+ updatedAt: now,
56
+ blobUrl: `file://${contentPath}`
57
+ };
58
+ fs.writeFileSync(path.join(projectDir, 'metadata.json'), JSON.stringify(metadata, null, 2));
59
+ return {
60
+ url: `file://${contentPath}`,
61
+ slug
62
+ };
63
+ }
64
+ // Store main content bundle in Vercel Blob
65
+ const contentBlob = await put(getProjectBlobPath(slug), JSON.stringify(content), {
66
+ access: 'public',
67
+ contentType: 'application/json',
68
+ allowOverwrite: true
69
+ });
70
+ // Store metadata separately for quick lookups
71
+ const metadata = {
72
+ slug,
73
+ name,
74
+ createdAt: now,
75
+ updatedAt: now,
76
+ blobUrl: contentBlob.url
77
+ };
78
+ await put(getProjectMetadataPath(slug), JSON.stringify(metadata), {
79
+ access: 'public',
80
+ contentType: 'application/json',
81
+ allowOverwrite: true
82
+ });
83
+ return {
84
+ url: contentBlob.url,
85
+ slug
86
+ };
87
+ }
88
+ /**
89
+ * Update existing project content
90
+ */ export async function updateProjectContent(slug, docsJson, files) {
91
+ // Get existing content to preserve createdAt
92
+ const existing = await getProjectContent(slug);
93
+ const now = new Date().toISOString();
94
+ const content = {
95
+ slug,
96
+ name: existing?.name || slug,
97
+ docsJson: JSON.stringify(docsJson),
98
+ files,
99
+ createdAt: existing?.createdAt || now,
100
+ updatedAt: now
101
+ };
102
+ // Use local filesystem in development
103
+ if (IS_LOCAL_DEV) {
104
+ const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug);
105
+ fs.mkdirSync(projectDir, {
106
+ recursive: true
107
+ });
108
+ const contentPath = path.join(projectDir, 'content.json');
109
+ fs.writeFileSync(contentPath, JSON.stringify(content, null, 2));
110
+ const metadata = {
111
+ slug,
112
+ name: content.name,
113
+ createdAt: content.createdAt,
114
+ updatedAt: now,
115
+ blobUrl: `file://${contentPath}`
116
+ };
117
+ fs.writeFileSync(path.join(projectDir, 'metadata.json'), JSON.stringify(metadata, null, 2));
118
+ return {
119
+ url: `file://${contentPath}`
120
+ };
121
+ }
122
+ // Overwrite content in Vercel Blob
123
+ const contentBlob = await put(getProjectBlobPath(slug), JSON.stringify(content), {
124
+ access: 'public',
125
+ contentType: 'application/json',
126
+ allowOverwrite: true
127
+ });
128
+ // Update metadata
129
+ const metadata = {
130
+ slug,
131
+ name: content.name,
132
+ createdAt: content.createdAt,
133
+ updatedAt: now,
134
+ blobUrl: contentBlob.url
135
+ };
136
+ await put(getProjectMetadataPath(slug), JSON.stringify(metadata), {
137
+ access: 'public',
138
+ contentType: 'application/json',
139
+ allowOverwrite: true
140
+ });
141
+ return {
142
+ url: contentBlob.url
143
+ };
144
+ }
145
+ /**
146
+ * Get project content from Vercel Blob (or local filesystem in dev)
147
+ */ export async function getProjectContent(slug) {
148
+ try {
149
+ // Use local filesystem in development
150
+ if (IS_LOCAL_DEV) {
151
+ const contentPath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'content.json');
152
+ if (!fs.existsSync(contentPath)) {
153
+ return null;
154
+ }
155
+ const data = fs.readFileSync(contentPath, 'utf-8');
156
+ return JSON.parse(data);
157
+ }
158
+ const blobPath = getProjectBlobPath(slug);
159
+ const blobInfo = await head(blobPath);
160
+ if (!blobInfo) {
161
+ return null;
162
+ }
163
+ const response = await fetch(blobInfo.url);
164
+ if (!response.ok) {
165
+ return null;
166
+ }
167
+ const content = await response.json();
168
+ return content;
169
+ } catch (error) {
170
+ console.error(`[Blob] Error fetching project ${slug}:`, error);
171
+ return null;
172
+ }
173
+ }
174
+ /**
175
+ * Get project metadata (quick lookup without full content)
176
+ */ export async function getProjectMetadata(slug) {
177
+ try {
178
+ const blobPath = getProjectMetadataPath(slug);
179
+ const blobInfo = await head(blobPath);
180
+ if (!blobInfo) {
181
+ return null;
182
+ }
183
+ const response = await fetch(blobInfo.url);
184
+ if (!response.ok) {
185
+ return null;
186
+ }
187
+ const metadata = await response.json();
188
+ return metadata;
189
+ } catch (error) {
190
+ console.error(`[Blob] Error fetching metadata for ${slug}:`, error);
191
+ return null;
192
+ }
193
+ }
194
+ /**
195
+ * Get a specific file from project content
196
+ */ export async function getProjectFile(slug, filePath) {
197
+ const content = await getProjectContent(slug);
198
+ if (!content) {
199
+ return null;
200
+ }
201
+ const file = content.files.find((f)=>f.path === filePath);
202
+ return file?.content || null;
203
+ }
204
+ /**
205
+ * Get docs.json for a project
206
+ */ export async function getProjectDocsJson(slug) {
207
+ const content = await getProjectContent(slug);
208
+ if (!content) {
209
+ return null;
210
+ }
211
+ try {
212
+ return JSON.parse(content.docsJson);
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+ /**
218
+ * Check if a project exists
219
+ */ export async function projectExists(slug) {
220
+ try {
221
+ // Use local filesystem in development
222
+ if (IS_LOCAL_DEV) {
223
+ const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug);
224
+ // Check for either metadata.json (full project) or apikey.json (registered project)
225
+ const metadataPath = path.join(projectDir, 'metadata.json');
226
+ const apiKeyPath = path.join(projectDir, 'apikey.json');
227
+ return fs.existsSync(metadataPath) || fs.existsSync(apiKeyPath);
228
+ }
229
+ // In production, check both metadata and apikey paths
230
+ const metadataPath = getProjectMetadataPath(slug);
231
+ const apiKeyPath = getApiKeyBlobPath(slug);
232
+ const [metadataInfo, apiKeyInfo] = await Promise.all([
233
+ head(metadataPath).catch(()=>null),
234
+ head(apiKeyPath).catch(()=>null)
235
+ ]);
236
+ return metadataInfo !== null || apiKeyInfo !== null;
237
+ } catch {
238
+ return false;
239
+ }
240
+ }
241
+ /**
242
+ * Delete a project and all its content
243
+ */ export async function deleteProject(slug) {
244
+ try {
245
+ // List all blobs for this project
246
+ const { blobs } = await list({
247
+ prefix: `projects/${slug}/`
248
+ });
249
+ // Delete all blobs
250
+ for (const blob of blobs){
251
+ await del(blob.url);
252
+ }
253
+ } catch (error) {
254
+ console.error(`[Blob] Error deleting project ${slug}:`, error);
255
+ throw error;
256
+ }
257
+ }
258
+ /**
259
+ * List all projects (for admin purposes)
260
+ */ export async function listProjects() {
261
+ try {
262
+ const { blobs } = await list({
263
+ prefix: 'projects/'
264
+ });
265
+ // Filter for metadata files only
266
+ const metadataBlobs = blobs.filter((b)=>b.pathname.endsWith('/metadata.json'));
267
+ const projects = [];
268
+ for (const blob of metadataBlobs){
269
+ try {
270
+ const response = await fetch(blob.url);
271
+ if (response.ok) {
272
+ const metadata = await response.json();
273
+ projects.push(metadata);
274
+ }
275
+ } catch {
276
+ // Skip invalid entries
277
+ }
278
+ }
279
+ return projects;
280
+ } catch (error) {
281
+ console.error('[Blob] Error listing projects:', error);
282
+ return [];
283
+ }
284
+ }
285
+ /**
286
+ * Get the blob path for a project's API key
287
+ */ function getApiKeyBlobPath(slug) {
288
+ return `projects/${slug}/apikey.json`;
289
+ }
290
+ const REGISTRY_PATH = 'registry/domains.json';
291
+ const LOCAL_REGISTRY_PATH = path.join(LOCAL_STORAGE_DIR, 'registry', 'domains.json');
292
+ /**
293
+ * Get the domain registry (cached in memory for performance)
294
+ */ let registryCache = null;
295
+ let registryCacheTime = 0;
296
+ const CACHE_TTL = 5000 // 5 seconds
297
+ ;
298
+ async function getRegistry() {
299
+ const now = Date.now();
300
+ // Return cached if fresh
301
+ if (registryCache && now - registryCacheTime < CACHE_TTL) {
302
+ return registryCache;
303
+ }
304
+ try {
305
+ if (IS_LOCAL_DEV) {
306
+ if (fs.existsSync(LOCAL_REGISTRY_PATH)) {
307
+ const data = fs.readFileSync(LOCAL_REGISTRY_PATH, 'utf-8');
308
+ registryCache = JSON.parse(data);
309
+ registryCacheTime = now;
310
+ return registryCache;
311
+ }
312
+ } else {
313
+ const blobInfo = await head(REGISTRY_PATH).catch(()=>null);
314
+ if (blobInfo) {
315
+ const response = await fetch(blobInfo.url);
316
+ if (response.ok) {
317
+ registryCache = await response.json();
318
+ registryCacheTime = now;
319
+ return registryCache;
320
+ }
321
+ }
322
+ }
323
+ } catch (error) {
324
+ console.error('[Registry] Error loading registry:', error);
325
+ }
326
+ // Return empty registry if not found
327
+ return {
328
+ domains: {},
329
+ customDomains: {},
330
+ projectToCustomDomain: {},
331
+ apiKeys: {},
332
+ updatedAt: new Date().toISOString()
333
+ };
334
+ }
335
+ /**
336
+ * Save the domain registry
337
+ */ async function saveRegistry(registry) {
338
+ registry.updatedAt = new Date().toISOString();
339
+ if (IS_LOCAL_DEV) {
340
+ const dir = path.dirname(LOCAL_REGISTRY_PATH);
341
+ fs.mkdirSync(dir, {
342
+ recursive: true
343
+ });
344
+ fs.writeFileSync(LOCAL_REGISTRY_PATH, JSON.stringify(registry, null, 2));
345
+ } else {
346
+ await put(REGISTRY_PATH, JSON.stringify(registry), {
347
+ access: 'public',
348
+ contentType: 'application/json',
349
+ allowOverwrite: true
350
+ });
351
+ }
352
+ // Update cache
353
+ registryCache = registry;
354
+ registryCacheTime = Date.now();
355
+ }
356
+ /**
357
+ * Check if a subdomain is registered (O(1) lookup)
358
+ */ export async function isSubdomainRegistered(subdomain) {
359
+ const registry = await getRegistry();
360
+ return subdomain in registry.domains;
361
+ }
362
+ /**
363
+ * Register a new subdomain in the registry
364
+ */ export async function registerSubdomain(subdomain, projectId, name, apiKey) {
365
+ const registry = await getRegistry();
366
+ const now = new Date().toISOString();
367
+ registry.domains[subdomain] = {
368
+ subdomain,
369
+ projectId,
370
+ name,
371
+ createdAt: now
372
+ };
373
+ registry.apiKeys[apiKey] = subdomain;
374
+ await saveRegistry(registry);
375
+ }
376
+ /**
377
+ * Validate API key and get subdomain (O(1) lookup)
378
+ */ export async function validateApiKeyFromRegistry(apiKey) {
379
+ if (!apiKey || !apiKey.startsWith('sk_live_') || apiKey.length !== 40) {
380
+ return null;
381
+ }
382
+ const registry = await getRegistry();
383
+ return registry.apiKeys[apiKey] || null;
384
+ }
385
+ /**
386
+ * Get domain entry from registry
387
+ */ export async function getDomainEntry(subdomain) {
388
+ const registry = await getRegistry();
389
+ return registry.domains[subdomain] || null;
390
+ }
391
+ // =============================================================================
392
+ // Custom Domain Management - One custom domain per project (free)
393
+ // =============================================================================
394
+ /**
395
+ * Generate a verification token for domain ownership
396
+ */ export function generateVerificationToken() {
397
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
398
+ let token = 'devdoc-verify=';
399
+ for(let i = 0; i < 24; i++){
400
+ token += chars.charAt(Math.floor(Math.random() * chars.length));
401
+ }
402
+ return token;
403
+ }
404
+ /**
405
+ * Check if a custom domain is already registered (O(1) lookup)
406
+ */ export async function isCustomDomainRegistered(customDomain) {
407
+ const registry = await getRegistry();
408
+ return customDomain in (registry.customDomains || {});
409
+ }
410
+ /**
411
+ * Check if a project already has a custom domain (O(1) lookup)
412
+ */ export async function getProjectCustomDomain(projectSlug) {
413
+ const registry = await getRegistry();
414
+ const customDomain = registry.projectToCustomDomain?.[projectSlug];
415
+ if (!customDomain) return null;
416
+ return registry.customDomains?.[customDomain] || null;
417
+ }
418
+ /**
419
+ * Get custom domain entry by domain name (O(1) lookup)
420
+ */ export async function getCustomDomainEntry(customDomain) {
421
+ const registry = await getRegistry();
422
+ return registry.customDomains?.[customDomain] || null;
423
+ }
424
+ /**
425
+ * Look up project slug from custom domain (O(1) lookup)
426
+ * Used by middleware for routing
427
+ */ export async function lookupCustomDomain(customDomain) {
428
+ const registry = await getRegistry();
429
+ const entry = registry.customDomains?.[customDomain];
430
+ // Only return if domain is active
431
+ if (entry && entry.status === 'active') {
432
+ return entry;
433
+ }
434
+ return null;
435
+ }
436
+ /**
437
+ * Add a custom domain to a project
438
+ * Returns error if project already has a custom domain (limit: 1 per project)
439
+ */ export async function addCustomDomain(projectSlug, customDomain) {
440
+ const registry = await getRegistry();
441
+ // Initialize custom domain maps if they don't exist
442
+ if (!registry.customDomains) {
443
+ registry.customDomains = {};
444
+ }
445
+ if (!registry.projectToCustomDomain) {
446
+ registry.projectToCustomDomain = {};
447
+ }
448
+ // Check if project already has a custom domain (limit: 1 per project)
449
+ const existingDomain = registry.projectToCustomDomain[projectSlug];
450
+ if (existingDomain) {
451
+ return {
452
+ success: false,
453
+ error: `Project already has a custom domain: ${existingDomain}. Remove it first before adding a new one.`
454
+ };
455
+ }
456
+ // Check if this domain is already registered to another project
457
+ if (registry.customDomains[customDomain]) {
458
+ return {
459
+ success: false,
460
+ error: `Domain ${customDomain} is already registered to another project.`
461
+ };
462
+ }
463
+ // Verify the project exists
464
+ const projectExists = await getDomainEntry(projectSlug);
465
+ if (!projectExists) {
466
+ return {
467
+ success: false,
468
+ error: `Project ${projectSlug} not found. Deploy your project first.`
469
+ };
470
+ }
471
+ const now = new Date().toISOString();
472
+ const verificationToken = generateVerificationToken();
473
+ const entry = {
474
+ customDomain,
475
+ projectSlug,
476
+ status: 'pending',
477
+ verificationToken,
478
+ createdAt: now
479
+ };
480
+ // Add to both lookups
481
+ registry.customDomains[customDomain] = entry;
482
+ registry.projectToCustomDomain[projectSlug] = customDomain;
483
+ await saveRegistry(registry);
484
+ return {
485
+ success: true,
486
+ entry
487
+ };
488
+ }
489
+ /**
490
+ * Update custom domain status
491
+ */ export async function updateCustomDomainStatus(customDomain, status, additionalFields) {
492
+ const registry = await getRegistry();
493
+ if (!registry.customDomains?.[customDomain]) {
494
+ return false;
495
+ }
496
+ registry.customDomains[customDomain] = {
497
+ ...registry.customDomains[customDomain],
498
+ status,
499
+ lastCheckedAt: new Date().toISOString(),
500
+ ...additionalFields
501
+ };
502
+ // Set verifiedAt when transitioning to dns_verified
503
+ if (status === 'dns_verified' && !registry.customDomains[customDomain].verifiedAt) {
504
+ registry.customDomains[customDomain].verifiedAt = new Date().toISOString();
505
+ }
506
+ await saveRegistry(registry);
507
+ return true;
508
+ }
509
+ /**
510
+ * Remove a custom domain from a project
511
+ */ export async function removeCustomDomain(customDomain, projectSlug) {
512
+ const registry = await getRegistry();
513
+ // Check if domain exists and belongs to this project
514
+ const entry = registry.customDomains?.[customDomain];
515
+ if (!entry) {
516
+ return {
517
+ success: false,
518
+ error: `Domain ${customDomain} not found.`
519
+ };
520
+ }
521
+ if (entry.projectSlug !== projectSlug) {
522
+ return {
523
+ success: false,
524
+ error: `Domain ${customDomain} does not belong to this project.`
525
+ };
526
+ }
527
+ // Remove from both lookups
528
+ delete registry.customDomains[customDomain];
529
+ delete registry.projectToCustomDomain[projectSlug];
530
+ await saveRegistry(registry);
531
+ return {
532
+ success: true
533
+ };
534
+ }
535
+ /**
536
+ * List all custom domains (for admin purposes)
537
+ */ export async function listCustomDomains() {
538
+ const registry = await getRegistry();
539
+ return Object.values(registry.customDomains || {});
540
+ }
541
+ /**
542
+ * Generate a secure API key
543
+ */ export function generateApiKey() {
544
+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
545
+ let key = 'sk_live_';
546
+ for(let i = 0; i < 32; i++){
547
+ key += chars.charAt(Math.floor(Math.random() * chars.length));
548
+ }
549
+ return key;
550
+ }
551
+ /**
552
+ * Store API key for a project
553
+ */ export async function storeProjectApiKey(slug, apiKey) {
554
+ const keyData = {
555
+ key: apiKey,
556
+ slug,
557
+ createdAt: new Date().toISOString()
558
+ };
559
+ // Use local filesystem in development
560
+ if (IS_LOCAL_DEV) {
561
+ const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug);
562
+ fs.mkdirSync(projectDir, {
563
+ recursive: true
564
+ });
565
+ fs.writeFileSync(path.join(projectDir, 'apikey.json'), JSON.stringify(keyData, null, 2));
566
+ return;
567
+ }
568
+ await put(getApiKeyBlobPath(slug), JSON.stringify(keyData), {
569
+ access: 'public',
570
+ contentType: 'application/json',
571
+ allowOverwrite: true
572
+ });
573
+ }
574
+ /**
575
+ * Get API key data for a project
576
+ */ export async function getProjectApiKey(slug) {
577
+ try {
578
+ // Use local filesystem in development
579
+ if (IS_LOCAL_DEV) {
580
+ const keyPath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'apikey.json');
581
+ if (!fs.existsSync(keyPath)) {
582
+ return null;
583
+ }
584
+ const data = fs.readFileSync(keyPath, 'utf-8');
585
+ return JSON.parse(data);
586
+ }
587
+ const blobPath = getApiKeyBlobPath(slug);
588
+ const blobInfo = await head(blobPath);
589
+ if (!blobInfo) {
590
+ return null;
591
+ }
592
+ const response = await fetch(blobInfo.url);
593
+ if (!response.ok) {
594
+ return null;
595
+ }
596
+ return await response.json();
597
+ } catch (error) {
598
+ console.error(`[Blob] Error fetching API key for ${slug}:`, error);
599
+ return null;
600
+ }
601
+ }
602
+ /**
603
+ * Validate an API key and return the associated project slug
604
+ * O(1) lookup from registry
605
+ */ export async function validateApiKey(apiKey) {
606
+ // API key format: sk_live_<32chars>
607
+ if (!apiKey || !apiKey.startsWith('sk_live_') || apiKey.length !== 40) {
608
+ return null;
609
+ }
610
+ try {
611
+ // O(1) registry lookup
612
+ const slug = await validateApiKeyFromRegistry(apiKey);
613
+ if (slug) {
614
+ // Update last used timestamp
615
+ await updateApiKeyLastUsed(slug);
616
+ }
617
+ return slug;
618
+ } catch (error) {
619
+ console.error('[Blob] Error validating API key:', error);
620
+ return null;
621
+ }
622
+ }
623
+ /**
624
+ * Update the lastUsedAt timestamp for an API key
625
+ */ async function updateApiKeyLastUsed(slug) {
626
+ try {
627
+ if (IS_LOCAL_DEV) {
628
+ const keyPath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'apikey.json');
629
+ if (fs.existsSync(keyPath)) {
630
+ const data = JSON.parse(fs.readFileSync(keyPath, 'utf-8'));
631
+ data.lastUsedAt = new Date().toISOString();
632
+ fs.writeFileSync(keyPath, JSON.stringify(data, null, 2));
633
+ }
634
+ } else {
635
+ const blobPath = getApiKeyBlobPath(slug);
636
+ const blobInfo = await head(blobPath).catch(()=>null);
637
+ if (blobInfo) {
638
+ const response = await fetch(blobInfo.url);
639
+ if (response.ok) {
640
+ const keyData = await response.json();
641
+ keyData.lastUsedAt = new Date().toISOString();
642
+ await put(blobPath, JSON.stringify(keyData), {
643
+ access: 'public',
644
+ contentType: 'application/json',
645
+ allowOverwrite: true
646
+ });
647
+ }
648
+ }
649
+ }
650
+ } catch {
651
+ // Non-critical, ignore errors
652
+ }
653
+ }
654
+ /**
655
+ * Regenerate API key for a project (requires old key for auth)
656
+ */ export async function regenerateApiKey(slug, oldApiKey) {
657
+ // Validate old key first
658
+ const validSlug = await validateApiKey(oldApiKey);
659
+ if (validSlug !== slug) {
660
+ return null;
661
+ }
662
+ // Generate new key
663
+ const newKey = generateApiKey();
664
+ // Update registry: remove old key, add new key
665
+ const registry = await getRegistry();
666
+ delete registry.apiKeys[oldApiKey];
667
+ registry.apiKeys[newKey] = slug;
668
+ await saveRegistry(registry);
669
+ // Also update project's apikey.json
670
+ await storeProjectApiKey(slug, newKey);
671
+ return newKey;
672
+ }
673
+ /**
674
+ * Get the blob path for a project's assets
675
+ */ export function getAssetBlobPath(slug, fileName) {
676
+ return `projects/${slug}/assets/${fileName}`;
677
+ }
678
+ /**
679
+ * List all assets for a project
680
+ */ export async function listProjectAssets(slug) {
681
+ try {
682
+ if (IS_LOCAL_DEV) {
683
+ const assetsDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'assets');
684
+ if (!fs.existsSync(assetsDir)) {
685
+ return [];
686
+ }
687
+ const files = fs.readdirSync(assetsDir);
688
+ return files.map((fileName)=>{
689
+ const filePath = path.join(assetsDir, fileName);
690
+ const stats = fs.statSync(filePath);
691
+ return {
692
+ path: `projects/${slug}/assets/${fileName}`,
693
+ url: `file://${filePath}`,
694
+ fileName,
695
+ size: stats.size,
696
+ contentType: getContentType(fileName),
697
+ uploadedAt: stats.mtime.toISOString()
698
+ };
699
+ });
700
+ }
701
+ const { blobs } = await list({
702
+ prefix: `projects/${slug}/assets/`
703
+ });
704
+ return blobs.map((blob)=>{
705
+ const fileName = blob.pathname.split('/').pop() || '';
706
+ return {
707
+ path: blob.pathname,
708
+ url: blob.url,
709
+ fileName,
710
+ size: blob.size,
711
+ contentType: getContentType(fileName),
712
+ uploadedAt: blob.uploadedAt.toISOString()
713
+ };
714
+ });
715
+ } catch (error) {
716
+ console.error(`[Blob] Error listing assets for ${slug}:`, error);
717
+ return [];
718
+ }
719
+ }
720
+ /**
721
+ * Delete an asset
722
+ */ export async function deleteProjectAsset(slug, fileName) {
723
+ try {
724
+ if (IS_LOCAL_DEV) {
725
+ const filePath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'assets', fileName);
726
+ if (fs.existsSync(filePath)) {
727
+ fs.unlinkSync(filePath);
728
+ return true;
729
+ }
730
+ return false;
731
+ }
732
+ const blobPath = getAssetBlobPath(slug, fileName);
733
+ const blobInfo = await head(blobPath).catch(()=>null);
734
+ if (blobInfo) {
735
+ await del(blobInfo.url);
736
+ return true;
737
+ }
738
+ return false;
739
+ } catch (error) {
740
+ console.error(`[Blob] Error deleting asset ${fileName} for ${slug}:`, error);
741
+ return false;
742
+ }
743
+ }
744
+ /**
745
+ * Helper to get content type from file extension
746
+ */ function getContentType(fileName) {
747
+ const ext = fileName.split('.').pop()?.toLowerCase();
748
+ const types = {
749
+ 'jpg': 'image/jpeg',
750
+ 'jpeg': 'image/jpeg',
751
+ 'png': 'image/png',
752
+ 'gif': 'image/gif',
753
+ 'webp': 'image/webp',
754
+ 'svg': 'image/svg+xml',
755
+ 'ico': 'image/x-icon',
756
+ 'pdf': 'application/pdf',
757
+ 'mp4': 'video/mp4',
758
+ 'webm': 'video/webm',
759
+ 'mp3': 'audio/mpeg',
760
+ 'wav': 'audio/wav',
761
+ 'woff': 'font/woff',
762
+ 'woff2': 'font/woff2',
763
+ 'ttf': 'font/ttf',
764
+ 'otf': 'font/otf'
765
+ };
766
+ return types[ext || ''] || 'application/octet-stream';
767
+ }