github-to-mcp-monorepo 1.0.0

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 (388) hide show
  1. package/.env.example +8 -0
  2. package/.github/CODEOWNERS +6 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.nvmrc +1 -0
  5. package/.prettierignore +5 -0
  6. package/.prettierrc +7 -0
  7. package/.vscode/settings.json +4 -0
  8. package/ARCHITECTURE.md +1429 -0
  9. package/CHANGELOG.md +167 -0
  10. package/CONTRIBUTING.md +327 -0
  11. package/LICENSE +201 -0
  12. package/README.md +1028 -0
  13. package/SECURITY.md +248 -0
  14. package/VISUAL_GUIDE.md +437 -0
  15. package/apps/vscode/IMPLEMENTATION.md +480 -0
  16. package/apps/vscode/README.md +248 -0
  17. package/apps/vscode/package.json +381 -0
  18. package/apps/vscode/resources/icon.png +0 -0
  19. package/apps/vscode/resources/icon.svg +5 -0
  20. package/apps/vscode/src/commands/browseRegistry.ts +211 -0
  21. package/apps/vscode/src/commands/configureClaudeDesktop.ts +332 -0
  22. package/apps/vscode/src/commands/convert.ts +82 -0
  23. package/apps/vscode/src/commands/convertCurrentRepo.ts +109 -0
  24. package/apps/vscode/src/commands/convertFromUrl.ts +138 -0
  25. package/apps/vscode/src/commands/index.ts +121 -0
  26. package/apps/vscode/src/commands/validate.ts +197 -0
  27. package/apps/vscode/src/extension.ts +464 -0
  28. package/apps/vscode/src/global.d.ts +36 -0
  29. package/apps/vscode/src/test/extension.test.ts +73 -0
  30. package/apps/vscode/src/utils/file-generator.ts +529 -0
  31. package/apps/vscode/src/utils/github-api.ts +335 -0
  32. package/apps/vscode/src/utils/index.ts +29 -0
  33. package/apps/vscode/src/utils/mcp-config.ts +334 -0
  34. package/apps/vscode/src/utils/storage.ts +87 -0
  35. package/apps/vscode/src/views/McpServersTreeView.ts +160 -0
  36. package/apps/vscode/src/views/OutputChannelView.ts +195 -0
  37. package/apps/vscode/src/views/StatusBarItem.ts +251 -0
  38. package/apps/vscode/src/views/ToolsExplorerView.ts +314 -0
  39. package/apps/vscode/src/views/historyProvider.ts +75 -0
  40. package/apps/vscode/src/views/index.ts +12 -0
  41. package/apps/vscode/src/views/resultsPanel.ts +330 -0
  42. package/apps/vscode/src/webviews/ConversionPanel.ts +350 -0
  43. package/apps/vscode/src/webviews/ToolDetailsPanel.ts +448 -0
  44. package/apps/vscode/src/webviews/index.ts +9 -0
  45. package/apps/vscode/src/webviews/webview-ui/styles.ts +492 -0
  46. package/apps/vscode/tsconfig.json +20 -0
  47. package/apps/web/PLAYGROUND_GUIDE.md +499 -0
  48. package/apps/web/README.md +505 -0
  49. package/apps/web/app/api/convert/route.ts +100 -0
  50. package/apps/web/app/api/convert/stream/route.ts +198 -0
  51. package/apps/web/app/api/deploy/route.ts +157 -0
  52. package/apps/web/app/api/edge/route.ts +308 -0
  53. package/apps/web/app/api/export-docker/route.ts +284 -0
  54. package/apps/web/app/api/generate-openapi/route.ts +119 -0
  55. package/apps/web/app/api/mcp/[serverId]/route.ts +263 -0
  56. package/apps/web/app/api/playground/connect/route.ts +143 -0
  57. package/apps/web/app/api/playground/disconnect/route.ts +78 -0
  58. package/apps/web/app/api/playground/execute/route.ts +135 -0
  59. package/apps/web/app/api/playground/sessions/route.ts +103 -0
  60. package/apps/web/app/api/playground/tools/route.ts +117 -0
  61. package/apps/web/app/api/playground/v2/connect/route.ts +96 -0
  62. package/apps/web/app/api/playground/v2/disconnect/route.ts +88 -0
  63. package/apps/web/app/api/playground/v2/health/route.ts +80 -0
  64. package/apps/web/app/api/playground/v2/prompts/route.ts +160 -0
  65. package/apps/web/app/api/playground/v2/resources/route.ts +159 -0
  66. package/apps/web/app/api/playground/v2/sessions/route.ts +184 -0
  67. package/apps/web/app/api/playground/v2/tools/route.ts +167 -0
  68. package/apps/web/app/api/stream/route.ts +232 -0
  69. package/apps/web/app/batch/BatchConvertClient.tsx +190 -0
  70. package/apps/web/app/batch/page.tsx +37 -0
  71. package/apps/web/app/convert/page.tsx +269 -0
  72. package/apps/web/app/dashboard/page.tsx +380 -0
  73. package/apps/web/app/globals.css +622 -0
  74. package/apps/web/app/layout.tsx +120 -0
  75. package/apps/web/app/manifest.ts +31 -0
  76. package/apps/web/app/opengraph-image.tsx +112 -0
  77. package/apps/web/app/page.old.tsx +924 -0
  78. package/apps/web/app/page.tsx +77 -0
  79. package/apps/web/app/playground/page.tsx +306 -0
  80. package/apps/web/app/playground/v2/error.tsx +163 -0
  81. package/apps/web/app/playground/v2/layout.tsx +58 -0
  82. package/apps/web/app/playground/v2/loading.tsx +152 -0
  83. package/apps/web/app/playground/v2/page.tsx +644 -0
  84. package/apps/web/app/playground/v2/providers.tsx +214 -0
  85. package/apps/web/app/playground/v2/use-shortcuts.ts +209 -0
  86. package/apps/web/app/playground/v2/use-url-state.ts +296 -0
  87. package/apps/web/app/providers.tsx +22 -0
  88. package/apps/web/app/sitemap.ts +32 -0
  89. package/apps/web/app/twitter-image.tsx +112 -0
  90. package/apps/web/components/BranchSelector.tsx +401 -0
  91. package/apps/web/components/ClaudeConfigExport.tsx +226 -0
  92. package/apps/web/components/Features.tsx +84 -0
  93. package/apps/web/components/Footer.tsx +119 -0
  94. package/apps/web/components/GenerationProgress.tsx +248 -0
  95. package/apps/web/components/GithubUrlInput.tsx +483 -0
  96. package/apps/web/components/Header.tsx +175 -0
  97. package/apps/web/components/Hero.tsx +117 -0
  98. package/apps/web/components/HowItWorks.tsx +119 -0
  99. package/apps/web/components/InstallBanner.tsx +158 -0
  100. package/apps/web/components/Logo.tsx +116 -0
  101. package/apps/web/components/ParticleBackground.tsx +105 -0
  102. package/apps/web/components/Playground.tsx +472 -0
  103. package/apps/web/components/PlaygroundToolTester.tsx +410 -0
  104. package/apps/web/components/ProductCards.tsx +179 -0
  105. package/apps/web/components/SplitView.tsx +194 -0
  106. package/apps/web/components/ToolFilter.tsx +260 -0
  107. package/apps/web/components/ToolList.tsx +325 -0
  108. package/apps/web/components/batch/BatchConvert.tsx +785 -0
  109. package/apps/web/components/batch/index.ts +7 -0
  110. package/apps/web/components/convert/ConfigTabs.tsx +230 -0
  111. package/apps/web/components/convert/ConversionResult.tsx +482 -0
  112. package/apps/web/components/convert/InlinePlayground.tsx +259 -0
  113. package/apps/web/components/convert/LoadingSteps.tsx +311 -0
  114. package/apps/web/components/convert/OneClickInstall.tsx +224 -0
  115. package/apps/web/components/convert/ToolCard.tsx +189 -0
  116. package/apps/web/components/convert/TryInPlayground.tsx +242 -0
  117. package/apps/web/components/convert/index.ts +12 -0
  118. package/apps/web/components/deploy/DeployButton.tsx +369 -0
  119. package/apps/web/components/deploy/index.ts +7 -0
  120. package/apps/web/components/docker/DockerExport.tsx +690 -0
  121. package/apps/web/components/docker/index.ts +7 -0
  122. package/apps/web/components/install/OneClickInstall.tsx +676 -0
  123. package/apps/web/components/install/index.ts +7 -0
  124. package/apps/web/components/playground/CapabilityTabs.tsx +150 -0
  125. package/apps/web/components/playground/ConnectionStatusV2.tsx +322 -0
  126. package/apps/web/components/playground/EmptyStates.tsx +305 -0
  127. package/apps/web/components/playground/ExecutionLog.tsx +260 -0
  128. package/apps/web/components/playground/ExecutionLogV2.tsx +378 -0
  129. package/apps/web/components/playground/JsonViewer.tsx +388 -0
  130. package/apps/web/components/playground/PlaygroundLayout.tsx +244 -0
  131. package/apps/web/components/playground/PromptsPanel.tsx +385 -0
  132. package/apps/web/components/playground/ResourcesPanel.tsx +378 -0
  133. package/apps/web/components/playground/SchemaForm.tsx +477 -0
  134. package/apps/web/components/playground/ServerStatus.tsx +151 -0
  135. package/apps/web/components/playground/ShareButton.tsx +239 -0
  136. package/apps/web/components/playground/ToolsPanel.tsx +309 -0
  137. package/apps/web/components/playground/TransportConfigurator.tsx +563 -0
  138. package/apps/web/components/playground/index.ts +74 -0
  139. package/apps/web/components/playground/types.ts +202 -0
  140. package/apps/web/components/streaming/StreamingProgress.tsx +441 -0
  141. package/apps/web/components/streaming/index.ts +7 -0
  142. package/apps/web/components/ui/badge.tsx +42 -0
  143. package/apps/web/components/ui/button.tsx +88 -0
  144. package/apps/web/components/ui/card.tsx +75 -0
  145. package/apps/web/components/ui/code-block.tsx +122 -0
  146. package/apps/web/components/ui/index.ts +12 -0
  147. package/apps/web/components/ui/input.tsx +55 -0
  148. package/apps/web/components/ui/tabs.tsx +61 -0
  149. package/apps/web/hooks/index.ts +85 -0
  150. package/apps/web/hooks/types.ts +1173 -0
  151. package/apps/web/hooks/use-conversion.ts +133 -0
  152. package/apps/web/hooks/use-execution-history.ts +376 -0
  153. package/apps/web/hooks/use-generation-progress.ts +147 -0
  154. package/apps/web/hooks/use-local-storage.ts +88 -0
  155. package/apps/web/hooks/use-mcp-client.ts +623 -0
  156. package/apps/web/hooks/use-mcp-connection.ts +500 -0
  157. package/apps/web/hooks/use-mcp-execution.ts +282 -0
  158. package/apps/web/hooks/use-mcp-prompts.ts +441 -0
  159. package/apps/web/hooks/use-mcp-resources.ts +430 -0
  160. package/apps/web/hooks/use-mcp-tools.ts +540 -0
  161. package/apps/web/hooks/use-playground-store.ts +299 -0
  162. package/apps/web/hooks/use-playground.ts +184 -0
  163. package/apps/web/hooks/use-streaming-conversion.ts +227 -0
  164. package/apps/web/hooks/useBatchConversion.ts +271 -0
  165. package/apps/web/hooks/useDockerConfig.ts +161 -0
  166. package/apps/web/hooks/usePlatformDetection.ts +80 -0
  167. package/apps/web/hooks/useStreaming.ts +199 -0
  168. package/apps/web/lib/api/errors.ts +386 -0
  169. package/apps/web/lib/api/index.ts +137 -0
  170. package/apps/web/lib/api/logger.ts +187 -0
  171. package/apps/web/lib/api/middleware.ts +364 -0
  172. package/apps/web/lib/api/openapi.ts +977 -0
  173. package/apps/web/lib/api/session-manager.ts +594 -0
  174. package/apps/web/lib/api/types.ts +433 -0
  175. package/apps/web/lib/api/validation.ts +523 -0
  176. package/apps/web/lib/constants.ts +114 -0
  177. package/apps/web/lib/mcp/client.ts +1137 -0
  178. package/apps/web/lib/mcp/events.ts +651 -0
  179. package/apps/web/lib/mcp/index.ts +347 -0
  180. package/apps/web/lib/mcp/logger.ts +428 -0
  181. package/apps/web/lib/mcp/metrics.ts +703 -0
  182. package/apps/web/lib/mcp/retry.ts +616 -0
  183. package/apps/web/lib/mcp/session-manager.ts +779 -0
  184. package/apps/web/lib/mcp/transports.ts +988 -0
  185. package/apps/web/lib/mcp/types.ts +594 -0
  186. package/apps/web/lib/mcp-client-enhanced.ts +871 -0
  187. package/apps/web/lib/mcp-client.ts +778 -0
  188. package/apps/web/lib/mcp-errors.ts +489 -0
  189. package/apps/web/lib/mcp-sandbox.ts +593 -0
  190. package/apps/web/lib/mcp-testing.ts +428 -0
  191. package/apps/web/lib/mcp-types.ts +448 -0
  192. package/apps/web/lib/playground-store.tsx +1147 -0
  193. package/apps/web/lib/utils.ts +439 -0
  194. package/apps/web/next-env.d.ts +5 -0
  195. package/apps/web/next.config.js +23 -0
  196. package/apps/web/package.json +55 -0
  197. package/apps/web/postcss.config.js +6 -0
  198. package/apps/web/public/.well-known/ai-plugin.json +17 -0
  199. package/apps/web/public/logo.svg +6 -0
  200. package/apps/web/public/robots.txt +22 -0
  201. package/apps/web/public/schema.json +27 -0
  202. package/apps/web/tailwind.config.js +26 -0
  203. package/apps/web/tailwind.config.ts +123 -0
  204. package/apps/web/tsconfig.json +20 -0
  205. package/apps/web/types/deploy.ts +139 -0
  206. package/apps/web/types/index.ts +247 -0
  207. package/apps/web/vercel.json +39 -0
  208. package/eslint.config.mjs +23 -0
  209. package/llms.txt +102 -0
  210. package/mkdocs/docs/api/core.md +318 -0
  211. package/mkdocs/docs/api/index.md +128 -0
  212. package/mkdocs/docs/api/mcp-server.md +301 -0
  213. package/mkdocs/docs/api/openapi-parser.md +254 -0
  214. package/mkdocs/docs/assets/logo.svg +7 -0
  215. package/mkdocs/docs/changelog.md +118 -0
  216. package/mkdocs/docs/cli/generate.md +148 -0
  217. package/mkdocs/docs/cli/index.md +52 -0
  218. package/mkdocs/docs/cli/inspect.md +164 -0
  219. package/mkdocs/docs/cli/serve.md +136 -0
  220. package/mkdocs/docs/concepts/classification.md +254 -0
  221. package/mkdocs/docs/concepts/how-it-works.md +299 -0
  222. package/mkdocs/docs/concepts/index.md +77 -0
  223. package/mkdocs/docs/concepts/mcp-protocol.md +362 -0
  224. package/mkdocs/docs/concepts/tool-types.md +382 -0
  225. package/mkdocs/docs/contributing/architecture.md +262 -0
  226. package/mkdocs/docs/contributing/development.md +245 -0
  227. package/mkdocs/docs/contributing/index.md +73 -0
  228. package/mkdocs/docs/contributing/testing.md +320 -0
  229. package/mkdocs/docs/getting-started/configuration.md +235 -0
  230. package/mkdocs/docs/getting-started/index.md +54 -0
  231. package/mkdocs/docs/getting-started/installation.md +145 -0
  232. package/mkdocs/docs/getting-started/quickstart.md +160 -0
  233. package/mkdocs/docs/guides/batch.md +375 -0
  234. package/mkdocs/docs/guides/claude-desktop.md +227 -0
  235. package/mkdocs/docs/guides/cursor.md +188 -0
  236. package/mkdocs/docs/guides/custom-tools.md +367 -0
  237. package/mkdocs/docs/guides/index.md +78 -0
  238. package/mkdocs/docs/guides/private-repos.md +221 -0
  239. package/mkdocs/docs/guides/vscode.md +247 -0
  240. package/mkdocs/docs/index.md +175 -0
  241. package/mkdocs/docs/reference/config.md +223 -0
  242. package/mkdocs/docs/reference/env.md +192 -0
  243. package/mkdocs/docs/reference/index.md +102 -0
  244. package/mkdocs/docs/reference/tools.md +309 -0
  245. package/mkdocs/docs/stylesheets/extra.css +231 -0
  246. package/mkdocs/mkdocs.yml +204 -0
  247. package/mkdocs/overrides/.gitkeep +1 -0
  248. package/mkdocs/overrides/main.html +7 -0
  249. package/mkdocs/python-deps.txt +7 -0
  250. package/mkdocs/vercel.json +11 -0
  251. package/package.json +63 -0
  252. package/packages/core/package.json +61 -0
  253. package/packages/core/src/__tests__/bitbucket-client.test.ts +366 -0
  254. package/packages/core/src/__tests__/cli.test.ts +235 -0
  255. package/packages/core/src/__tests__/code-extractor.test.ts +378 -0
  256. package/packages/core/src/__tests__/docker-generator.test.ts +255 -0
  257. package/packages/core/src/__tests__/github-client.test.ts +390 -0
  258. package/packages/core/src/__tests__/gitlab-client.test.ts +319 -0
  259. package/packages/core/src/__tests__/go-extractor.test.ts +351 -0
  260. package/packages/core/src/__tests__/graphql-extractor.test.ts +330 -0
  261. package/packages/core/src/__tests__/java-extractor.test.ts +497 -0
  262. package/packages/core/src/__tests__/plugins.test.ts +467 -0
  263. package/packages/core/src/__tests__/readme-extractor.test.ts +258 -0
  264. package/packages/core/src/__tests__/redis-cache.test.ts +307 -0
  265. package/packages/core/src/__tests__/rust-extractor.test.ts +252 -0
  266. package/packages/core/src/__tests__/streaming.test.ts +251 -0
  267. package/packages/core/src/additional-extractors.ts +333 -0
  268. package/packages/core/src/cache/cache-interface.ts +179 -0
  269. package/packages/core/src/cache/index.ts +210 -0
  270. package/packages/core/src/cache/redis-cache.ts +291 -0
  271. package/packages/core/src/cache/upstash-cache.ts +379 -0
  272. package/packages/core/src/cache.ts +251 -0
  273. package/packages/core/src/cli.ts +822 -0
  274. package/packages/core/src/code-extractor.ts +696 -0
  275. package/packages/core/src/docker-generator.ts +470 -0
  276. package/packages/core/src/edge-compatible.ts +491 -0
  277. package/packages/core/src/extractors/go-extractor.ts +791 -0
  278. package/packages/core/src/extractors/index.ts +9 -0
  279. package/packages/core/src/extractors/java-extractor.ts +937 -0
  280. package/packages/core/src/extractors/rust-extractor.ts +744 -0
  281. package/packages/core/src/github-client.ts +319 -0
  282. package/packages/core/src/go-generator.ts +356 -0
  283. package/packages/core/src/graphql-extractor.ts +358 -0
  284. package/packages/core/src/index.ts +797 -0
  285. package/packages/core/src/langchain-exporter.ts +617 -0
  286. package/packages/core/src/language-parsers.ts +1114 -0
  287. package/packages/core/src/mcp-introspector.ts +279 -0
  288. package/packages/core/src/monorepo-detector.ts +378 -0
  289. package/packages/core/src/plugins/index.ts +370 -0
  290. package/packages/core/src/plugins/registry.ts +404 -0
  291. package/packages/core/src/plugins/types.ts +215 -0
  292. package/packages/core/src/providers/base-provider.ts +246 -0
  293. package/packages/core/src/providers/bitbucket-client.ts +464 -0
  294. package/packages/core/src/providers/gitlab-client.ts +388 -0
  295. package/packages/core/src/providers/index.ts +176 -0
  296. package/packages/core/src/python-generator.ts +260 -0
  297. package/packages/core/src/queue/index.ts +100 -0
  298. package/packages/core/src/queue/memory-queue.ts +445 -0
  299. package/packages/core/src/queue/redis-queue.ts +578 -0
  300. package/packages/core/src/queue/types.ts +251 -0
  301. package/packages/core/src/readme-extractor.ts +409 -0
  302. package/packages/core/src/schema-generator.ts +638 -0
  303. package/packages/core/src/streaming.ts +999 -0
  304. package/packages/core/src/types.ts +289 -0
  305. package/packages/core/tsconfig.json +9 -0
  306. package/packages/core/tsup.config.ts +25 -0
  307. package/packages/mcp-server/README.md +297 -0
  308. package/packages/mcp-server/package.json +55 -0
  309. package/packages/mcp-server/src/__tests__/mcp-server.test.ts +177 -0
  310. package/packages/mcp-server/src/__tests__/tools.test.ts +217 -0
  311. package/packages/mcp-server/src/index.ts +1206 -0
  312. package/packages/mcp-server/src/prompts/index.ts +601 -0
  313. package/packages/mcp-server/src/tools/export-docker.ts +362 -0
  314. package/packages/mcp-server/src/tools/generate-openapi.ts +162 -0
  315. package/packages/mcp-server/src/tools/monitor-mcp-server.ts +448 -0
  316. package/packages/mcp-server/src/tools/stream-convert.ts +398 -0
  317. package/packages/mcp-server/src/tools/test-mcp-tool.ts +531 -0
  318. package/packages/mcp-server/tsconfig.json +12 -0
  319. package/packages/mcp-server/tsup.config.ts +14 -0
  320. package/packages/openapi-parser/package-lock.json +3028 -0
  321. package/packages/openapi-parser/package.json +41 -0
  322. package/packages/openapi-parser/src/analyzer.ts +700 -0
  323. package/packages/openapi-parser/src/asyncapi-parser.ts +475 -0
  324. package/packages/openapi-parser/src/cli.ts +302 -0
  325. package/packages/openapi-parser/src/generator.ts +570 -0
  326. package/packages/openapi-parser/src/generators/express-analyzer.ts +649 -0
  327. package/packages/openapi-parser/src/generators/fastapi-analyzer.ts +960 -0
  328. package/packages/openapi-parser/src/generators/index.ts +200 -0
  329. package/packages/openapi-parser/src/generators/nextjs-analyzer.ts +768 -0
  330. package/packages/openapi-parser/src/generators/openapi-builder.ts +527 -0
  331. package/packages/openapi-parser/src/generators/types.ts +298 -0
  332. package/packages/openapi-parser/src/graphql-parser.ts +462 -0
  333. package/packages/openapi-parser/src/grpc-parser.ts +649 -0
  334. package/packages/openapi-parser/src/har-parser.ts +723 -0
  335. package/packages/openapi-parser/src/index.ts +635 -0
  336. package/packages/openapi-parser/src/insomnia-parser.ts +614 -0
  337. package/packages/openapi-parser/src/parser.ts +231 -0
  338. package/packages/openapi-parser/src/postman-parser.ts +611 -0
  339. package/packages/openapi-parser/src/ref-resolver.ts +313 -0
  340. package/packages/openapi-parser/src/transformer.ts +459 -0
  341. package/packages/openapi-parser/tests/generators/express.test.ts +209 -0
  342. package/packages/openapi-parser/tests/generators/fastapi.test.ts +236 -0
  343. package/packages/openapi-parser/tests/generators/nextjs.test.ts +273 -0
  344. package/packages/openapi-parser/tests/parsers.test.ts +847 -0
  345. package/packages/openapi-parser/tsconfig.json +9 -0
  346. package/packages/openapi-parser/tsup.config.ts +11 -0
  347. package/packages/registry/package.json +59 -0
  348. package/packages/registry/src/cli.ts +456 -0
  349. package/packages/registry/src/index.ts +44 -0
  350. package/packages/registry/src/popular/github.json +47 -0
  351. package/packages/registry/src/popular/index.ts +55 -0
  352. package/packages/registry/src/popular/linear.json +42 -0
  353. package/packages/registry/src/popular/notion.json +42 -0
  354. package/packages/registry/src/popular/openai.json +40 -0
  355. package/packages/registry/src/popular/resend.json +38 -0
  356. package/packages/registry/src/popular/slack.json +42 -0
  357. package/packages/registry/src/popular/stripe.json +163 -0
  358. package/packages/registry/src/popular/supabase.json +42 -0
  359. package/packages/registry/src/popular/twilio.json +40 -0
  360. package/packages/registry/src/popular/vercel.json +40 -0
  361. package/packages/registry/src/registry.ts +492 -0
  362. package/packages/registry/src/storage.ts +334 -0
  363. package/packages/registry/src/types.ts +275 -0
  364. package/packages/registry/src/updater.ts +208 -0
  365. package/packages/registry/tsconfig.json +10 -0
  366. package/packages/registry/tsup.config.ts +11 -0
  367. package/pnpm-workspace.yaml +3 -0
  368. package/scripts/build-docs.sh +16 -0
  369. package/server.json +9 -0
  370. package/templates/Dockerfile.python.template +60 -0
  371. package/templates/Dockerfile.typescript.template +60 -0
  372. package/templates/docker-compose.template.yml +68 -0
  373. package/tests/fixtures/express-app/index.js +34 -0
  374. package/tests/fixtures/express-app/routes/posts.js +43 -0
  375. package/tests/fixtures/express-app/routes/users.js +58 -0
  376. package/tests/fixtures/fastapi-app/main.py +125 -0
  377. package/tests/fixtures/fastapi-app/routes/admin.py +42 -0
  378. package/tests/fixtures/graphql/simple-schema.graphql +65 -0
  379. package/tests/fixtures/mocks/github-api-responses.json +63 -0
  380. package/tests/fixtures/nextjs-app/app/api/posts/route.ts +55 -0
  381. package/tests/fixtures/nextjs-app/app/api/users/[id]/route.ts +63 -0
  382. package/tests/fixtures/nextjs-app/app/api/users/route.ts +44 -0
  383. package/tests/fixtures/nextjs-app/pages/api/health.ts +28 -0
  384. package/tests/fixtures/openapi/petstore.yaml +179 -0
  385. package/tests/integration/langchain-export.test.ts +405 -0
  386. package/tests/integration/openapi-conversion.test.ts +221 -0
  387. package/tsconfig.json +18 -0
  388. package/vitest.config.ts +32 -0
