@brainfish-ai/devdoc 0.1.21

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 (268) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +415 -0
  3. package/bin/devdoc.js +13 -0
  4. package/dist/cli/commands/build.d.ts +5 -0
  5. package/dist/cli/commands/build.js +87 -0
  6. package/dist/cli/commands/check.d.ts +1 -0
  7. package/dist/cli/commands/check.js +143 -0
  8. package/dist/cli/commands/create.d.ts +24 -0
  9. package/dist/cli/commands/create.js +387 -0
  10. package/dist/cli/commands/deploy.d.ts +9 -0
  11. package/dist/cli/commands/deploy.js +433 -0
  12. package/dist/cli/commands/dev.d.ts +6 -0
  13. package/dist/cli/commands/dev.js +139 -0
  14. package/dist/cli/commands/init.d.ts +11 -0
  15. package/dist/cli/commands/init.js +238 -0
  16. package/dist/cli/commands/keys.d.ts +12 -0
  17. package/dist/cli/commands/keys.js +165 -0
  18. package/dist/cli/commands/start.d.ts +5 -0
  19. package/dist/cli/commands/start.js +56 -0
  20. package/dist/cli/commands/upload.d.ts +13 -0
  21. package/dist/cli/commands/upload.js +238 -0
  22. package/dist/cli/commands/whoami.d.ts +8 -0
  23. package/dist/cli/commands/whoami.js +91 -0
  24. package/dist/cli/index.d.ts +1 -0
  25. package/dist/cli/index.js +106 -0
  26. package/dist/config/index.d.ts +80 -0
  27. package/dist/config/index.js +133 -0
  28. package/dist/constants.d.ts +9 -0
  29. package/dist/constants.js +13 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/index.js +12 -0
  32. package/dist/utils/logger.d.ts +16 -0
  33. package/dist/utils/logger.js +61 -0
  34. package/dist/utils/paths.d.ts +16 -0
  35. package/dist/utils/paths.js +50 -0
  36. package/package.json +51 -0
  37. package/renderer/app/api/assets/[...path]/route.ts +123 -0
  38. package/renderer/app/api/assets/route.ts +124 -0
  39. package/renderer/app/api/assets/upload/route.ts +177 -0
  40. package/renderer/app/api/auth-schemes/route.ts +77 -0
  41. package/renderer/app/api/chat/route.ts +858 -0
  42. package/renderer/app/api/codegen/route.ts +72 -0
  43. package/renderer/app/api/collections/route.ts +1016 -0
  44. package/renderer/app/api/debug/route.ts +53 -0
  45. package/renderer/app/api/deploy/route.ts +234 -0
  46. package/renderer/app/api/device/route.ts +42 -0
  47. package/renderer/app/api/docs/route.ts +187 -0
  48. package/renderer/app/api/keys/regenerate/route.ts +80 -0
  49. package/renderer/app/api/openapi-spec/route.ts +151 -0
  50. package/renderer/app/api/projects/[slug]/route.ts +153 -0
  51. package/renderer/app/api/projects/[slug]/stats/route.ts +96 -0
  52. package/renderer/app/api/projects/register/route.ts +152 -0
  53. package/renderer/app/api/proxy/route.ts +149 -0
  54. package/renderer/app/api/proxy-stream/route.ts +168 -0
  55. package/renderer/app/api/redirects/route.ts +47 -0
  56. package/renderer/app/api/schema/route.ts +65 -0
  57. package/renderer/app/api/subdomains/check/route.ts +172 -0
  58. package/renderer/app/api/suggestions/route.ts +144 -0
  59. package/renderer/app/favicon.ico +0 -0
  60. package/renderer/app/globals.css +1103 -0
  61. package/renderer/app/layout.tsx +47 -0
  62. package/renderer/app/llms-full.txt/route.ts +346 -0
  63. package/renderer/app/llms.txt/route.ts +279 -0
  64. package/renderer/app/page.tsx +14 -0
  65. package/renderer/app/robots.txt/route.ts +84 -0
  66. package/renderer/app/sitemap.xml/route.ts +199 -0
  67. package/renderer/components/docs/index.ts +12 -0
  68. package/renderer/components/docs/mdx/accordion.tsx +169 -0
  69. package/renderer/components/docs/mdx/badge.tsx +132 -0
  70. package/renderer/components/docs/mdx/callouts.tsx +154 -0
  71. package/renderer/components/docs/mdx/cards.tsx +213 -0
  72. package/renderer/components/docs/mdx/changelog.tsx +120 -0
  73. package/renderer/components/docs/mdx/code-block.tsx +186 -0
  74. package/renderer/components/docs/mdx/code-group.tsx +421 -0
  75. package/renderer/components/docs/mdx/file-embeds.tsx +105 -0
  76. package/renderer/components/docs/mdx/frame.tsx +112 -0
  77. package/renderer/components/docs/mdx/highlight.tsx +151 -0
  78. package/renderer/components/docs/mdx/iframe.tsx +134 -0
  79. package/renderer/components/docs/mdx/image.tsx +235 -0
  80. package/renderer/components/docs/mdx/index.ts +204 -0
  81. package/renderer/components/docs/mdx/mermaid.tsx +240 -0
  82. package/renderer/components/docs/mdx/param-field.tsx +200 -0
  83. package/renderer/components/docs/mdx/steps.tsx +113 -0
  84. package/renderer/components/docs/mdx/tabs.tsx +86 -0
  85. package/renderer/components/docs/mdx-renderer.tsx +100 -0
  86. package/renderer/components/docs/navigation/breadcrumbs.tsx +76 -0
  87. package/renderer/components/docs/navigation/index.ts +8 -0
  88. package/renderer/components/docs/navigation/page-nav.tsx +64 -0
  89. package/renderer/components/docs/navigation/sidebar.tsx +515 -0
  90. package/renderer/components/docs/navigation/toc.tsx +113 -0
  91. package/renderer/components/docs/notice.tsx +105 -0
  92. package/renderer/components/docs-header.tsx +274 -0
  93. package/renderer/components/docs-viewer/agent/agent-chat.tsx +2076 -0
  94. package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +90 -0
  95. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +49 -0
  96. package/renderer/components/docs-viewer/agent/cards/index.tsx +50 -0
  97. package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +212 -0
  98. package/renderer/components/docs-viewer/agent/cards/types.ts +84 -0
  99. package/renderer/components/docs-viewer/agent/chat-message.tsx +17 -0
  100. package/renderer/components/docs-viewer/agent/index.tsx +6 -0
  101. package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +119 -0
  102. package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +46 -0
  103. package/renderer/components/docs-viewer/agent/messages/index.ts +17 -0
  104. package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +721 -0
  105. package/renderer/components/docs-viewer/agent/messages/types.ts +61 -0
  106. package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +24 -0
  107. package/renderer/components/docs-viewer/agent/messages/user-message.tsx +51 -0
  108. package/renderer/components/docs-viewer/code-editor/index.tsx +2 -0
  109. package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +1283 -0
  110. package/renderer/components/docs-viewer/content/changelog-page.tsx +331 -0
  111. package/renderer/components/docs-viewer/content/doc-page.tsx +285 -0
  112. package/renderer/components/docs-viewer/content/documentation-viewer.tsx +17 -0
  113. package/renderer/components/docs-viewer/content/index.tsx +29 -0
  114. package/renderer/components/docs-viewer/content/introduction.tsx +21 -0
  115. package/renderer/components/docs-viewer/content/request-details.tsx +330 -0
  116. package/renderer/components/docs-viewer/content/sections/auth.tsx +69 -0
  117. package/renderer/components/docs-viewer/content/sections/body.tsx +66 -0
  118. package/renderer/components/docs-viewer/content/sections/headers.tsx +43 -0
  119. package/renderer/components/docs-viewer/content/sections/overview.tsx +40 -0
  120. package/renderer/components/docs-viewer/content/sections/parameters.tsx +43 -0
  121. package/renderer/components/docs-viewer/content/sections/responses.tsx +87 -0
  122. package/renderer/components/docs-viewer/global-auth-modal.tsx +352 -0
  123. package/renderer/components/docs-viewer/index.tsx +1466 -0
  124. package/renderer/components/docs-viewer/playground/auth-editor.tsx +280 -0
  125. package/renderer/components/docs-viewer/playground/body-editor.tsx +221 -0
  126. package/renderer/components/docs-viewer/playground/code-editor.tsx +224 -0
  127. package/renderer/components/docs-viewer/playground/code-snippet.tsx +387 -0
  128. package/renderer/components/docs-viewer/playground/graphql-playground.tsx +745 -0
  129. package/renderer/components/docs-viewer/playground/index.tsx +671 -0
  130. package/renderer/components/docs-viewer/playground/key-value-editor.tsx +261 -0
  131. package/renderer/components/docs-viewer/playground/method-selector.tsx +60 -0
  132. package/renderer/components/docs-viewer/playground/request-builder.tsx +179 -0
  133. package/renderer/components/docs-viewer/playground/request-tabs.tsx +237 -0
  134. package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +21 -0
  135. package/renderer/components/docs-viewer/playground/response-cards/index.tsx +93 -0
  136. package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +16 -0
  137. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +23 -0
  138. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +268 -0
  139. package/renderer/components/docs-viewer/playground/response-cards/types.ts +82 -0
  140. package/renderer/components/docs-viewer/playground/response-viewer.tsx +43 -0
  141. package/renderer/components/docs-viewer/search/index.ts +2 -0
  142. package/renderer/components/docs-viewer/search/search-dialog.tsx +331 -0
  143. package/renderer/components/docs-viewer/search/use-search.ts +117 -0
  144. package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +431 -0
  145. package/renderer/components/docs-viewer/shared/method-badge.tsx +41 -0
  146. package/renderer/components/docs-viewer/shared/schema-viewer.tsx +349 -0
  147. package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +239 -0
  148. package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +316 -0
  149. package/renderer/components/docs-viewer/sidebar/index.tsx +343 -0
  150. package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +202 -0
  151. package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +118 -0
  152. package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +226 -0
  153. package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +52 -0
  154. package/renderer/components/theme-provider.tsx +11 -0
  155. package/renderer/components/theme-toggle.tsx +76 -0
  156. package/renderer/components/ui/badge.tsx +46 -0
  157. package/renderer/components/ui/button.tsx +59 -0
  158. package/renderer/components/ui/dialog.tsx +118 -0
  159. package/renderer/components/ui/dropdown-menu.tsx +257 -0
  160. package/renderer/components/ui/input.tsx +21 -0
  161. package/renderer/components/ui/label.tsx +24 -0
  162. package/renderer/components/ui/navigation-menu.tsx +168 -0
  163. package/renderer/components/ui/select.tsx +190 -0
  164. package/renderer/components/ui/spinner.tsx +114 -0
  165. package/renderer/components/ui/tabs.tsx +66 -0
  166. package/renderer/components/ui/tooltip.tsx +61 -0
  167. package/renderer/hooks/use-code-copy.ts +88 -0
  168. package/renderer/hooks/use-openapi-title.ts +44 -0
  169. package/renderer/lib/api-docs/agent/index.ts +6 -0
  170. package/renderer/lib/api-docs/agent/indexer.ts +323 -0
  171. package/renderer/lib/api-docs/agent/spec-summary.ts +335 -0
  172. package/renderer/lib/api-docs/agent/types.ts +116 -0
  173. package/renderer/lib/api-docs/auth/auth-context.tsx +225 -0
  174. package/renderer/lib/api-docs/auth/auth-storage.ts +87 -0
  175. package/renderer/lib/api-docs/auth/crypto.ts +89 -0
  176. package/renderer/lib/api-docs/auth/index.ts +4 -0
  177. package/renderer/lib/api-docs/code-editor/db.ts +164 -0
  178. package/renderer/lib/api-docs/code-editor/hooks.ts +266 -0
  179. package/renderer/lib/api-docs/code-editor/index.ts +6 -0
  180. package/renderer/lib/api-docs/code-editor/mode-context.tsx +207 -0
  181. package/renderer/lib/api-docs/code-editor/types.ts +105 -0
  182. package/renderer/lib/api-docs/codegen/definitions.ts +297 -0
  183. package/renderer/lib/api-docs/codegen/har.ts +251 -0
  184. package/renderer/lib/api-docs/codegen/index.ts +159 -0
  185. package/renderer/lib/api-docs/factories.ts +151 -0
  186. package/renderer/lib/api-docs/index.ts +17 -0
  187. package/renderer/lib/api-docs/mobile-context.tsx +112 -0
  188. package/renderer/lib/api-docs/navigation-context.tsx +88 -0
  189. package/renderer/lib/api-docs/parsers/graphql/README.md +129 -0
  190. package/renderer/lib/api-docs/parsers/graphql/index.ts +91 -0
  191. package/renderer/lib/api-docs/parsers/graphql/parser.ts +491 -0
  192. package/renderer/lib/api-docs/parsers/graphql/transformer.ts +246 -0
  193. package/renderer/lib/api-docs/parsers/graphql/types.ts +283 -0
  194. package/renderer/lib/api-docs/parsers/openapi/README.md +32 -0
  195. package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +60 -0
  196. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +574 -0
  197. package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +403 -0
  198. package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +232 -0
  199. package/renderer/lib/api-docs/parsers/openapi/index.ts +171 -0
  200. package/renderer/lib/api-docs/parsers/openapi/transformer.ts +277 -0
  201. package/renderer/lib/api-docs/parsers/openapi/validator.ts +31 -0
  202. package/renderer/lib/api-docs/playground/context.tsx +107 -0
  203. package/renderer/lib/api-docs/playground/navigation-context.tsx +124 -0
  204. package/renderer/lib/api-docs/playground/request-builder.ts +223 -0
  205. package/renderer/lib/api-docs/playground/request-runner.ts +282 -0
  206. package/renderer/lib/api-docs/playground/types.ts +35 -0
  207. package/renderer/lib/api-docs/types.ts +269 -0
  208. package/renderer/lib/api-docs/utils.ts +311 -0
  209. package/renderer/lib/cache.ts +193 -0
  210. package/renderer/lib/docs/config/index.ts +29 -0
  211. package/renderer/lib/docs/config/loader.ts +142 -0
  212. package/renderer/lib/docs/config/schema.ts +298 -0
  213. package/renderer/lib/docs/index.ts +12 -0
  214. package/renderer/lib/docs/mdx/compiler.ts +176 -0
  215. package/renderer/lib/docs/mdx/frontmatter.ts +80 -0
  216. package/renderer/lib/docs/mdx/index.ts +26 -0
  217. package/renderer/lib/docs/navigation/generator.ts +348 -0
  218. package/renderer/lib/docs/navigation/index.ts +12 -0
  219. package/renderer/lib/docs/navigation/types.ts +123 -0
  220. package/renderer/lib/docs-navigation-context.tsx +80 -0
  221. package/renderer/lib/multi-tenant/context.ts +105 -0
  222. package/renderer/lib/storage/blob.ts +845 -0
  223. package/renderer/lib/utils.ts +6 -0
  224. package/renderer/next.config.ts +76 -0
  225. package/renderer/package.json +66 -0
  226. package/renderer/postcss.config.mjs +5 -0
  227. package/renderer/public/assets/images/screenshot.png +0 -0
  228. package/renderer/public/assets/logo/dark.svg +9 -0
  229. package/renderer/public/assets/logo/light.svg +9 -0
  230. package/renderer/public/assets/logo.svg +9 -0
  231. package/renderer/public/file.svg +1 -0
  232. package/renderer/public/globe.svg +1 -0
  233. package/renderer/public/icon.png +0 -0
  234. package/renderer/public/logo.svg +9 -0
  235. package/renderer/public/window.svg +1 -0
  236. package/renderer/tsconfig.json +28 -0
  237. package/templates/basic/README.md +139 -0
  238. package/templates/basic/assets/favicon.svg +4 -0
  239. package/templates/basic/assets/logo.svg +9 -0
  240. package/templates/basic/docs.json +47 -0
  241. package/templates/basic/guides/configuration.mdx +149 -0
  242. package/templates/basic/guides/overview.mdx +96 -0
  243. package/templates/basic/index.mdx +39 -0
  244. package/templates/basic/package.json +14 -0
  245. package/templates/basic/quickstart.mdx +92 -0
  246. package/templates/basic/vercel.json +6 -0
  247. package/templates/graphql/README.md +139 -0
  248. package/templates/graphql/api-reference/schema.graphql +305 -0
  249. package/templates/graphql/assets/favicon.svg +4 -0
  250. package/templates/graphql/assets/logo.svg +9 -0
  251. package/templates/graphql/docs.json +54 -0
  252. package/templates/graphql/guides/configuration.mdx +149 -0
  253. package/templates/graphql/guides/overview.mdx +96 -0
  254. package/templates/graphql/index.mdx +39 -0
  255. package/templates/graphql/package.json +14 -0
  256. package/templates/graphql/quickstart.mdx +92 -0
  257. package/templates/graphql/vercel.json +6 -0
  258. package/templates/openapi/README.md +139 -0
  259. package/templates/openapi/api-reference/openapi.json +419 -0
  260. package/templates/openapi/assets/favicon.svg +4 -0
  261. package/templates/openapi/assets/logo.svg +9 -0
  262. package/templates/openapi/docs.json +61 -0
  263. package/templates/openapi/guides/configuration.mdx +149 -0
  264. package/templates/openapi/guides/overview.mdx +96 -0
  265. package/templates/openapi/index.mdx +39 -0
  266. package/templates/openapi/package.json +14 -0
  267. package/templates/openapi/quickstart.mdx +92 -0
  268. package/templates/openapi/vercel.json +6 -0
