@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,171 @@
1
+ /**
2
+ * OpenAPI Importer
3
+ *
4
+ * Main entry point for importing OpenAPI specifications
5
+ * Ported from Hoppscotch's openapi/index.ts
6
+ */
7
+
8
+ import yaml from 'js-yaml'
9
+ import type { OpenAPI, OpenAPIV2, OpenAPIV3 } from 'openapi-types'
10
+ import type { BrainfishCollection } from '../../types'
11
+ import { objectHasProperty } from '../../utils'
12
+ import { validateOpenAPISpec } from './validator'
13
+ import { dereferenceOpenAPISpec, hasUnresolvedRefs } from './dereferencer'
14
+ import { convertOpenAPIToCollection } from './transformer'
15
+
16
+ export const OPENAPI_DEREF_ERROR = 'openapi/deref_error' as const
17
+ export const IMPORTER_INVALID_FILE_FORMAT = 'importer/invalid_file_format' as const
18
+
19
+ /**
20
+ * Safely parses JSON string
21
+ */
22
+ function safeParseJSON(str: string): unknown | null {
23
+ try {
24
+ return JSON.parse(str)
25
+ } catch {
26
+ return null
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Safely parses YAML string
32
+ */
33
+ function safeParseYAML(str: string): unknown | null {
34
+ try {
35
+ return yaml.load(str)
36
+ } catch {
37
+ return null
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Parses OpenAPI document content (JSON or YAML)
43
+ */
44
+ function parseOpenAPIDocContent(str: string): unknown | null {
45
+ const jsonResult = safeParseJSON(str)
46
+ if (jsonResult !== null) {
47
+ return jsonResult
48
+ }
49
+ return safeParseYAML(str)
50
+ }
51
+
52
+ /**
53
+ * Checks if document is OpenAPI v2
54
+ */
55
+ function isOpenAPIV2Document(doc: unknown): doc is OpenAPIV2.Document {
56
+ return (
57
+ objectHasProperty(doc, 'swagger') &&
58
+ typeof doc.swagger === 'string' &&
59
+ doc.swagger === '2.0'
60
+ )
61
+ }
62
+
63
+ /**
64
+ * Checks if document is OpenAPI v3
65
+ */
66
+ function isOpenAPIV3Document(
67
+ doc: unknown
68
+ ): doc is OpenAPIV3.Document {
69
+ return (
70
+ objectHasProperty(doc, 'openapi') &&
71
+ typeof doc.openapi === 'string' &&
72
+ doc.openapi.startsWith('3.')
73
+ )
74
+ }
75
+
76
+ /**
77
+ * Imports OpenAPI specification from file contents or document
78
+ *
79
+ * @param input - OpenAPI document object or array of file contents (strings)
80
+ * @returns Promise resolving to BrainfishCollection array
81
+ */
82
+ export async function importOpenAPISpec(
83
+ input: OpenAPI.Document | string | string[]
84
+ ): Promise<BrainfishCollection[]> {
85
+ let docArr: unknown[]
86
+
87
+ // Handle different input types
88
+ if (typeof input === 'string') {
89
+ // Single file content string
90
+ const parsed = parseOpenAPIDocContent(input)
91
+ if (!parsed) {
92
+ throw new Error(IMPORTER_INVALID_FILE_FORMAT)
93
+ }
94
+ docArr = [parsed]
95
+ } else if (Array.isArray(input)) {
96
+ // Array of file content strings
97
+ docArr = input
98
+ .map((str) => parseOpenAPIDocContent(str))
99
+ .filter((doc): doc is unknown => doc !== null)
100
+
101
+ if (docArr.length === 0) {
102
+ throw new Error(IMPORTER_INVALID_FILE_FORMAT)
103
+ }
104
+ } else {
105
+ // Already a parsed OpenAPI document
106
+ docArr = [input]
107
+ }
108
+
109
+ // Validate documents
110
+ const validatedDocs: OpenAPI.Document[] = []
111
+
112
+ for (const docObj of docArr) {
113
+ try {
114
+ // More lenient check - if it has paths, we'll try to import it
115
+ const isValidOpenAPISpec =
116
+ objectHasProperty(docObj, 'paths') &&
117
+ (isOpenAPIV2Document(docObj) ||
118
+ isOpenAPIV3Document(docObj) ||
119
+ objectHasProperty(docObj, 'info'))
120
+
121
+ if (!isValidOpenAPISpec) {
122
+ throw new Error('INVALID_OPENAPI_SPEC')
123
+ }
124
+
125
+ try {
126
+ const validatedDoc = await validateOpenAPISpec(docObj)
127
+ validatedDocs.push(validatedDoc)
128
+ } catch (validationError) {
129
+ // If validation fails but it has basic OpenAPI structure, add it anyway
130
+ if (objectHasProperty(docObj, 'paths')) {
131
+ validatedDocs.push(docObj as OpenAPI.Document)
132
+ } else {
133
+ throw validationError
134
+ }
135
+ }
136
+ } catch (err) {
137
+ if (err instanceof Error && err.message === 'INVALID_OPENAPI_SPEC') {
138
+ throw new Error('INVALID_OPENAPI_SPEC')
139
+ }
140
+ // Continue with other documents
141
+ console.warn('Failed to validate document:', err)
142
+ }
143
+ }
144
+
145
+ if (validatedDocs.length === 0) {
146
+ throw new Error(IMPORTER_INVALID_FILE_FORMAT)
147
+ }
148
+
149
+ // Dereference documents
150
+ const dereferencedDocs: OpenAPI.Document[] = []
151
+
152
+ for (const docObj of validatedDocs) {
153
+ try {
154
+ const dereferencedDoc = await dereferenceOpenAPISpec(docObj)
155
+ dereferencedDocs.push(dereferencedDoc)
156
+ } catch {
157
+ // Check if the document has unresolved references
158
+ if (hasUnresolvedRefs(docObj)) {
159
+ console.warn(
160
+ 'Document contains unresolved references which may affect import quality'
161
+ )
162
+ }
163
+
164
+ // If dereferencing fails, use the original document
165
+ dereferencedDocs.push(docObj)
166
+ }
167
+ }
168
+
169
+ // Convert to BrainfishCollections
170
+ return convertOpenAPIToCollection(dereferencedDocs)
171
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * OpenAPI Transformer
3
+ *
4
+ * Ported from Hoppscotch's openapi/index.ts
5
+ * Converts OpenAPI Document to BrainfishCollection
6
+ */
7
+
8
+ /* eslint-disable @typescript-eslint/no-explicit-any */
9
+
10
+ import type {
11
+ OpenAPI,
12
+ OpenAPIV2,
13
+ OpenAPIV3,
14
+ } from 'openapi-types'
15
+ import type {
16
+ BrainfishCollection,
17
+ BrainfishRESTRequest,
18
+ BrainfishRESTResponseOriginalRequest,
19
+ } from '../../types'
20
+ import {
21
+ makeBrainfishCollection,
22
+ makeBrainfishRESTRequest,
23
+ makeBrainfishRESTResponseOriginalRequest,
24
+ } from '../../factories'
25
+ import { objectHasProperty, cloneDeep } from '../../utils'
26
+ import { hasUnresolvedRefs } from './dereferencer'
27
+ import {
28
+ replaceOpenApiPathTemplating,
29
+ parseOpenAPIParams,
30
+ parseOpenAPIHeaders,
31
+ parseOpenAPIVariables,
32
+ parseOpenAPIResponses,
33
+ type OpenAPIPathInfoType,
34
+ type OpenAPIOperationType,
35
+ } from './extractors'
36
+ import { parseOpenAPIBody } from './extractors/body'
37
+ import { parseOpenAPIAuth, extractSecurityHeaders, getDefaultAuth } from './extractors/auth'
38
+
39
+ /**
40
+ * Formats an operation name for display
41
+ * Converts snake_case/kebab-case operationIds to readable Title Case
42
+ * Prefers summary over operationId if available
43
+ */
44
+ function formatOperationName(info: any): string {
45
+ const summary = info.summary?.trim()
46
+ const operationId = info.operationId?.trim()
47
+
48
+ // Prefer summary as it's usually more human-readable
49
+ if (summary) {
50
+ return summary
51
+ }
52
+
53
+ // If only operationId is available, convert to readable format
54
+ if (operationId) {
55
+ return operationId
56
+ // Replace underscores and hyphens with spaces
57
+ .replace(/[_-]/g, ' ')
58
+ // Handle camelCase by adding space before capital letters
59
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
60
+ // Capitalize first letter of each word
61
+ .replace(/\b\w/g, (char: string) => char.toUpperCase())
62
+ // Clean up multiple spaces
63
+ .replace(/\s+/g, ' ')
64
+ .trim()
65
+ }
66
+
67
+ return 'Untitled Request'
68
+ }
69
+
70
+ /**
71
+ * Parses OpenAPI URL from document (v2 or v3)
72
+ */
73
+ function parseOpenAPIUrl(
74
+ doc: OpenAPI.Document | OpenAPIV2.Document | OpenAPIV3.Document
75
+ ): string {
76
+ // OpenAPI V2 has host and basePath in document properties
77
+ if (objectHasProperty(doc, 'swagger')) {
78
+ const host = (doc as OpenAPIV2.Document).host?.trim() || '<<baseUrl>>'
79
+ const basePath = (doc as OpenAPIV2.Document).basePath?.trim() || ''
80
+ return `${host}${basePath}`
81
+ }
82
+
83
+ // OpenAPI V3 has servers array
84
+ if (objectHasProperty(doc, 'servers')) {
85
+ const serverUrl = (doc as OpenAPIV3.Document).servers?.[0]?.url
86
+ return !serverUrl || serverUrl === './' ? '<<baseUrl>>' : serverUrl
87
+ }
88
+
89
+ // Fallback
90
+ return '<<baseUrl>>'
91
+ }
92
+
93
+ /**
94
+ * Converts an OpenAPI path to Brainfish requests
95
+ */
96
+ function convertPathToBrainfishReqs(
97
+ doc: OpenAPI.Document,
98
+ pathName: string,
99
+ pathObj: OpenAPIPathInfoType
100
+ ): Array<{
101
+ request: BrainfishRESTRequest
102
+ metadata: {
103
+ tags: string[]
104
+ }
105
+ }> {
106
+ const methods = ['get', 'head', 'post', 'put', 'delete', 'options', 'patch'] as const
107
+
108
+ return methods
109
+ .filter((method) => !!pathObj[method])
110
+ .map((method) => {
111
+ const info = pathObj[method]! as OpenAPIOperationType
112
+ const openAPIUrl = parseOpenAPIUrl(doc)
113
+ const openAPIPath = replaceOpenApiPathTemplating(pathName)
114
+
115
+ const endpoint =
116
+ openAPIUrl.endsWith('/') && openAPIPath.startsWith('/')
117
+ ? openAPIUrl + openAPIPath.slice(1)
118
+ : openAPIUrl + openAPIPath
119
+
120
+ const params = parseOpenAPIParams(
121
+ (info.parameters as any) ?? []
122
+ )
123
+ const baseHeaders = parseOpenAPIHeaders(
124
+ (info.parameters as any) ?? []
125
+ )
126
+
127
+ // Get the primary auth key (handled by global auth) to exclude from additional headers
128
+ const defaultAuth = getDefaultAuth(doc)
129
+ const primaryAuthKey = defaultAuth?.authType === 'api-key' ? defaultAuth.key : undefined
130
+
131
+ // Extract additional security headers (e.g., agent-key when access-token is primary)
132
+ const securityHeaders = extractSecurityHeaders(doc, info, primaryAuthKey)
133
+
134
+ // Merge headers: base headers + security headers (avoiding duplicates)
135
+ const existingKeys = new Set(baseHeaders.map(h => h.key.toLowerCase()))
136
+ const additionalSecurityHeaders = securityHeaders
137
+ .filter(h => !existingKeys.has(h.key.toLowerCase()))
138
+ .map(h => ({
139
+ key: h.key,
140
+ value: h.value,
141
+ active: h.active,
142
+ description: h.description,
143
+ }))
144
+
145
+ const headers = [...baseHeaders, ...additionalSecurityHeaders]
146
+
147
+ const requestVariables = parseOpenAPIVariables(
148
+ (info.parameters as any) ?? []
149
+ )
150
+ const auth = parseOpenAPIAuth(doc, info)
151
+ const body = parseOpenAPIBody(doc, info)
152
+
153
+ const displayName = formatOperationName(info)
154
+
155
+ const originalRequest: BrainfishRESTResponseOriginalRequest =
156
+ makeBrainfishRESTResponseOriginalRequest({
157
+ name: displayName,
158
+ method: method.toUpperCase() as any,
159
+ endpoint,
160
+ params,
161
+ headers,
162
+ body,
163
+ auth,
164
+ requestVariables,
165
+ })
166
+
167
+ const responses = parseOpenAPIResponses(doc, info, originalRequest)
168
+
169
+ const request = makeBrainfishRESTRequest({
170
+ name: displayName,
171
+ description: (info as any).description ?? null,
172
+ method: method.toUpperCase() as any,
173
+ endpoint,
174
+ params,
175
+ headers,
176
+ auth,
177
+ body,
178
+ requestVariables,
179
+ responses,
180
+ tags: (info as any).tags ?? [],
181
+ })
182
+
183
+ return {
184
+ request,
185
+ metadata: {
186
+ tags: (info as any).tags ?? [],
187
+ },
188
+ }
189
+ })
190
+ }
191
+
192
+ /**
193
+ * Converts OpenAPI documents to BrainfishCollections
194
+ */
195
+ export function convertOpenAPIToCollection(
196
+ docs: OpenAPI.Document[]
197
+ ): BrainfishCollection[] {
198
+ // Check for unresolved references before conversion
199
+ for (const doc of docs) {
200
+ if (hasUnresolvedRefs(doc)) {
201
+ console.warn(
202
+ 'Document contains unresolved references which may affect import quality'
203
+ )
204
+ // Continue anyway to provide a best-effort import
205
+ }
206
+ }
207
+
208
+ const collections = docs.map((doc) => {
209
+ const name = doc.info.title
210
+ const description = doc.info.description ?? null
211
+
212
+ // Extract tag descriptions from OpenAPI spec
213
+ const tagDescriptions: Record<string, string> = {}
214
+ if ('tags' in doc && Array.isArray(doc.tags)) {
215
+ doc.tags.forEach((tag: any) => {
216
+ if (tag.name && tag.description) {
217
+ tagDescriptions[tag.name] = tag.description
218
+ }
219
+ })
220
+ }
221
+
222
+ // Convert all paths to requests
223
+ const paths = Object.entries(doc.paths ?? {})
224
+ .map(([pathName, pathObj]) =>
225
+ convertPathToBrainfishReqs(doc, pathName, pathObj as OpenAPIPathInfoType)
226
+ )
227
+ .flat()
228
+
229
+ // Organize requests by tags
230
+ const requestsByTags: Record<string, BrainfishRESTRequest[]> = {}
231
+ const requestsWithoutTags: BrainfishRESTRequest[] = []
232
+
233
+ paths.forEach(({ metadata, request }) => {
234
+ const tags = metadata.tags
235
+
236
+ if (tags.length === 0) {
237
+ requestsWithoutTags.push(request)
238
+ return
239
+ }
240
+
241
+ for (const tag of tags) {
242
+ if (!requestsByTags[tag]) {
243
+ requestsByTags[tag] = []
244
+ }
245
+
246
+ // Clone request for each tag (request can belong to multiple tags)
247
+ requestsByTags[tag].push(cloneDeep(request))
248
+ }
249
+ })
250
+
251
+ // Create folders (collections) for each tag
252
+ const folders = Object.entries(requestsByTags).map(([tagName, requests]) =>
253
+ makeBrainfishCollection({
254
+ name: tagName,
255
+ description: tagDescriptions[tagName] ?? null,
256
+ requests,
257
+ folders: [],
258
+ variables: [],
259
+ auth: { authType: 'inherit', authActive: true },
260
+ headers: [],
261
+ })
262
+ )
263
+
264
+ // Create main collection
265
+ return makeBrainfishCollection({
266
+ name,
267
+ description,
268
+ folders,
269
+ requests: requestsWithoutTags,
270
+ variables: [],
271
+ auth: { authType: 'inherit', authActive: true },
272
+ headers: [],
273
+ })
274
+ })
275
+
276
+ return collections
277
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * OpenAPI Validator
3
+ *
4
+ * Ported from Hoppscotch's openapi-import-worker.ts
5
+ * Converts Web Worker pattern to direct function calls for Next.js
6
+ */
7
+
8
+ import SwaggerParser from '@apidevtools/swagger-parser'
9
+ import type { OpenAPI } from 'openapi-types'
10
+
11
+ /**
12
+ * Validates an OpenAPI document using SwaggerParser
13
+ *
14
+ * @param docs - The OpenAPI document to validate
15
+ * @returns Promise resolving to validated OpenAPI document
16
+ * @throws Error if validation fails
17
+ */
18
+ export async function validateOpenAPISpec(
19
+ docs: unknown
20
+ ): Promise<OpenAPI.Document> {
21
+ try {
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ const validated = await SwaggerParser.validate(docs as any, {
24
+ // @ts-expect-error - this is a valid option, but types may not be updated
25
+ continueOnError: true,
26
+ })
27
+ return validated as unknown as OpenAPI.Document
28
+ } catch {
29
+ throw new Error('COULD_NOT_VALIDATE')
30
+ }
31
+ }
@@ -0,0 +1,107 @@
1
+ 'use client'
2
+
3
+ import { createContext, useContext, useState, useCallback, type ReactNode } from 'react'
4
+ import type { PrefillData } from '@/lib/api-docs/agent/types'
5
+
6
+ /** Current request payload from the playground */
7
+ export interface CurrentRequestPayload {
8
+ method: string
9
+ endpoint: string
10
+ params: Array<{ key: string; value: string; active: boolean }>
11
+ headers: Array<{ key: string; value: string; active: boolean }>
12
+ body: string | null
13
+ bodyContentType: string | null
14
+ }
15
+
16
+ interface PlaygroundContextValue {
17
+ /** Pending prefill data to be applied */
18
+ pendingPrefill: PrefillData | null
19
+ /** Set prefill data - will be consumed by playground */
20
+ setPrefill: (data: PrefillData) => void
21
+ /** Clear pending prefill after it's been applied */
22
+ clearPrefill: () => void
23
+ /** Current request payload from the playground */
24
+ currentRequestPayload: CurrentRequestPayload | null
25
+ /** Set current request payload - called by playground when values change */
26
+ setCurrentRequestPayload: (payload: CurrentRequestPayload | null) => void
27
+ /** Trigger to send request from agent */
28
+ triggerSend: boolean
29
+ /** Call to trigger send from agent */
30
+ requestSend: () => void
31
+ /** Clear send trigger after playground handles it */
32
+ clearSendTrigger: () => void
33
+ }
34
+
35
+ const PlaygroundContext = createContext<PlaygroundContextValue | null>(null)
36
+
37
+ export function PlaygroundProvider({ children }: { children: ReactNode }) {
38
+ const [pendingPrefill, setPendingPrefill] = useState<PrefillData | null>(null)
39
+ const [currentRequestPayload, setCurrentRequestPayloadState] = useState<CurrentRequestPayload | null>(null)
40
+ const [triggerSend, setTriggerSend] = useState(false)
41
+
42
+ const setPrefill = useCallback((data: PrefillData) => {
43
+ setPendingPrefill(data)
44
+ }, [])
45
+
46
+ const clearPrefill = useCallback(() => {
47
+ setPendingPrefill(null)
48
+ }, [])
49
+
50
+ const setCurrentRequestPayload = useCallback((payload: CurrentRequestPayload | null) => {
51
+ setCurrentRequestPayloadState(payload)
52
+ }, [])
53
+
54
+ const requestSend = useCallback(() => {
55
+ setTriggerSend(true)
56
+ }, [])
57
+
58
+ const clearSendTrigger = useCallback(() => {
59
+ setTriggerSend(false)
60
+ }, [])
61
+
62
+ return (
63
+ <PlaygroundContext.Provider value={{
64
+ pendingPrefill,
65
+ setPrefill,
66
+ clearPrefill,
67
+ currentRequestPayload,
68
+ setCurrentRequestPayload,
69
+ triggerSend,
70
+ requestSend,
71
+ clearSendTrigger,
72
+ }}>
73
+ {children}
74
+ </PlaygroundContext.Provider>
75
+ )
76
+ }
77
+
78
+ export function usePlaygroundPrefill() {
79
+ const context = useContext(PlaygroundContext)
80
+ if (!context) {
81
+ throw new Error('usePlaygroundPrefill must be used within a PlaygroundProvider')
82
+ }
83
+ return context
84
+ }
85
+
86
+ export function useCurrentRequestPayload() {
87
+ const context = useContext(PlaygroundContext)
88
+ if (!context) {
89
+ throw new Error('useCurrentRequestPayload must be used within a PlaygroundProvider')
90
+ }
91
+ return {
92
+ currentRequestPayload: context.currentRequestPayload,
93
+ setCurrentRequestPayload: context.setCurrentRequestPayload,
94
+ }
95
+ }
96
+
97
+ export function useSendTrigger() {
98
+ const context = useContext(PlaygroundContext)
99
+ if (!context) {
100
+ throw new Error('useSendTrigger must be used within a PlaygroundProvider')
101
+ }
102
+ return {
103
+ triggerSend: context.triggerSend,
104
+ requestSend: context.requestSend,
105
+ clearSendTrigger: context.clearSendTrigger,
106
+ }
107
+ }
@@ -0,0 +1,124 @@
1
+ 'use client'
2
+
3
+ import { createContext, useContext, useState, useCallback, ReactNode } from 'react'
4
+
5
+ // Tabs that can be navigated to
6
+ export type PlaygroundTab = 'params' | 'body' | 'headers' | 'auth'
7
+
8
+ // Fields that can be highlighted
9
+ export type HighlightField =
10
+ | { type: 'param'; key?: string }
11
+ | { type: 'header'; key?: string }
12
+ | { type: 'body' }
13
+ | { type: 'auth'; field?: 'apiKey' | 'bearerToken' | 'basicUsername' | 'basicPassword' }
14
+
15
+ interface PlaygroundNavigationState {
16
+ /** Currently active tab (controlled externally) */
17
+ activeTab: PlaygroundTab | null
18
+ /** Field to highlight */
19
+ highlightField: HighlightField | null
20
+ /** Whether to show error styling on highlighted field */
21
+ showError: boolean
22
+ }
23
+
24
+ interface PlaygroundNavigationContextType {
25
+ state: PlaygroundNavigationState
26
+ /** Navigate to a specific tab */
27
+ navigateToTab: (tab: PlaygroundTab) => void
28
+ /** Navigate to tab and highlight a field */
29
+ navigateAndHighlight: (tab: PlaygroundTab, field?: HighlightField, showError?: boolean) => void
30
+ /** Clear highlight */
31
+ clearHighlight: () => void
32
+ /** Set active tab (called by RequestTabs) */
33
+ setActiveTab: (tab: PlaygroundTab) => void
34
+ /** Reset navigation state (called when endpoint changes) */
35
+ resetNavigation: () => void
36
+ }
37
+
38
+ const PlaygroundNavigationContext = createContext<PlaygroundNavigationContextType | undefined>(undefined)
39
+
40
+ export function PlaygroundNavigationProvider({ children }: { children: ReactNode }) {
41
+ const [state, setState] = useState<PlaygroundNavigationState>({
42
+ activeTab: null,
43
+ highlightField: null,
44
+ showError: false,
45
+ })
46
+
47
+ const navigateToTab = useCallback((tab: PlaygroundTab) => {
48
+ setState(prev => ({
49
+ ...prev,
50
+ activeTab: tab,
51
+ highlightField: null,
52
+ showError: false,
53
+ }))
54
+ }, [])
55
+
56
+ const navigateAndHighlight = useCallback((
57
+ tab: PlaygroundTab,
58
+ field?: HighlightField,
59
+ showError: boolean = true
60
+ ) => {
61
+ setState({
62
+ activeTab: tab,
63
+ highlightField: field || null,
64
+ showError,
65
+ })
66
+
67
+ // Auto-clear highlight after a delay
68
+ if (field) {
69
+ setTimeout(() => {
70
+ setState(prev => ({
71
+ ...prev,
72
+ highlightField: null,
73
+ showError: false,
74
+ }))
75
+ }, 3000)
76
+ }
77
+ }, [])
78
+
79
+ const clearHighlight = useCallback(() => {
80
+ setState(prev => ({
81
+ ...prev,
82
+ highlightField: null,
83
+ showError: false,
84
+ }))
85
+ }, [])
86
+
87
+ const setActiveTab = useCallback((tab: PlaygroundTab) => {
88
+ setState(prev => ({
89
+ ...prev,
90
+ activeTab: tab,
91
+ }))
92
+ }, [])
93
+
94
+ const resetNavigation = useCallback(() => {
95
+ setState({
96
+ activeTab: null,
97
+ highlightField: null,
98
+ showError: false,
99
+ })
100
+ }, [])
101
+
102
+ return (
103
+ <PlaygroundNavigationContext.Provider
104
+ value={{
105
+ state,
106
+ navigateToTab,
107
+ navigateAndHighlight,
108
+ clearHighlight,
109
+ setActiveTab,
110
+ resetNavigation,
111
+ }}
112
+ >
113
+ {children}
114
+ </PlaygroundNavigationContext.Provider>
115
+ )
116
+ }
117
+
118
+ export function usePlaygroundNavigation() {
119
+ const context = useContext(PlaygroundNavigationContext)
120
+ if (context === undefined) {
121
+ throw new Error('usePlaygroundNavigation must be used within a PlaygroundNavigationProvider')
122
+ }
123
+ return context
124
+ }