@@ -0,0 +1,960 @@
1
+ /**
2
+ * @fileoverview FastAPI/Flask route analyzer
3
+ * Parses Python web framework routes and extracts API information
4
+ * @copyright Copyright (c) 2024-2026 nirholas
5
+ * @license MIT
6
+ */
7
+
8
+ import {
9
+ RouteAnalyzer,
10
+ FileContent,
11
+ AnalysisResult,
12
+ AnalyzedRoute,
13
+ RouteParameter,
14
+ RouteBody,
15
+ RouteResponse,
16
+ JsonSchemaDefinition,
17
+ HttpMethod,
18
+ } from './types.js';
19
+ import { OpenAPIV3_1 } from 'openapi-types';
20
+
21
+ /**
22
+ * Regular expressions for FastAPI/Flask route detection
23
+ */
24
+ const ROUTE_PATTERNS = {
25
+ // FastAPI decorators: @app.get('/path')
26
+ fastapiDecorator: /@(?:app|router)\s*\.\s*(get|post|put|patch|delete|head|options)\s*\(\s*['"]([^'"]+)['"]/gi,
27
+
28
+ // Flask decorators: @app.route('/path', methods=['GET'])
29
+ flaskRoute: /@(?:app|blueprint|bp)\s*\.\s*route\s*\(\s*['"]([^'"]+)['"](?:\s*,\s*methods\s*=\s*\[([^\]]+)\])?/gi,
30
+
31
+ // FastAPI path parameters
32
+ fastapiPathParam: /\{(\w+)(?::\s*\w+)?\}/g,
33
+
34
+ // Flask path parameters
35
+ flaskPathParam: /<(?:(\w+):)?(\w+)>/g,
36
+
37
+ // Pydantic model
38
+ pydanticModel: /class\s+(\w+)\s*\(\s*(?:BaseModel|Base)\s*\)\s*:\s*([\s\S]*?)(?=\nclass\s|\n\n\n|$)/gi,
39
+
40
+ // Type hints
41
+ typeHint: /(\w+)\s*:\s*(\w+(?:\[[\w\[\],\s]+\])?)/g,
42
+
43
+ // Default values
44
+ defaultValue: /(\w+)\s*:\s*\w+\s*=\s*([^,\n]+)/g,
45
+
46
+ // Query parameter
47
+ queryParam: /(\w+)\s*:\s*(\w+)\s*=\s*Query\s*\(/gi,
48
+
49
+ // Path parameter
50
+ pathParam: /(\w+)\s*:\s*(\w+)\s*=\s*Path\s*\(/gi,
51
+
52
+ // Body parameter
53
+ bodyParam: /(\w+)\s*:\s*(\w+)\s*=\s*Body\s*\(/gi,
54
+
55
+ // Header parameter
56
+ headerParam: /(\w+)\s*:\s*(\w+)\s*=\s*Header\s*\(/gi,
57
+
58
+ // Docstring
59
+ docstring: /"""([\s\S]*?)"""|'''([\s\S]*?)'''/g,
60
+
61
+ // Function definition
62
+ functionDef: /(?:async\s+)?def\s+(\w+)\s*\([^)]*\)(?:\s*->\s*[\w\[\],\s]+)?:\s*/g,
63
+
64
+ // Return type annotation
65
+ returnType: /->\s*([\w\[\],\s]+)/,
66
+
67
+ // Response model
68
+ responseModel: /response_model\s*=\s*(\w+)/gi,
69
+
70
+ // Status code
71
+ statusCode: /status_code\s*=\s*(\d+)/gi,
72
+
73
+ // Tags
74
+ tags: /tags\s*=\s*\[([^\]]+)\]/gi,
75
+
76
+ // Summary/description
77
+ summary: /summary\s*=\s*['"]([^'"]+)['"]/gi,
78
+ description: /description\s*=\s*['"]([^'"]+)['"]/gi,
79
+
80
+ // Deprecated
81
+ deprecated: /deprecated\s*=\s*True/gi,
82
+
83
+ // Optional type
84
+ optionalType: /Optional\[(\w+)\]/,
85
+
86
+ // List type
87
+ listType: /List\[(\w+)\]|list\[(\w+)\]/,
88
+
89
+ // Dict type
90
+ dictType: /Dict\[(\w+),\s*(\w+)\]|dict\[(\w+),\s*(\w+)\]/,
91
+ };
92
+
93
+ /**
94
+ * FastAPI/Flask route analyzer implementation
95
+ */
96
+ export class FastAPIAnalyzer implements RouteAnalyzer {
97
+ name = 'fastapi';
98
+
99
+ private schemas: Record<string, JsonSchemaDefinition> = {};
100
+ private warnings: string[] = [];
101
+ private errors: string[] = [];
102
+ private framework: 'fastapi' | 'flask' = 'fastapi';
103
+
104
+ /**
105
+ * Check if files contain FastAPI or Flask code
106
+ */
107
+ canAnalyze(files: FileContent[]): boolean {
108
+ return files.some(file => {
109
+ const content = file.content;
110
+ return (
111
+ content.includes('from fastapi import') ||
112
+ content.includes('import fastapi') ||
113
+ content.includes('from flask import') ||
114
+ content.includes('import flask') ||
115
+ content.includes('FastAPI()') ||
116
+ content.includes('Flask(__name__)') ||
117
+ /@(?:app|router)\s*\.\s*(get|post|put|patch|delete)/.test(content)
118
+ );
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Analyze FastAPI/Flask files and extract routes
124
+ */
125
+ async analyze(files: FileContent[]): Promise<AnalysisResult> {
126
+ this.schemas = {};
127
+ this.warnings = [];
128
+ this.errors = [];
129
+
130
+ const routes: AnalyzedRoute[] = [];
131
+ const filesAnalyzed: string[] = [];
132
+
133
+ // Detect framework
134
+ this.detectFramework(files);
135
+
136
+ // First pass: extract Pydantic models
137
+ for (const file of files) {
138
+ this.extractPydanticModels(file);
139
+ }
140
+
141
+ // Second pass: extract routes
142
+ for (const file of files) {
143
+ if (this.isPythonApiFile(file)) {
144
+ filesAnalyzed.push(file.path);
145
+ const fileRoutes = this.extractRoutes(file);
146
+ routes.push(...fileRoutes);
147
+ }
148
+ }
149
+
150
+ return {
151
+ routes,
152
+ schemas: this.schemas,
153
+ securitySchemes: this.detectSecuritySchemes(files),
154
+ warnings: this.warnings,
155
+ errors: this.errors,
156
+ framework: this.framework,
157
+ filesAnalyzed,
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Detect whether it's FastAPI or Flask
163
+ */
164
+ private detectFramework(files: FileContent[]): void {
165
+ for (const file of files) {
166
+ if (file.content.includes('fastapi') || file.content.includes('FastAPI')) {
167
+ this.framework = 'fastapi';
168
+ return;
169
+ }
170
+ if (file.content.includes('flask') || file.content.includes('Flask')) {
171
+ this.framework = 'flask';
172
+ return;
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Check if a file contains Python API routes
179
+ */
180
+ private isPythonApiFile(file: FileContent): boolean {
181
+ const content = file.content;
182
+ return (
183
+ file.path.endsWith('.py') &&
184
+ (ROUTE_PATTERNS.fastapiDecorator.test(content) ||
185
+ ROUTE_PATTERNS.flaskRoute.test(content))
186
+ );
187
+ }
188
+
189
+ /**
190
+ * Extract routes from a file
191
+ */
192
+ private extractRoutes(file: FileContent): AnalyzedRoute[] {
193
+ const routes: AnalyzedRoute[] = [];
194
+ const content = file.content;
195
+ const lines = content.split('\n');
196
+
197
+ // Reset regex lastIndex
198
+ ROUTE_PATTERNS.fastapiDecorator.lastIndex = 0;
199
+ ROUTE_PATTERNS.flaskRoute.lastIndex = 0;
200
+
201
+ if (this.framework === 'fastapi') {
202
+ // Extract FastAPI routes
203
+ let match;
204
+ while ((match = ROUTE_PATTERNS.fastapiDecorator.exec(content)) !== null) {
205
+ const method = match[1].toLowerCase() as HttpMethod;
206
+ const path = match[2];
207
+ const line = this.getLineNumber(content, match.index);
208
+
209
+ const decoratorLine = lines[line - 1];
210
+ const route = this.createFastAPIRoute(file, method, path, line, content, decoratorLine);
211
+ routes.push(route);
212
+ }
213
+ } else {
214
+ // Extract Flask routes
215
+ let match;
216
+ while ((match = ROUTE_PATTERNS.flaskRoute.exec(content)) !== null) {
217
+ const path = match[1];
218
+ const methodsStr = match[2] || "'GET'";
219
+ const methods = methodsStr
220
+ .replace(/['"]/g, '')
221
+ .split(',')
222
+ .map(m => m.trim().toLowerCase() as HttpMethod);
223
+
224
+ const line = this.getLineNumber(content, match.index);
225
+
226
+ for (const method of methods) {
227
+ const route = this.createFlaskRoute(file, method, path, line, content);
228
+ routes.push(route);
229
+ }
230
+ }
231
+ }
232
+
233
+ return routes;
234
+ }
235
+
236
+ /**
237
+ * Create a FastAPI route
238
+ */
239
+ private createFastAPIRoute(
240
+ file: FileContent,
241
+ method: HttpMethod,
242
+ path: string,
243
+ line: number,
244
+ content: string,
245
+ decoratorLine: string
246
+ ): AnalyzedRoute {
247
+ const openApiPath = this.convertToOpenApiPath(path);
248
+ const pathParams = this.extractFastAPIPathParameters(path);
249
+
250
+ // Extract metadata from decorator
251
+ const { tags, summary, description, statusCode, responseModel, deprecated } =
252
+ this.parseDecoratorMetadata(decoratorLine, content, line);
253
+
254
+ // Find the function definition and extract parameters
255
+ const funcInfo = this.findFunctionInfo(content, line);
256
+ const { queryParams, headerParams, bodyParam } = this.parseFunctionParameters(funcInfo?.params || '');
257
+
258
+ const route: AnalyzedRoute = {
259
+ method,
260
+ path,
261
+ openApiPath,
262
+ operationId: funcInfo?.name || this.generateOperationId(method, path),
263
+ summary: summary || funcInfo?.docSummary,
264
+ description: description || funcInfo?.docDescription,
265
+ tags,
266
+ pathParameters: pathParams,
267
+ queryParameters: queryParams,
268
+ headerParameters: headerParams,
269
+ responses: this.buildResponses(statusCode, responseModel, method),
270
+ deprecated,
271
+ sourceFile: file.path,
272
+ sourceLine: line,
273
+ };
274
+
275
+ if (bodyParam) {
276
+ route.requestBody = bodyParam;
277
+ } else if (['post', 'put', 'patch'].includes(method)) {
278
+ // Try to infer body from Pydantic type hint
279
+ const bodyType = this.inferBodyType(funcInfo?.params || '');
280
+ if (bodyType) {
281
+ route.requestBody = {
282
+ contentType: 'application/json',
283
+ required: true,
284
+ schema: { $ref: `#/components/schemas/${bodyType}` },
285
+ };
286
+ }
287
+ }
288
+
289
+ return route;
290
+ }
291
+
292
+ /**
293
+ * Create a Flask route
294
+ */
295
+ private createFlaskRoute(
296
+ file: FileContent,
297
+ method: HttpMethod,
298
+ path: string,
299
+ line: number,
300
+ content: string
301
+ ): AnalyzedRoute {
302
+ const openApiPath = this.convertFlaskToOpenApiPath(path);
303
+ const pathParams = this.extractFlaskPathParameters(path);
304
+
305
+ const funcInfo = this.findFunctionInfo(content, line);
306
+
307
+ const route: AnalyzedRoute = {
308
+ method,
309
+ path,
310
+ openApiPath,
311
+ operationId: funcInfo?.name || this.generateOperationId(method, path),
312
+ summary: funcInfo?.docSummary,
313
+ description: funcInfo?.docDescription,
314
+ tags: this.inferTags(path),
315
+ pathParameters: pathParams,
316
+ queryParameters: this.extractFlaskQueryParams(content, line),
317
+ headerParameters: [],
318
+ responses: this.buildResponses(200, undefined, method),
319
+ sourceFile: file.path,
320
+ sourceLine: line,
321
+ };
322
+
323
+ if (['post', 'put', 'patch'].includes(method)) {
324
+ route.requestBody = {
325
+ contentType: 'application/json',
326
+ required: true,
327
+ schema: { type: 'object' },
328
+ };
329
+ }
330
+
331
+ return route;
332
+ }
333
+
334
+ /**
335
+ * Convert FastAPI path to OpenAPI format
336
+ */
337
+ private convertToOpenApiPath(path: string): string {
338
+ // FastAPI already uses {param} format
339
+ return path;
340
+ }
341
+
342
+ /**
343
+ * Convert Flask path to OpenAPI format
344
+ */
345
+ private convertFlaskToOpenApiPath(path: string): string {
346
+ // Convert <type:param> to {param}
347
+ return path.replace(/<(?:\w+:)?(\w+)>/g, '{$1}');
348
+ }
349
+
350
+ /**
351
+ * Extract path parameters from FastAPI path
352
+ */
353
+ private extractFastAPIPathParameters(path: string): RouteParameter[] {
354
+ const params: RouteParameter[] = [];
355
+ ROUTE_PATTERNS.fastapiPathParam.lastIndex = 0;
356
+
357
+ let match;
358
+ while ((match = ROUTE_PATTERNS.fastapiPathParam.exec(path)) !== null) {
359
+ params.push({
360
+ name: match[1],
361
+ location: 'path',
362
+ required: true,
363
+ type: 'string',
364
+ description: `Path parameter: ${match[1]}`,
365
+ });
366
+ }
367
+
368
+ return params;
369
+ }
370
+
371
+ /**
372
+ * Extract path parameters from Flask path
373
+ */
374
+ private extractFlaskPathParameters(path: string): RouteParameter[] {
375
+ const params: RouteParameter[] = [];
376
+ ROUTE_PATTERNS.flaskPathParam.lastIndex = 0;
377
+
378
+ let match;
379
+ while ((match = ROUTE_PATTERNS.flaskPathParam.exec(path)) !== null) {
380
+ const type = match[1] || 'string';
381
+ const name = match[2];
382
+ params.push({
383
+ name,
384
+ location: 'path',
385
+ required: true,
386
+ type: this.mapFlaskType(type),
387
+ description: `Path parameter: ${name}`,
388
+ });
389
+ }
390
+
391
+ return params;
392
+ }
393
+
394
+ /**
395
+ * Map Flask type to JSON Schema type
396
+ */
397
+ private mapFlaskType(flaskType: string): 'string' | 'number' | 'integer' {
398
+ const typeMap: Record<string, 'string' | 'number' | 'integer'> = {
399
+ string: 'string',
400
+ int: 'integer',
401
+ float: 'number',
402
+ path: 'string',
403
+ uuid: 'string',
404
+ };
405
+ return typeMap[flaskType] || 'string';
406
+ }
407
+
408
+ /**
409
+ * Parse FastAPI decorator metadata
410
+ */
411
+ private parseDecoratorMetadata(
412
+ decoratorLine: string,
413
+ content: string,
414
+ line: number
415
+ ): {
416
+ tags: string[];
417
+ summary?: string;
418
+ description?: string;
419
+ statusCode?: number;
420
+ responseModel?: string;
421
+ deprecated: boolean;
422
+ } {
423
+ // Get the full decorator including multiple lines
424
+ const lines = content.split('\n');
425
+ let fullDecorator = decoratorLine;
426
+ let i = line;
427
+
428
+ // Handle multi-line decorators
429
+ while (!fullDecorator.includes(')') && i < lines.length) {
430
+ fullDecorator += lines[i];
431
+ i++;
432
+ }
433
+
434
+ const result = {
435
+ tags: [] as string[],
436
+ summary: undefined as string | undefined,
437
+ description: undefined as string | undefined,
438
+ statusCode: undefined as number | undefined,
439
+ responseModel: undefined as string | undefined,
440
+ deprecated: false,
441
+ };
442
+
443
+ // Extract tags
444
+ const tagsMatch = fullDecorator.match(/tags\s*=\s*\[([^\]]+)\]/);
445
+ if (tagsMatch) {
446
+ result.tags = tagsMatch[1]
447
+ .split(',')
448
+ .map(t => t.trim().replace(/['"]/g, ''))
449
+ .filter(t => t);
450
+ }
451
+
452
+ // Extract summary
453
+ const summaryMatch = fullDecorator.match(/summary\s*=\s*['"]([^'"]+)['"]/);
454
+ if (summaryMatch) {
455
+ result.summary = summaryMatch[1];
456
+ }
457
+
458
+ // Extract description
459
+ const descMatch = fullDecorator.match(/description\s*=\s*['"]([^'"]+)['"]/);
460
+ if (descMatch) {
461
+ result.description = descMatch[1];
462
+ }
463
+
464
+ // Extract status code
465
+ const statusMatch = fullDecorator.match(/status_code\s*=\s*(\d+)/);
466
+ if (statusMatch) {
467
+ result.statusCode = parseInt(statusMatch[1], 10);
468
+ }
469
+
470
+ // Extract response model
471
+ const modelMatch = fullDecorator.match(/response_model\s*=\s*(\w+)/);
472
+ if (modelMatch) {
473
+ result.responseModel = modelMatch[1];
474
+ }
475
+
476
+ // Check for deprecated
477
+ if (fullDecorator.includes('deprecated=True')) {
478
+ result.deprecated = true;
479
+ }
480
+
481
+ return result;
482
+ }
483
+
484
+ /**
485
+ * Find function info after a route decorator
486
+ */
487
+ private findFunctionInfo(content: string, decoratorLine: number): {
488
+ name: string;
489
+ params: string;
490
+ returnType?: string;
491
+ docSummary?: string;
492
+ docDescription?: string;
493
+ } | undefined {
494
+ const lines = content.split('\n');
495
+
496
+ // Find the function definition after the decorator
497
+ for (let i = decoratorLine; i < Math.min(decoratorLine + 10, lines.length); i++) {
498
+ const line = lines[i];
499
+ const funcMatch = line.match(/(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*([\w\[\],\s]+))?/);
500
+
501
+ if (funcMatch) {
502
+ const name = funcMatch[1];
503
+ const params = funcMatch[2];
504
+ const returnType = funcMatch[3];
505
+
506
+ // Look for docstring
507
+ const docstring = this.extractDocstring(lines, i + 1);
508
+
509
+ return {
510
+ name,
511
+ params,
512
+ returnType,
513
+ docSummary: docstring?.summary,
514
+ docDescription: docstring?.description,
515
+ };
516
+ }
517
+ }
518
+
519
+ return undefined;
520
+ }
521
+
522
+ /**
523
+ * Extract docstring from function
524
+ */
525
+ private extractDocstring(lines: string[], startLine: number): {
526
+ summary?: string;
527
+ description?: string;
528
+ } | undefined {
529
+ // Look for docstring start
530
+ for (let i = startLine; i < Math.min(startLine + 3, lines.length); i++) {
531
+ const line = lines[i].trim();
532
+ if (line.startsWith('"""') || line.startsWith("'''")) {
533
+ const quote = line.startsWith('"""') ? '"""' : "'''";
534
+ let docContent = '';
535
+
536
+ // Single line docstring
537
+ if (line.endsWith(quote) && line.length > 6) {
538
+ docContent = line.slice(3, -3);
539
+ } else {
540
+ // Multi-line docstring
541
+ docContent = line.slice(3);
542
+ for (let j = i + 1; j < lines.length; j++) {
543
+ const docLine = lines[j];
544
+ if (docLine.includes(quote)) {
545
+ docContent += '\n' + docLine.split(quote)[0];
546
+ break;
547
+ }
548
+ docContent += '\n' + docLine;
549
+ }
550
+ }
551
+
552
+ const parts = docContent.trim().split('\n\n');
553
+ return {
554
+ summary: parts[0]?.trim(),
555
+ description: parts.length > 1 ? parts.slice(1).join('\n\n').trim() : undefined,
556
+ };
557
+ }
558
+ }
559
+
560
+ return undefined;
561
+ }
562
+
563
+ /**
564
+ * Parse function parameters
565
+ */
566
+ private parseFunctionParameters(params: string): {
567
+ queryParams: RouteParameter[];
568
+ headerParams: RouteParameter[];
569
+ bodyParam?: RouteBody;
570
+ } {
571
+ const queryParams: RouteParameter[] = [];
572
+ const headerParams: RouteParameter[] = [];
573
+ let bodyParam: RouteBody | undefined;
574
+
575
+ // Split parameters
576
+ const paramList = params.split(',').map(p => p.trim()).filter(p => p);
577
+
578
+ for (const param of paramList) {
579
+ // Skip self, request, etc.
580
+ if (['self', 'request', 'db', 'session'].some(skip => param.startsWith(skip))) {
581
+ continue;
582
+ }
583
+
584
+ // Check for Query parameter
585
+ if (param.includes('= Query(')) {
586
+ const match = param.match(/(\w+)\s*:\s*(\w+)/);
587
+ if (match) {
588
+ queryParams.push({
589
+ name: match[1],
590
+ location: 'query',
591
+ required: !param.includes('None'),
592
+ type: this.mapPythonType(match[2]),
593
+ });
594
+ }
595
+ }
596
+ // Check for Header parameter
597
+ else if (param.includes('= Header(')) {
598
+ const match = param.match(/(\w+)\s*:\s*(\w+)/);
599
+ if (match) {
600
+ headerParams.push({
601
+ name: match[1],
602
+ location: 'header',
603
+ required: !param.includes('None'),
604
+ type: this.mapPythonType(match[2]),
605
+ });
606
+ }
607
+ }
608
+ // Check for Body parameter
609
+ else if (param.includes('= Body(')) {
610
+ const match = param.match(/(\w+)\s*:\s*(\w+)/);
611
+ if (match) {
612
+ bodyParam = {
613
+ contentType: 'application/json',
614
+ required: !param.includes('None'),
615
+ schema: this.schemas[match[2]]
616
+ ? { $ref: `#/components/schemas/${match[2]}` }
617
+ : { type: 'object' },
618
+ };
619
+ }
620
+ }
621
+ // Regular type-hinted parameter (likely Pydantic model for body)
622
+ else if (param.includes(':') && !param.includes('=')) {
623
+ const match = param.match(/(\w+)\s*:\s*(\w+)/);
624
+ if (match && this.schemas[match[2]]) {
625
+ // This is likely a request body
626
+ bodyParam = {
627
+ contentType: 'application/json',
628
+ required: true,
629
+ schema: { $ref: `#/components/schemas/${match[2]}` },
630
+ };
631
+ }
632
+ }
633
+ }
634
+
635
+ return { queryParams, headerParams, bodyParam };
636
+ }
637
+
638
+ /**
639
+ * Infer body type from function parameters
640
+ */
641
+ private inferBodyType(params: string): string | undefined {
642
+ const paramList = params.split(',').map(p => p.trim()).filter(p => p);
643
+
644
+ for (const param of paramList) {
645
+ if (['self', 'request', 'db', 'session'].some(skip => param.startsWith(skip))) {
646
+ continue;
647
+ }
648
+
649
+ const match = param.match(/\w+\s*:\s*(\w+)/);
650
+ if (match && this.schemas[match[1]]) {
651
+ return match[1];
652
+ }
653
+ }
654
+
655
+ return undefined;
656
+ }
657
+
658
+ /**
659
+ * Map Python type to JSON Schema type
660
+ */
661
+ private mapPythonType(pythonType: string): 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' {
662
+ const typeMap: Record<string, 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object'> = {
663
+ str: 'string',
664
+ int: 'integer',
665
+ float: 'number',
666
+ bool: 'boolean',
667
+ list: 'array',
668
+ List: 'array',
669
+ dict: 'object',
670
+ Dict: 'object',
671
+ Any: 'object',
672
+ };
673
+ return typeMap[pythonType] || 'object';
674
+ }
675
+
676
+ /**
677
+ * Extract Flask query parameters
678
+ */
679
+ private extractFlaskQueryParams(content: string, routeLine: number): RouteParameter[] {
680
+ const params: RouteParameter[] = [];
681
+ const lines = content.split('\n');
682
+
683
+ // Look for request.args usage in the function
684
+ for (let i = routeLine; i < Math.min(routeLine + 50, lines.length); i++) {
685
+ const line = lines[i];
686
+
687
+ // Check for request.args.get('param')
688
+ const argsMatch = line.match(/request\.args\.get\s*\(\s*['"](\w+)['"]/g);
689
+ if (argsMatch) {
690
+ for (const match of argsMatch) {
691
+ const paramMatch = match.match(/['"](\w+)['"]/);
692
+ if (paramMatch) {
693
+ params.push({
694
+ name: paramMatch[1],
695
+ location: 'query',
696
+ required: false,
697
+ type: 'string',
698
+ });
699
+ }
700
+ }
701
+ }
702
+
703
+ // Stop at next function definition
704
+ if (i > routeLine && /^def\s+\w+/.test(line)) {
705
+ break;
706
+ }
707
+ }
708
+
709
+ return params;
710
+ }
711
+
712
+ /**
713
+ * Build response definitions
714
+ */
715
+ private buildResponses(
716
+ statusCode?: number,
717
+ responseModel?: string,
718
+ method?: HttpMethod
719
+ ): RouteResponse[] {
720
+ const responses: RouteResponse[] = [];
721
+
722
+ const defaultStatus = statusCode || this.getDefaultStatus(method);
723
+
724
+ responses.push({
725
+ statusCode: defaultStatus,
726
+ description: this.getStatusDescription(defaultStatus),
727
+ contentType: 'application/json',
728
+ schema: responseModel && this.schemas[responseModel]
729
+ ? { $ref: `#/components/schemas/${responseModel}` }
730
+ : { type: 'object' },
731
+ });
732
+
733
+ // Add error responses
734
+ responses.push({
735
+ statusCode: 422,
736
+ description: 'Validation Error',
737
+ contentType: 'application/json',
738
+ schema: {
739
+ type: 'object',
740
+ properties: {
741
+ detail: {
742
+ type: 'array',
743
+ items: {
744
+ type: 'object',
745
+ properties: {
746
+ loc: { type: 'array', items: { type: 'string' } },
747
+ msg: { type: 'string' },
748
+ type: { type: 'string' },
749
+ },
750
+ },
751
+ },
752
+ },
753
+ },
754
+ });
755
+
756
+ return responses;
757
+ }
758
+
759
+ /**
760
+ * Get default status code for method
761
+ */
762
+ private getDefaultStatus(method?: HttpMethod): number {
763
+ switch (method) {
764
+ case 'post': return 201;
765
+ case 'delete': return 204;
766
+ default: return 200;
767
+ }
768
+ }
769
+
770
+ /**
771
+ * Get status description
772
+ */
773
+ private getStatusDescription(status: number): string {
774
+ const descriptions: Record<number, string> = {
775
+ 200: 'Successful Response',
776
+ 201: 'Created',
777
+ 204: 'No Content',
778
+ 400: 'Bad Request',
779
+ 401: 'Unauthorized',
780
+ 403: 'Forbidden',
781
+ 404: 'Not Found',
782
+ 422: 'Validation Error',
783
+ 500: 'Internal Server Error',
784
+ };
785
+ return descriptions[status] || 'Response';
786
+ }
787
+
788
+ /**
789
+ * Extract Pydantic models
790
+ */
791
+ private extractPydanticModels(file: FileContent): void {
792
+ const content = file.content;
793
+ ROUTE_PATTERNS.pydanticModel.lastIndex = 0;
794
+
795
+ let match;
796
+ while ((match = ROUTE_PATTERNS.pydanticModel.exec(content)) !== null) {
797
+ const name = match[1];
798
+ const body = match[2];
799
+ this.schemas[name] = this.parsePydanticModel(body);
800
+ }
801
+ }
802
+
803
+ /**
804
+ * Parse Pydantic model body to JSON Schema
805
+ */
806
+ private parsePydanticModel(body: string): JsonSchemaDefinition {
807
+ const properties: Record<string, JsonSchemaDefinition> = {};
808
+ const required: string[] = [];
809
+
810
+ const lines = body.split('\n').filter(l => l.trim() && !l.trim().startsWith('#'));
811
+
812
+ for (const line of lines) {
813
+ const match = line.match(/^\s*(\w+)\s*:\s*([^=]+)(?:=\s*(.+))?/);
814
+ if (match) {
815
+ const propName = match[1];
816
+ const propType = match[2].trim();
817
+ const defaultValue = match[3]?.trim();
818
+
819
+ properties[propName] = this.pythonTypeToJsonSchema(propType);
820
+
821
+ // If no default value and not Optional, it's required
822
+ if (!defaultValue && !propType.includes('Optional')) {
823
+ required.push(propName);
824
+ }
825
+ }
826
+ }
827
+
828
+ return {
829
+ type: 'object',
830
+ properties,
831
+ required: required.length > 0 ? required : undefined,
832
+ };
833
+ }
834
+
835
+ /**
836
+ * Convert Python type annotation to JSON Schema
837
+ */
838
+ private pythonTypeToJsonSchema(pythonType: string): JsonSchemaDefinition {
839
+ const type = pythonType.trim();
840
+
841
+ // Check for Optional
842
+ const optionalMatch = type.match(/Optional\[(.+)\]/);
843
+ if (optionalMatch) {
844
+ const innerSchema = this.pythonTypeToJsonSchema(optionalMatch[1]);
845
+ return { ...innerSchema, nullable: true };
846
+ }
847
+
848
+ // Check for List
849
+ const listMatch = type.match(/(?:List|list)\[(.+)\]/);
850
+ if (listMatch) {
851
+ return {
852
+ type: 'array',
853
+ items: this.pythonTypeToJsonSchema(listMatch[1]),
854
+ };
855
+ }
856
+
857
+ // Check for Dict
858
+ const dictMatch = type.match(/(?:Dict|dict)\[(\w+),\s*(.+)\]/);
859
+ if (dictMatch) {
860
+ return {
861
+ type: 'object',
862
+ additionalProperties: this.pythonTypeToJsonSchema(dictMatch[2]),
863
+ };
864
+ }
865
+
866
+ // Basic types
867
+ const typeMap: Record<string, JsonSchemaDefinition> = {
868
+ str: { type: 'string' },
869
+ int: { type: 'integer' },
870
+ float: { type: 'number' },
871
+ bool: { type: 'boolean' },
872
+ Any: { type: 'object' },
873
+ None: { type: 'null' },
874
+ };
875
+
876
+ if (typeMap[type]) {
877
+ return typeMap[type];
878
+ }
879
+
880
+ // Check if it's a reference to a known schema
881
+ if (this.schemas[type]) {
882
+ return { $ref: `#/components/schemas/${type}` };
883
+ }
884
+
885
+ return { type: 'object' };
886
+ }
887
+
888
+ /**
889
+ * Generate operation ID
890
+ */
891
+ private generateOperationId(method: HttpMethod, path: string): string {
892
+ const cleanPath = path
893
+ .replace(/[/{}]/g, '_')
894
+ .replace(/_+/g, '_')
895
+ .replace(/^_|_$/g, '');
896
+ return `${method}_${cleanPath || 'root'}`;
897
+ }
898
+
899
+ /**
900
+ * Infer tags from path
901
+ */
902
+ private inferTags(path: string): string[] {
903
+ const parts = path.split('/').filter(p => p && !p.startsWith('{'));
904
+ if (parts.length > 0) {
905
+ return [parts[0].charAt(0).toUpperCase() + parts[0].slice(1)];
906
+ }
907
+ return ['Default'];
908
+ }
909
+
910
+ /**
911
+ * Get line number from character index
912
+ */
913
+ private getLineNumber(content: string, index: number): number {
914
+ return content.slice(0, index).split('\n').length;
915
+ }
916
+
917
+ /**
918
+ * Detect security schemes
919
+ */
920
+ private detectSecuritySchemes(files: FileContent[]): Record<string, OpenAPIV3_1.SecuritySchemeObject> {
921
+ const schemes: Record<string, OpenAPIV3_1.SecuritySchemeObject> = {};
922
+
923
+ for (const file of files) {
924
+ const content = file.content;
925
+
926
+ // OAuth2
927
+ if (content.includes('OAuth2PasswordBearer') || content.includes('oauth2_scheme')) {
928
+ schemes.oauth2 = {
929
+ type: 'oauth2',
930
+ flows: {
931
+ password: {
932
+ tokenUrl: '/token',
933
+ scopes: {},
934
+ },
935
+ },
936
+ };
937
+ }
938
+
939
+ // API Key
940
+ if (content.includes('APIKeyHeader') || content.includes('api_key')) {
941
+ schemes.apiKey = {
942
+ type: 'apiKey',
943
+ in: 'header',
944
+ name: 'X-API-Key',
945
+ };
946
+ }
947
+
948
+ // JWT/Bearer
949
+ if (content.includes('HTTPBearer') || content.includes('jwt')) {
950
+ schemes.bearerAuth = {
951
+ type: 'http',
952
+ scheme: 'bearer',
953
+ bearerFormat: 'JWT',
954
+ };
955
+ }
956
+ }
957
+
958
+ return schemes;
959
+ }
960
+ }