@@ -0,0 +1,53 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { headers } from 'next/headers'
3
+ import { getProjectContent, projectExists } from '@/lib/storage/blob'
4
+
5
+ /**
6
+ * Debug endpoint to check multi-tenant routing
7
+ *
8
+ * GET /api/debug?slug=acme-api-docs-i2rjwv
9
+ */
10
+ export async function GET(request: NextRequest) {
11
+ const headersList = await headers()
12
+ const { searchParams } = new URL(request.url)
13
+ const testSlug = searchParams.get('slug')
14
+
15
+ // Collect all headers
16
+ const allHeaders: Record<string, string> = {}
17
+ headersList.forEach((value, key) => {
18
+ allHeaders[key] = value
19
+ })
20
+
21
+ // Check for project header
22
+ const projectSlug = headersList.get('x-devdoc-project')
23
+
24
+ // Test Blob storage if slug provided
25
+ let blobTest = null
26
+ if (testSlug) {
27
+ try {
28
+ const exists = await projectExists(testSlug)
29
+ const content = exists ? await getProjectContent(testSlug) : null
30
+ blobTest = {
31
+ slug: testSlug,
32
+ exists,
33
+ hasContent: !!content,
34
+ filesCount: content?.files?.length || 0,
35
+ projectName: content?.name || null,
36
+ }
37
+ } catch (error) {
38
+ blobTest = {
39
+ slug: testSlug,
40
+ error: String(error),
41
+ }
42
+ }
43
+ }
44
+
45
+ return NextResponse.json({
46
+ timestamp: new Date().toISOString(),
47
+ host: headersList.get('host'),
48
+ projectSlug,
49
+ hasBlobToken: !!process.env.BLOB_READ_WRITE_TOKEN,
50
+ blobTest,
51
+ headers: allHeaders,
52
+ })
53
+ }
@@ -0,0 +1,234 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import {
3
+ storeProjectContent,
4
+ updateProjectContent,
5
+ projectExists,
6
+ generateProjectSlug,
7
+ generateApiKey,
8
+ storeProjectApiKey,
9
+ validateApiKey,
10
+ getProjectApiKey,
11
+ type ProjectFile
12
+ } from '@/lib/storage/blob'
13
+ import { getProjectUrl, isValidSlug } from '@/lib/multi-tenant/context'
14
+
15
+ /**
16
+ * Deploy API - receives content from CLI and stores in Vercel Blob
17
+ *
18
+ * POST /api/deploy
19
+ * Headers:
20
+ * Authorization: Bearer <api_key> // Required for updates
21
+ * Body: {
22
+ * name: string, // Project name from docs.json
23
+ * slug?: string, // Optional: existing slug for updates
24
+ * docsJson: object, // The docs.json configuration
25
+ * files: Array<{ // MDX and other content files
26
+ * path: string,
27
+ * content: string
28
+ * }>
29
+ * }
30
+ */
31
+ export async function POST(request: NextRequest) {
32
+ try {
33
+ const body = await request.json()
34
+
35
+ // Validate request body
36
+ const { name, slug: existingSlug, docsJson, files } = body
37
+
38
+ if (!name || typeof name !== 'string') {
39
+ return NextResponse.json(
40
+ { error: 'Missing or invalid project name' },
41
+ { status: 400 }
42
+ )
43
+ }
44
+
45
+ if (!docsJson || typeof docsJson !== 'object') {
46
+ return NextResponse.json(
47
+ { error: 'Missing or invalid docs.json configuration' },
48
+ { status: 400 }
49
+ )
50
+ }
51
+
52
+ if (!files || !Array.isArray(files)) {
53
+ return NextResponse.json(
54
+ { error: 'Missing or invalid files array' },
55
+ { status: 400 }
56
+ )
57
+ }
58
+
59
+ // Validate files structure
60
+ const validFiles: ProjectFile[] = []
61
+ for (const file of files) {
62
+ if (!file.path || typeof file.path !== 'string') {
63
+ return NextResponse.json(
64
+ { error: `Invalid file entry: missing path` },
65
+ { status: 400 }
66
+ )
67
+ }
68
+ if (typeof file.content !== 'string') {
69
+ return NextResponse.json(
70
+ { error: `Invalid file entry for ${file.path}: content must be a string` },
71
+ { status: 400 }
72
+ )
73
+ }
74
+ validFiles.push({
75
+ path: file.path,
76
+ content: file.content,
77
+ })
78
+ }
79
+
80
+ let slug: string
81
+ let isUpdate = false
82
+ let apiKey: string | null = null
83
+
84
+ // Check if this is an update to an existing project
85
+ if (existingSlug) {
86
+ if (!isValidSlug(existingSlug)) {
87
+ return NextResponse.json(
88
+ { error: 'Invalid project slug format' },
89
+ { status: 400 }
90
+ )
91
+ }
92
+
93
+ const exists = await projectExists(existingSlug)
94
+ if (!exists) {
95
+ return NextResponse.json(
96
+ { error: `Project with slug "${existingSlug}" not found` },
97
+ { status: 404 }
98
+ )
99
+ }
100
+
101
+ // For updates, validate API key
102
+ const authHeader = request.headers.get('Authorization')
103
+ const providedKey = authHeader?.replace('Bearer ', '') || body.apiKey
104
+
105
+ if (!providedKey) {
106
+ return NextResponse.json(
107
+ { error: 'API key required for project updates. Provide via Authorization header or apiKey field.' },
108
+ { status: 401 }
109
+ )
110
+ }
111
+
112
+ const validatedSlug = await validateApiKey(providedKey)
113
+ if (validatedSlug !== existingSlug) {
114
+ return NextResponse.json(
115
+ { error: 'Invalid API key for this project' },
116
+ { status: 403 }
117
+ )
118
+ }
119
+
120
+ slug = existingSlug
121
+ isUpdate = true
122
+ } else {
123
+ // Generate a new unique slug
124
+ slug = generateProjectSlug(name)
125
+
126
+ // Ensure it doesn't already exist (very unlikely but possible)
127
+ let attempts = 0
128
+ while (await projectExists(slug) && attempts < 5) {
129
+ slug = generateProjectSlug(name)
130
+ attempts++
131
+ }
132
+
133
+ if (attempts >= 5) {
134
+ return NextResponse.json(
135
+ { error: 'Failed to generate unique project slug' },
136
+ { status: 500 }
137
+ )
138
+ }
139
+
140
+ // Generate API key for new project
141
+ apiKey = generateApiKey()
142
+ }
143
+
144
+ // Store or update content
145
+ let result
146
+ if (isUpdate) {
147
+ result = await updateProjectContent(slug, docsJson, validFiles)
148
+ } else {
149
+ result = await storeProjectContent(slug, name, docsJson, validFiles)
150
+ // Store the API key for new projects
151
+ if (apiKey) {
152
+ await storeProjectApiKey(slug, apiKey)
153
+ }
154
+ }
155
+
156
+ // Build response
157
+ const projectUrl = getProjectUrl(slug)
158
+
159
+ // For new projects, include the API key in response
160
+ const response: {
161
+ success: boolean
162
+ slug: string
163
+ url: string
164
+ blobUrl: string
165
+ isUpdate: boolean
166
+ filesCount: number
167
+ apiKey?: string
168
+ } = {
169
+ success: true,
170
+ slug,
171
+ url: projectUrl,
172
+ blobUrl: result.url,
173
+ isUpdate,
174
+ filesCount: validFiles.length,
175
+ }
176
+
177
+ if (apiKey) {
178
+ response.apiKey = apiKey
179
+ }
180
+
181
+ return NextResponse.json(response)
182
+
183
+ } catch (error) {
184
+ console.error('[Deploy API] Error:', error)
185
+ const message = error instanceof Error ? error.message : String(error)
186
+
187
+ // Provide clearer error messages for common issues
188
+ let userMessage = 'Deployment failed'
189
+ if (message.includes('blob already exists')) {
190
+ userMessage = 'Storage conflict - please try again'
191
+ } else if (message.includes('network') || message.includes('fetch')) {
192
+ userMessage = 'Network error connecting to storage'
193
+ } else if (message.includes('unauthorized') || message.includes('401')) {
194
+ userMessage = 'Authentication failed'
195
+ }
196
+
197
+ return NextResponse.json(
198
+ { error: userMessage, details: message },
199
+ { status: 500 }
200
+ )
201
+ }
202
+ }
203
+
204
+ /**
205
+ * GET /api/deploy - Check deployment status or get project info
206
+ */
207
+ export async function GET(request: NextRequest) {
208
+ const { searchParams } = new URL(request.url)
209
+ const slug = searchParams.get('slug')
210
+
211
+ if (!slug) {
212
+ return NextResponse.json(
213
+ { error: 'Missing slug parameter' },
214
+ { status: 400 }
215
+ )
216
+ }
217
+
218
+ const exists = await projectExists(slug)
219
+
220
+ if (!exists) {
221
+ return NextResponse.json(
222
+ { exists: false, slug },
223
+ { status: 404 }
224
+ )
225
+ }
226
+
227
+ const projectUrl = getProjectUrl(slug)
228
+
229
+ return NextResponse.json({
230
+ exists: true,
231
+ slug,
232
+ url: projectUrl,
233
+ })
234
+ }
@@ -0,0 +1,42 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { cookies } from 'next/headers'
3
+
4
+ const DEVICE_COOKIE_NAME = 'bf_device_id'
5
+ const COOKIE_MAX_AGE = 60 * 60 * 24 * 365 // 1 year
6
+
7
+ /**
8
+ * Generate a unique device ID
9
+ * Uses crypto.randomUUID for secure random generation
10
+ */
11
+ function generateDeviceId(): string {
12
+ return crypto.randomUUID()
13
+ }
14
+
15
+ /**
16
+ * GET /api/device
17
+ * Returns or creates a unique device ID for the client
18
+ * The device ID is stored in an httpOnly cookie for security
19
+ */
20
+ export async function GET() {
21
+ const cookieStore = await cookies()
22
+ let deviceId = cookieStore.get(DEVICE_COOKIE_NAME)?.value
23
+
24
+ // Generate new device ID if not exists
25
+ if (!deviceId) {
26
+ deviceId = generateDeviceId()
27
+ }
28
+
29
+ // Create response with device ID
30
+ const response = NextResponse.json({ deviceId })
31
+
32
+ // Set/refresh the httpOnly cookie
33
+ response.cookies.set(DEVICE_COOKIE_NAME, deviceId, {
34
+ httpOnly: true,
35
+ secure: process.env.NODE_ENV === 'production',
36
+ sameSite: 'strict',
37
+ maxAge: COOKIE_MAX_AGE,
38
+ path: '/',
39
+ })
40
+
41
+ return response
42
+ }
@@ -0,0 +1,187 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { readFileSync, existsSync } from 'fs'
3
+ import { join, isAbsolute } from 'path'
4
+ import matter from 'gray-matter'
5
+ import { serialize } from 'next-mdx-remote/serialize'
6
+ import remarkGfm from 'remark-gfm'
7
+ import rehypeSlug from 'rehype-slug'
8
+ // Note: rehype-pretty-code is dynamically imported to handle serverless environments
9
+ // where shiki may not be fully available
10
+ import { getProjectFile } from '@/lib/storage/blob'
11
+
12
+ const STARTER_PATH = process.env.STARTER_PATH || 'devdoc-docs'
13
+
14
+ // Helper to get content root - supports both relative and absolute paths
15
+ function getContentRoot(): string {
16
+ if (isAbsolute(STARTER_PATH)) {
17
+ return STARTER_PATH
18
+ }
19
+ return join(process.cwd(), STARTER_PATH)
20
+ }
21
+
22
+ // Shiki theme options for syntax highlighting
23
+ const prettyCodeOptions = {
24
+ theme: 'github-dark',
25
+ keepBackground: true,
26
+ defaultLang: 'plaintext',
27
+ }
28
+
29
+ export async function GET(request: NextRequest) {
30
+ const searchParams = request.nextUrl.searchParams
31
+ const slug = searchParams.get('slug')
32
+
33
+ if (!slug) {
34
+ return NextResponse.json(
35
+ { error: 'Missing slug parameter' },
36
+ { status: 400 }
37
+ )
38
+ }
39
+
40
+ // Check for multi-tenant mode (project slug from middleware)
41
+ const projectSlug = request.headers.get('x-devdoc-project')
42
+
43
+ // If multi-tenant, fetch from Blob Storage
44
+ if (projectSlug && !projectSlug.startsWith('custom:')) {
45
+ return handleMultiTenantDocs(projectSlug, slug)
46
+ }
47
+
48
+ try {
49
+ const starterDir = getContentRoot()
50
+
51
+ // Try .mdx then .md
52
+ let fullPath = join(starterDir, `${slug}.mdx`)
53
+ if (!existsSync(fullPath)) {
54
+ fullPath = join(starterDir, `${slug}.md`)
55
+ }
56
+
57
+ if (!existsSync(fullPath)) {
58
+ return NextResponse.json(
59
+ { error: 'Page not found' },
60
+ { status: 404 }
61
+ )
62
+ }
63
+
64
+ const fileContent = readFileSync(fullPath, 'utf-8')
65
+ const { data: frontmatter, content } = matter(fileContent)
66
+
67
+ // Serialize MDX for client-side rendering
68
+ // Note: rehype-pretty-code is dynamically imported to handle cases where shiki
69
+ // may not be available in serverless environments
70
+ let mdxSource
71
+ try {
72
+ // Try with syntax highlighting first
73
+ const rehypePrettyCodeModule = await import('rehype-pretty-code')
74
+ const rehypePrettyCode = rehypePrettyCodeModule.default
75
+
76
+ mdxSource = await serialize(content, {
77
+ mdxOptions: {
78
+ remarkPlugins: [remarkGfm],
79
+ rehypePlugins: [
80
+ rehypeSlug,
81
+ [rehypePrettyCode, prettyCodeOptions],
82
+ ],
83
+ },
84
+ parseFrontmatter: false,
85
+ })
86
+ } catch (syntaxError) {
87
+ // Fallback: serialize without syntax highlighting if shiki is unavailable
88
+ console.warn('[Docs API] Syntax highlighting unavailable, using fallback:', syntaxError)
89
+ mdxSource = await serialize(content, {
90
+ mdxOptions: {
91
+ remarkPlugins: [remarkGfm],
92
+ rehypePlugins: [rehypeSlug],
93
+ },
94
+ parseFrontmatter: false,
95
+ })
96
+ }
97
+
98
+ return NextResponse.json({
99
+ slug,
100
+ frontmatter: {
101
+ title: frontmatter.title || slug,
102
+ description: frontmatter.description,
103
+ icon: frontmatter.icon,
104
+ },
105
+ mdxSource,
106
+ rawContent: content, // Include raw content for changelog version extraction
107
+ })
108
+ } catch (error) {
109
+ console.error('[Docs API] Error loading page:', error)
110
+ return NextResponse.json(
111
+ { error: 'Failed to load page', details: error instanceof Error ? error.message : String(error) },
112
+ { status: 500 }
113
+ )
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Handle multi-tenant docs request - fetch from Blob Storage
119
+ */
120
+ async function handleMultiTenantDocs(projectSlug: string, slug: string): Promise<Response> {
121
+ try {
122
+ // Try .mdx then .md
123
+ let fileContent = await getProjectFile(projectSlug, `${slug}.mdx`)
124
+ if (!fileContent) {
125
+ fileContent = await getProjectFile(projectSlug, `${slug}.md`)
126
+ }
127
+
128
+ if (!fileContent) {
129
+ return NextResponse.json(
130
+ { error: 'Page not found' },
131
+ { status: 404 }
132
+ )
133
+ }
134
+
135
+ const { data: frontmatter, content } = matter(fileContent)
136
+
137
+ // Serialize MDX for client-side rendering
138
+ // Note: rehype-pretty-code is dynamically imported to handle cases where shiki
139
+ // may not be available in serverless environments
140
+ let mdxSource
141
+ try {
142
+ // Try with syntax highlighting first
143
+ const rehypePrettyCodeModule = await import('rehype-pretty-code')
144
+ const rehypePrettyCode = rehypePrettyCodeModule.default
145
+
146
+ mdxSource = await serialize(content, {
147
+ mdxOptions: {
148
+ remarkPlugins: [remarkGfm],
149
+ rehypePlugins: [
150
+ rehypeSlug,
151
+ [rehypePrettyCode, prettyCodeOptions],
152
+ ],
153
+ },
154
+ parseFrontmatter: false,
155
+ })
156
+ } catch (syntaxError) {
157
+ // Fallback: serialize without syntax highlighting if shiki is unavailable
158
+ console.warn('[Docs API] Multi-tenant syntax highlighting unavailable:', syntaxError)
159
+ mdxSource = await serialize(content, {
160
+ mdxOptions: {
161
+ remarkPlugins: [remarkGfm],
162
+ rehypePlugins: [rehypeSlug],
163
+ },
164
+ parseFrontmatter: false,
165
+ })
166
+ }
167
+
168
+ return NextResponse.json({
169
+ slug,
170
+ frontmatter: {
171
+ title: frontmatter.title || slug,
172
+ description: frontmatter.description,
173
+ icon: frontmatter.icon,
174
+ },
175
+ mdxSource,
176
+ rawContent: content,
177
+ isMultiTenant: true,
178
+ projectSlug,
179
+ })
180
+ } catch (error) {
181
+ console.error('[Docs API] Multi-tenant error:', error)
182
+ return NextResponse.json(
183
+ { error: 'Failed to load page', details: error instanceof Error ? error.message : String(error) },
184
+ { status: 500 }
185
+ )
186
+ }
187
+ }
@@ -0,0 +1,80 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import {
3
+ validateApiKey,
4
+ regenerateApiKey,
5
+ projectExists
6
+ } from '@/lib/storage/blob'
7
+
8
+ /**
9
+ * POST /api/keys/regenerate
10
+ * Regenerate API key for a project
11
+ *
12
+ * Headers:
13
+ * Authorization: Bearer <current_api_key>
14
+ * Body:
15
+ * { slug: string }
16
+ */
17
+ export async function POST(request: NextRequest) {
18
+ try {
19
+ const body = await request.json()
20
+ const { slug } = body
21
+
22
+ if (!slug || typeof slug !== 'string') {
23
+ return NextResponse.json(
24
+ { error: 'Missing or invalid project slug' },
25
+ { status: 400 }
26
+ )
27
+ }
28
+
29
+ // Validate current API key
30
+ const authHeader = request.headers.get('Authorization')
31
+ const currentKey = authHeader?.replace('Bearer ', '')
32
+
33
+ if (!currentKey) {
34
+ return NextResponse.json(
35
+ { error: 'API key required' },
36
+ { status: 401 }
37
+ )
38
+ }
39
+
40
+ // Check project exists
41
+ const exists = await projectExists(slug)
42
+ if (!exists) {
43
+ return NextResponse.json(
44
+ { error: 'Project not found' },
45
+ { status: 404 }
46
+ )
47
+ }
48
+
49
+ // Validate the provided key belongs to this project
50
+ const validatedSlug = await validateApiKey(currentKey)
51
+ if (validatedSlug !== slug) {
52
+ return NextResponse.json(
53
+ { error: 'Invalid API key for this project' },
54
+ { status: 403 }
55
+ )
56
+ }
57
+
58
+ // Regenerate the key
59
+ const newKey = await regenerateApiKey(slug, currentKey)
60
+
61
+ if (!newKey) {
62
+ return NextResponse.json(
63
+ { error: 'Failed to regenerate API key' },
64
+ { status: 500 }
65
+ )
66
+ }
67
+
68
+ return NextResponse.json({
69
+ success: true,
70
+ apiKey: newKey,
71
+ })
72
+
73
+ } catch (error) {
74
+ console.error('[Keys API] Error:', error)
75
+ return NextResponse.json(
76
+ { error: 'Internal server error' },
77
+ { status: 500 }
78
+ )
79
+ }
80
+ }