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,390 @@
1
+ /**
2
+ * @fileoverview Unit tests for github-client module
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
6
+ import { GithubClient } from '../github-client';
7
+
8
+ // Mock Octokit
9
+ vi.mock('@octokit/rest', () => ({
10
+ Octokit: vi.fn().mockImplementation(() => ({
11
+ repos: {
12
+ get: vi.fn(),
13
+ getContent: vi.fn(),
14
+ },
15
+ rateLimit: {
16
+ get: vi.fn(),
17
+ },
18
+ })),
19
+ }));
20
+
21
+ describe('GithubClient', () => {
22
+ let client: GithubClient;
23
+
24
+ beforeEach(() => {
25
+ vi.clearAllMocks();
26
+ client = new GithubClient();
27
+ });
28
+
29
+ describe('parseGithubUrl', () => {
30
+ it('should parse basic GitHub URL', () => {
31
+ const url = 'https://github.com/owner/repo';
32
+ const result = client.parseGithubUrl(url);
33
+
34
+ expect(result.owner).toBe('owner');
35
+ expect(result.repo).toBe('repo');
36
+ expect(result.branch).toBe('main');
37
+ });
38
+
39
+ it('should parse URL with .git suffix', () => {
40
+ const url = 'https://github.com/owner/repo.git';
41
+ const result = client.parseGithubUrl(url);
42
+
43
+ expect(result.owner).toBe('owner');
44
+ expect(result.repo).toBe('repo');
45
+ });
46
+
47
+ it('should parse URL with branch', () => {
48
+ const url = 'https://github.com/owner/repo/tree/develop';
49
+ const result = client.parseGithubUrl(url);
50
+
51
+ expect(result.owner).toBe('owner');
52
+ expect(result.repo).toBe('repo');
53
+ expect(result.branch).toBe('develop');
54
+ });
55
+
56
+ it('should parse URL with branch and path', () => {
57
+ const url = 'https://github.com/owner/repo/tree/main/src/index.ts';
58
+ const result = client.parseGithubUrl(url);
59
+
60
+ expect(result.owner).toBe('owner');
61
+ expect(result.repo).toBe('repo');
62
+ expect(result.branch).toBe('main');
63
+ expect(result.path).toBe('src/index.ts');
64
+ });
65
+
66
+ it('should throw for invalid URL', () => {
67
+ const url = 'https://gitlab.com/owner/repo';
68
+
69
+ expect(() => client.parseGithubUrl(url)).toThrow('Invalid GitHub URL');
70
+ });
71
+
72
+ it('should throw for empty URL', () => {
73
+ expect(() => client.parseGithubUrl('')).toThrow('Invalid GitHub URL');
74
+ });
75
+
76
+ it('should handle URLs with trailing slash', () => {
77
+ const url = 'https://github.com/owner/repo/';
78
+ const result = client.parseGithubUrl(url);
79
+
80
+ expect(result.owner).toBe('owner');
81
+ expect(result.repo).toBe('repo');
82
+ });
83
+
84
+ it('should parse URL with special characters in repo name', () => {
85
+ const url = 'https://github.com/owner/my-awesome-repo';
86
+ const result = client.parseGithubUrl(url);
87
+
88
+ expect(result.owner).toBe('owner');
89
+ expect(result.repo).toBe('my-awesome-repo');
90
+ });
91
+
92
+ it('should handle feature branch names', () => {
93
+ const url = 'https://github.com/owner/repo/tree/feature/new-feature';
94
+ const result = client.parseGithubUrl(url);
95
+
96
+ expect(result.owner).toBe('owner');
97
+ expect(result.repo).toBe('repo');
98
+ expect(result.branch).toBe('feature/new-feature');
99
+ });
100
+ });
101
+
102
+ describe('getRepoMetadata', () => {
103
+ it('should fetch repository metadata', async () => {
104
+ const mockData = {
105
+ stargazers_count: 100,
106
+ language: 'TypeScript',
107
+ license: { spdx_id: 'MIT' },
108
+ description: 'A test repo',
109
+ default_branch: 'main',
110
+ };
111
+
112
+ (client as any).octokit.repos.get.mockResolvedValue({ data: mockData });
113
+
114
+ const result = await client.getRepoMetadata('owner', 'repo');
115
+
116
+ expect(result.stars).toBe(100);
117
+ expect(result.language).toBe('TypeScript');
118
+ expect(result.license).toBe('MIT');
119
+ expect(result.description).toBe('A test repo');
120
+ expect(result.defaultBranch).toBe('main');
121
+ });
122
+
123
+ it('should handle null license', async () => {
124
+ const mockData = {
125
+ stargazers_count: 50,
126
+ language: 'JavaScript',
127
+ license: null,
128
+ description: null,
129
+ default_branch: 'master',
130
+ };
131
+
132
+ (client as any).octokit.repos.get.mockResolvedValue({ data: mockData });
133
+
134
+ const result = await client.getRepoMetadata('owner', 'repo');
135
+
136
+ expect(result.license).toBeUndefined();
137
+ expect(result.description).toBeUndefined();
138
+ });
139
+
140
+ it('should handle missing language', async () => {
141
+ const mockData = {
142
+ stargazers_count: 0,
143
+ language: null,
144
+ license: null,
145
+ description: null,
146
+ default_branch: 'main',
147
+ };
148
+
149
+ (client as any).octokit.repos.get.mockResolvedValue({ data: mockData });
150
+
151
+ const result = await client.getRepoMetadata('owner', 'repo');
152
+
153
+ expect(result.language).toBe('unknown');
154
+ });
155
+ });
156
+
157
+ describe('getFileContent', () => {
158
+ it('should fetch file content', async () => {
159
+ const content = 'console.log("Hello");';
160
+ const encoded = Buffer.from(content).toString('base64');
161
+
162
+ (client as any).octokit.repos.getContent.mockResolvedValue({
163
+ data: {
164
+ type: 'file',
165
+ path: 'index.js',
166
+ content: encoded,
167
+ sha: 'abc123',
168
+ },
169
+ });
170
+
171
+ const result = await client.getFileContent('owner', 'repo', 'index.js');
172
+
173
+ expect(result).not.toBeNull();
174
+ expect(result?.content).toBe(content);
175
+ expect(result?.path).toBe('index.js');
176
+ });
177
+
178
+ it('should return null for directories', async () => {
179
+ (client as any).octokit.repos.getContent.mockResolvedValue({
180
+ data: [
181
+ { type: 'file', path: 'file1.ts' },
182
+ { type: 'file', path: 'file2.ts' },
183
+ ],
184
+ });
185
+
186
+ const result = await client.getFileContent('owner', 'repo', 'src');
187
+
188
+ expect(result).toBeNull();
189
+ });
190
+
191
+ it('should return null on 404 error', async () => {
192
+ (client as any).octokit.repos.getContent.mockRejectedValue(new Error('Not Found'));
193
+
194
+ const result = await client.getFileContent('owner', 'repo', 'nonexistent.ts');
195
+
196
+ expect(result).toBeNull();
197
+ });
198
+
199
+ it('should handle file with no content', async () => {
200
+ (client as any).octokit.repos.getContent.mockResolvedValue({
201
+ data: {
202
+ type: 'file',
203
+ path: 'empty.ts',
204
+ content: undefined,
205
+ sha: 'def456',
206
+ },
207
+ });
208
+
209
+ const result = await client.getFileContent('owner', 'repo', 'empty.ts');
210
+
211
+ expect(result).toBeNull();
212
+ });
213
+ });
214
+
215
+ describe('listDirectory', () => {
216
+ it('should list directory contents', async () => {
217
+ (client as any).octokit.repos.getContent.mockResolvedValue({
218
+ data: [
219
+ { path: 'src', type: 'dir', sha: 'abc' },
220
+ { path: 'README.md', type: 'file', sha: 'def' },
221
+ { path: 'package.json', type: 'file', sha: 'ghi' },
222
+ ],
223
+ });
224
+
225
+ const result = await client.listDirectory('owner', 'repo', '');
226
+
227
+ expect(result).toHaveLength(3);
228
+ expect(result[0].type).toBe('dir');
229
+ expect(result[1].type).toBe('file');
230
+ });
231
+
232
+ it('should return empty array for file path', async () => {
233
+ (client as any).octokit.repos.getContent.mockResolvedValue({
234
+ data: { type: 'file', path: 'index.ts' },
235
+ });
236
+
237
+ const result = await client.listDirectory('owner', 'repo', 'index.ts');
238
+
239
+ expect(result).toEqual([]);
240
+ });
241
+
242
+ it('should return empty array on error', async () => {
243
+ (client as any).octokit.repos.getContent.mockRejectedValue(new Error('Not Found'));
244
+
245
+ const result = await client.listDirectory('owner', 'repo', 'nonexistent');
246
+
247
+ expect(result).toEqual([]);
248
+ });
249
+ });
250
+
251
+ describe('findApiSpecs', () => {
252
+ it('should find OpenAPI spec in root', async () => {
253
+ const specContent = JSON.stringify({
254
+ openapi: '3.0.0',
255
+ info: { title: 'API', version: '1.0.0' },
256
+ paths: {},
257
+ });
258
+
259
+ (client as any).octokit.repos.getContent.mockImplementation(async ({ path }: { path: string }) => {
260
+ if (path === 'openapi.json') {
261
+ return {
262
+ data: {
263
+ type: 'file',
264
+ content: Buffer.from(specContent).toString('base64'),
265
+ path,
266
+ sha: 'abc',
267
+ },
268
+ };
269
+ }
270
+ throw new Error('Not Found');
271
+ });
272
+
273
+ const result = await client.findApiSpecs('owner', 'repo');
274
+
275
+ expect(result).toHaveLength(1);
276
+ expect(result[0].type).toBe('openapi');
277
+ expect(result[0].version).toBe('3.0.0');
278
+ });
279
+
280
+ it('should find Swagger 2.0 spec', async () => {
281
+ const specContent = JSON.stringify({
282
+ swagger: '2.0',
283
+ info: { title: 'API', version: '1.0.0' },
284
+ paths: {},
285
+ });
286
+
287
+ (client as any).octokit.repos.getContent.mockImplementation(async ({ path }: { path: string }) => {
288
+ if (path === 'swagger.json') {
289
+ return {
290
+ data: {
291
+ type: 'file',
292
+ content: Buffer.from(specContent).toString('base64'),
293
+ path,
294
+ sha: 'abc',
295
+ },
296
+ };
297
+ }
298
+ throw new Error('Not Found');
299
+ });
300
+
301
+ const result = await client.findApiSpecs('owner', 'repo');
302
+
303
+ expect(result.length).toBeGreaterThanOrEqual(0);
304
+ });
305
+
306
+ it('should return empty array when no specs found', async () => {
307
+ (client as any).octokit.repos.getContent.mockRejectedValue(new Error('Not Found'));
308
+
309
+ const result = await client.findApiSpecs('owner', 'repo');
310
+
311
+ expect(result).toEqual([]);
312
+ });
313
+ });
314
+
315
+ describe('getReadme', () => {
316
+ it('should fetch README.md', async () => {
317
+ const readmeContent = '# Hello World';
318
+ const encoded = Buffer.from(readmeContent).toString('base64');
319
+
320
+ (client as any).octokit.repos.getContent.mockImplementation(async ({ path }: { path: string }) => {
321
+ if (path === 'README.md') {
322
+ return {
323
+ data: {
324
+ type: 'file',
325
+ content: encoded,
326
+ path,
327
+ sha: 'abc',
328
+ },
329
+ };
330
+ }
331
+ throw new Error('Not Found');
332
+ });
333
+
334
+ const result = await client.getReadme('owner', 'repo');
335
+
336
+ expect(result).toBe(readmeContent);
337
+ });
338
+
339
+ it('should try alternative README filenames', async () => {
340
+ const readmeContent = '# Hello';
341
+ const encoded = Buffer.from(readmeContent).toString('base64');
342
+
343
+ (client as any).octokit.repos.getContent.mockImplementation(async ({ path }: { path: string }) => {
344
+ if (path === 'readme.md') {
345
+ return {
346
+ data: {
347
+ type: 'file',
348
+ content: encoded,
349
+ path,
350
+ sha: 'abc',
351
+ },
352
+ };
353
+ }
354
+ throw new Error('Not Found');
355
+ });
356
+
357
+ const result = await client.getReadme('owner', 'repo');
358
+
359
+ expect(result).toBe(readmeContent);
360
+ });
361
+
362
+ it('should return null when README not found', async () => {
363
+ (client as any).octokit.repos.getContent.mockRejectedValue(new Error('Not Found'));
364
+
365
+ const result = await client.getReadme('owner', 'repo');
366
+
367
+ expect(result).toBeNull();
368
+ });
369
+ });
370
+
371
+ describe('getRateLimit', () => {
372
+ it('should return rate limit info', async () => {
373
+ (client as any).octokit.rateLimit.get.mockResolvedValue({
374
+ data: {
375
+ rate: {
376
+ remaining: 4999,
377
+ limit: 5000,
378
+ reset: 1700000000,
379
+ },
380
+ },
381
+ });
382
+
383
+ const result = await client.getRateLimit();
384
+
385
+ expect(result.remaining).toBe(4999);
386
+ expect(result.limit).toBe(5000);
387
+ expect(result.reset).toBeInstanceOf(Date);
388
+ });
389
+ });
390
+ });
@@ -0,0 +1,319 @@
1
+ /**
2
+ * @fileoverview Unit tests for GitLab client
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
6
+ import { GitLabClient, createGitLabClient } from '../providers/gitlab-client';
7
+
8
+ // Mock global fetch
9
+ const mockFetch = vi.fn();
10
+ global.fetch = mockFetch;
11
+
12
+ describe('GitLabClient', () => {
13
+ let client: GitLabClient;
14
+
15
+ beforeEach(() => {
16
+ vi.clearAllMocks();
17
+ client = new GitLabClient({ token: 'test-token' });
18
+ });
19
+
20
+ afterEach(() => {
21
+ vi.resetAllMocks();
22
+ });
23
+
24
+ describe('parseUrl', () => {
25
+ it('should parse simple GitLab URL', () => {
26
+ const result = client.parseUrl('https://gitlab.com/owner/repo');
27
+
28
+ expect(result.owner).toBe('owner');
29
+ expect(result.repo).toBe('repo');
30
+ expect(result.branch).toBe('main');
31
+ });
32
+
33
+ it('should parse GitLab URL with .git suffix', () => {
34
+ const result = client.parseUrl('https://gitlab.com/owner/repo.git');
35
+
36
+ expect(result.owner).toBe('owner');
37
+ expect(result.repo).toBe('repo');
38
+ });
39
+
40
+ it('should parse GitLab URL with nested groups', () => {
41
+ const result = client.parseUrl('https://gitlab.com/group/subgroup/repo');
42
+
43
+ expect(result.owner).toBe('group/subgroup');
44
+ expect(result.repo).toBe('repo');
45
+ });
46
+
47
+ it('should parse GitLab URL with tree/branch', () => {
48
+ const result = client.parseUrl('https://gitlab.com/owner/repo/-/tree/develop');
49
+
50
+ expect(result.owner).toBe('owner');
51
+ expect(result.repo).toBe('repo');
52
+ expect(result.branch).toBe('develop');
53
+ });
54
+
55
+ it('should parse GitLab URL with tree/branch/path', () => {
56
+ const result = client.parseUrl('https://gitlab.com/owner/repo/-/tree/main/src/index.ts');
57
+
58
+ expect(result.owner).toBe('owner');
59
+ expect(result.repo).toBe('repo');
60
+ expect(result.branch).toBe('main');
61
+ expect(result.path).toBe('src/index.ts');
62
+ });
63
+
64
+ it('should throw error for invalid URL', () => {
65
+ expect(() => client.parseUrl('https://invalid-url.com/test')).toThrow('Invalid GitLab URL');
66
+ });
67
+ });
68
+
69
+ describe('getRepoMetadata', () => {
70
+ it('should fetch repository metadata', async () => {
71
+ mockFetch.mockResolvedValueOnce({
72
+ ok: true,
73
+ json: async () => ({
74
+ id: 123,
75
+ name: 'test-repo',
76
+ description: 'A test repository',
77
+ default_branch: 'main',
78
+ star_count: 50,
79
+ forks_count: 10,
80
+ open_issues_count: 5,
81
+ created_at: '2024-01-01T00:00:00Z',
82
+ last_activity_at: '2024-06-01T00:00:00Z',
83
+ topics: ['test', 'mcp'],
84
+ license: { key: 'MIT', name: 'MIT License' }
85
+ })
86
+ });
87
+
88
+ const metadata = await client.getRepoMetadata('owner', 'repo');
89
+
90
+ expect(metadata.stars).toBe(50);
91
+ expect(metadata.defaultBranch).toBe('main');
92
+ expect(metadata.license).toBe('MIT');
93
+ expect(metadata.description).toBe('A test repository');
94
+ expect(metadata.forksCount).toBe(10);
95
+ expect(metadata.topics).toContain('mcp');
96
+ });
97
+
98
+ it('should throw error on API failure', async () => {
99
+ mockFetch.mockResolvedValueOnce({
100
+ ok: false,
101
+ status: 404,
102
+ statusText: 'Not Found'
103
+ });
104
+
105
+ await expect(client.getRepoMetadata('owner', 'nonexistent')).rejects.toThrow('GitLab API error');
106
+ });
107
+ });
108
+
109
+ describe('getFile', () => {
110
+ it('should fetch file content', async () => {
111
+ const content = 'Hello, World!';
112
+ const base64Content = Buffer.from(content).toString('base64');
113
+
114
+ mockFetch.mockResolvedValueOnce({
115
+ ok: true,
116
+ json: async () => ({
117
+ file_path: 'README.md',
118
+ encoding: 'base64',
119
+ content: base64Content,
120
+ blob_id: 'abc123'
121
+ })
122
+ });
123
+
124
+ const file = await client.getFile('owner', 'repo', 'README.md');
125
+
126
+ expect(file).not.toBeNull();
127
+ expect(file?.content).toBe(content);
128
+ expect(file?.path).toBe('README.md');
129
+ expect(file?.sha).toBe('abc123');
130
+ });
131
+
132
+ it('should return null for non-existent file', async () => {
133
+ mockFetch.mockResolvedValueOnce({
134
+ ok: false,
135
+ status: 404
136
+ });
137
+
138
+ const file = await client.getFile('owner', 'repo', 'nonexistent.md');
139
+
140
+ expect(file).toBeNull();
141
+ });
142
+ });
143
+
144
+ describe('getReadme', () => {
145
+ it('should fetch README content', async () => {
146
+ const content = '# Test Repo\n\nThis is a test.';
147
+ const base64Content = Buffer.from(content).toString('base64');
148
+
149
+ mockFetch.mockResolvedValueOnce({
150
+ ok: true,
151
+ json: async () => ({
152
+ file_path: 'README.md',
153
+ encoding: 'base64',
154
+ content: base64Content,
155
+ blob_id: 'abc123'
156
+ })
157
+ });
158
+
159
+ const readme = await client.getReadme('owner', 'repo');
160
+
161
+ expect(readme).toBe(content);
162
+ });
163
+
164
+ it('should try multiple README variants', async () => {
165
+ // First call fails (README.md)
166
+ mockFetch.mockResolvedValueOnce({ ok: false, status: 404 });
167
+ // Second call fails (README.MD)
168
+ mockFetch.mockResolvedValueOnce({ ok: false, status: 404 });
169
+ // Third call succeeds (readme.md)
170
+ mockFetch.mockResolvedValueOnce({
171
+ ok: true,
172
+ json: async () => ({
173
+ file_path: 'readme.md',
174
+ encoding: 'base64',
175
+ content: Buffer.from('Found!').toString('base64'),
176
+ blob_id: 'abc123'
177
+ })
178
+ });
179
+
180
+ const readme = await client.getReadme('owner', 'repo');
181
+
182
+ expect(readme).toBe('Found!');
183
+ expect(mockFetch).toHaveBeenCalledTimes(3);
184
+ });
185
+ });
186
+
187
+ describe('listFiles', () => {
188
+ it('should list directory contents', async () => {
189
+ mockFetch.mockResolvedValueOnce({
190
+ ok: true,
191
+ json: async () => [
192
+ { id: '1', name: 'src', type: 'tree', path: 'src', mode: '040000' },
193
+ { id: '2', name: 'README.md', type: 'blob', path: 'README.md', mode: '100644' }
194
+ ]
195
+ });
196
+
197
+ const files = await client.listFiles('owner', 'repo');
198
+
199
+ expect(files).toHaveLength(2);
200
+ expect(files[0].type).toBe('dir');
201
+ expect(files[1].type).toBe('file');
202
+ });
203
+
204
+ it('should return empty array on error', async () => {
205
+ mockFetch.mockResolvedValueOnce({ ok: false, status: 404 });
206
+
207
+ const files = await client.listFiles('owner', 'repo');
208
+
209
+ expect(files).toEqual([]);
210
+ });
211
+ });
212
+
213
+ describe('searchCode', () => {
214
+ it('should search code in repository', async () => {
215
+ mockFetch.mockResolvedValueOnce({
216
+ ok: true,
217
+ json: async () => [
218
+ {
219
+ basename: 'index',
220
+ data: 'function test() {}',
221
+ path: 'src/index.ts',
222
+ filename: 'index.ts',
223
+ id: 'abc123',
224
+ ref: 'main',
225
+ startline: 10,
226
+ project_id: 123
227
+ }
228
+ ]
229
+ });
230
+
231
+ const results = await client.searchCode('owner', 'repo', 'function');
232
+
233
+ expect(results).toHaveLength(1);
234
+ expect(results[0].path).toBe('src/index.ts');
235
+ expect(results[0].matchedLines?.[0].lineNumber).toBe(10);
236
+ });
237
+
238
+ it('should filter by extensions', async () => {
239
+ mockFetch.mockResolvedValueOnce({
240
+ ok: true,
241
+ json: async () => [
242
+ { path: 'src/index.ts', filename: 'index.ts', data: 'test', id: '1', startline: 1, project_id: 1 },
243
+ { path: 'src/style.css', filename: 'style.css', data: 'test', id: '2', startline: 1, project_id: 1 }
244
+ ]
245
+ });
246
+
247
+ const results = await client.searchCode('owner', 'repo', 'test', {
248
+ extensions: ['ts']
249
+ });
250
+
251
+ expect(results).toHaveLength(1);
252
+ expect(results[0].path).toBe('src/index.ts');
253
+ });
254
+ });
255
+
256
+ describe('findApiSpecs', () => {
257
+ it('should find OpenAPI specs', async () => {
258
+ const spec = { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' } };
259
+
260
+ // Mock multiple file lookups - first fails, second succeeds
261
+ mockFetch
262
+ .mockResolvedValueOnce({ ok: false, status: 404 }) // openapi.json
263
+ .mockResolvedValueOnce({ ok: false, status: 404 }) // openapi.yaml
264
+ .mockResolvedValueOnce({ ok: false, status: 404 }) // openapi.yml
265
+ .mockResolvedValueOnce({ // swagger.json - succeeds
266
+ ok: true,
267
+ json: async () => ({
268
+ file_path: 'swagger.json',
269
+ encoding: 'base64',
270
+ content: Buffer.from(JSON.stringify(spec)).toString('base64'),
271
+ blob_id: 'abc123'
272
+ })
273
+ });
274
+
275
+ const specs = await client.findApiSpecs('owner', 'repo');
276
+
277
+ // May find specs in swagger.json location
278
+ expect(Array.isArray(specs)).toBe(true);
279
+ });
280
+ });
281
+
282
+ describe('getRateLimit', () => {
283
+ it('should return rate limit info', async () => {
284
+ mockFetch.mockResolvedValueOnce({
285
+ ok: true,
286
+ headers: new Headers({
287
+ 'RateLimit-Remaining': '100',
288
+ 'RateLimit-Limit': '200',
289
+ 'RateLimit-Reset': String(Math.floor(Date.now() / 1000) + 3600)
290
+ }),
291
+ json: async () => []
292
+ });
293
+
294
+ const rateLimit = await client.getRateLimit();
295
+
296
+ expect(rateLimit.remaining).toBe(100);
297
+ expect(rateLimit.limit).toBe(200);
298
+ expect(rateLimit.reset).toBeInstanceOf(Date);
299
+ });
300
+ });
301
+ });
302
+
303
+ describe('createGitLabClient', () => {
304
+ it('should create a GitLabClient instance', () => {
305
+ const client = createGitLabClient({ token: 'test-token' });
306
+
307
+ expect(client).toBeInstanceOf(GitLabClient);
308
+ expect(client.name).toBe('gitlab');
309
+ });
310
+
311
+ it('should create client with custom base URL', () => {
312
+ const client = createGitLabClient({
313
+ token: 'test-token',
314
+ baseUrl: 'https://gitlab.example.com/api/v4'
315
+ });
316
+
317
+ expect(client).toBeInstanceOf(GitLabClient);
318
+ });
319
+ });