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.
- package/.env.example +8 -0
- package/.github/CODEOWNERS +6 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +7 -0
- package/.vscode/settings.json +4 -0
- package/ARCHITECTURE.md +1429 -0
- package/CHANGELOG.md +167 -0
- package/CONTRIBUTING.md +327 -0
- package/LICENSE +201 -0
- package/README.md +1028 -0
- package/SECURITY.md +248 -0
- package/VISUAL_GUIDE.md +437 -0
- package/apps/vscode/IMPLEMENTATION.md +480 -0
- package/apps/vscode/README.md +248 -0
- package/apps/vscode/package.json +381 -0
- package/apps/vscode/resources/icon.png +0 -0
- package/apps/vscode/resources/icon.svg +5 -0
- package/apps/vscode/src/commands/browseRegistry.ts +211 -0
- package/apps/vscode/src/commands/configureClaudeDesktop.ts +332 -0
- package/apps/vscode/src/commands/convert.ts +82 -0
- package/apps/vscode/src/commands/convertCurrentRepo.ts +109 -0
- package/apps/vscode/src/commands/convertFromUrl.ts +138 -0
- package/apps/vscode/src/commands/index.ts +121 -0
- package/apps/vscode/src/commands/validate.ts +197 -0
- package/apps/vscode/src/extension.ts +464 -0
- package/apps/vscode/src/global.d.ts +36 -0
- package/apps/vscode/src/test/extension.test.ts +73 -0
- package/apps/vscode/src/utils/file-generator.ts +529 -0
- package/apps/vscode/src/utils/github-api.ts +335 -0
- package/apps/vscode/src/utils/index.ts +29 -0
- package/apps/vscode/src/utils/mcp-config.ts +334 -0
- package/apps/vscode/src/utils/storage.ts +87 -0
- package/apps/vscode/src/views/McpServersTreeView.ts +160 -0
- package/apps/vscode/src/views/OutputChannelView.ts +195 -0
- package/apps/vscode/src/views/StatusBarItem.ts +251 -0
- package/apps/vscode/src/views/ToolsExplorerView.ts +314 -0
- package/apps/vscode/src/views/historyProvider.ts +75 -0
- package/apps/vscode/src/views/index.ts +12 -0
- package/apps/vscode/src/views/resultsPanel.ts +330 -0
- package/apps/vscode/src/webviews/ConversionPanel.ts +350 -0
- package/apps/vscode/src/webviews/ToolDetailsPanel.ts +448 -0
- package/apps/vscode/src/webviews/index.ts +9 -0
- package/apps/vscode/src/webviews/webview-ui/styles.ts +492 -0
- package/apps/vscode/tsconfig.json +20 -0
- package/apps/web/PLAYGROUND_GUIDE.md +499 -0
- package/apps/web/README.md +505 -0
- package/apps/web/app/api/convert/route.ts +100 -0
- package/apps/web/app/api/convert/stream/route.ts +198 -0
- package/apps/web/app/api/deploy/route.ts +157 -0
- package/apps/web/app/api/edge/route.ts +308 -0
- package/apps/web/app/api/export-docker/route.ts +284 -0
- package/apps/web/app/api/generate-openapi/route.ts +119 -0
- package/apps/web/app/api/mcp/[serverId]/route.ts +263 -0
- package/apps/web/app/api/playground/connect/route.ts +143 -0
- package/apps/web/app/api/playground/disconnect/route.ts +78 -0
- package/apps/web/app/api/playground/execute/route.ts +135 -0
- package/apps/web/app/api/playground/sessions/route.ts +103 -0
- package/apps/web/app/api/playground/tools/route.ts +117 -0
- package/apps/web/app/api/playground/v2/connect/route.ts +96 -0
- package/apps/web/app/api/playground/v2/disconnect/route.ts +88 -0
- package/apps/web/app/api/playground/v2/health/route.ts +80 -0
- package/apps/web/app/api/playground/v2/prompts/route.ts +160 -0
- package/apps/web/app/api/playground/v2/resources/route.ts +159 -0
- package/apps/web/app/api/playground/v2/sessions/route.ts +184 -0
- package/apps/web/app/api/playground/v2/tools/route.ts +167 -0
- package/apps/web/app/api/stream/route.ts +232 -0
- package/apps/web/app/batch/BatchConvertClient.tsx +190 -0
- package/apps/web/app/batch/page.tsx +37 -0
- package/apps/web/app/convert/page.tsx +269 -0
- package/apps/web/app/dashboard/page.tsx +380 -0
- package/apps/web/app/globals.css +622 -0
- package/apps/web/app/layout.tsx +120 -0
- package/apps/web/app/manifest.ts +31 -0
- package/apps/web/app/opengraph-image.tsx +112 -0
- package/apps/web/app/page.old.tsx +924 -0
- package/apps/web/app/page.tsx +77 -0
- package/apps/web/app/playground/page.tsx +306 -0
- package/apps/web/app/playground/v2/error.tsx +163 -0
- package/apps/web/app/playground/v2/layout.tsx +58 -0
- package/apps/web/app/playground/v2/loading.tsx +152 -0
- package/apps/web/app/playground/v2/page.tsx +644 -0
- package/apps/web/app/playground/v2/providers.tsx +214 -0
- package/apps/web/app/playground/v2/use-shortcuts.ts +209 -0
- package/apps/web/app/playground/v2/use-url-state.ts +296 -0
- package/apps/web/app/providers.tsx +22 -0
- package/apps/web/app/sitemap.ts +32 -0
- package/apps/web/app/twitter-image.tsx +112 -0
- package/apps/web/components/BranchSelector.tsx +401 -0
- package/apps/web/components/ClaudeConfigExport.tsx +226 -0
- package/apps/web/components/Features.tsx +84 -0
- package/apps/web/components/Footer.tsx +119 -0
- package/apps/web/components/GenerationProgress.tsx +248 -0
- package/apps/web/components/GithubUrlInput.tsx +483 -0
- package/apps/web/components/Header.tsx +175 -0
- package/apps/web/components/Hero.tsx +117 -0
- package/apps/web/components/HowItWorks.tsx +119 -0
- package/apps/web/components/InstallBanner.tsx +158 -0
- package/apps/web/components/Logo.tsx +116 -0
- package/apps/web/components/ParticleBackground.tsx +105 -0
- package/apps/web/components/Playground.tsx +472 -0
- package/apps/web/components/PlaygroundToolTester.tsx +410 -0
- package/apps/web/components/ProductCards.tsx +179 -0
- package/apps/web/components/SplitView.tsx +194 -0
- package/apps/web/components/ToolFilter.tsx +260 -0
- package/apps/web/components/ToolList.tsx +325 -0
- package/apps/web/components/batch/BatchConvert.tsx +785 -0
- package/apps/web/components/batch/index.ts +7 -0
- package/apps/web/components/convert/ConfigTabs.tsx +230 -0
- package/apps/web/components/convert/ConversionResult.tsx +482 -0
- package/apps/web/components/convert/InlinePlayground.tsx +259 -0
- package/apps/web/components/convert/LoadingSteps.tsx +311 -0
- package/apps/web/components/convert/OneClickInstall.tsx +224 -0
- package/apps/web/components/convert/ToolCard.tsx +189 -0
- package/apps/web/components/convert/TryInPlayground.tsx +242 -0
- package/apps/web/components/convert/index.ts +12 -0
- package/apps/web/components/deploy/DeployButton.tsx +369 -0
- package/apps/web/components/deploy/index.ts +7 -0
- package/apps/web/components/docker/DockerExport.tsx +690 -0
- package/apps/web/components/docker/index.ts +7 -0
- package/apps/web/components/install/OneClickInstall.tsx +676 -0
- package/apps/web/components/install/index.ts +7 -0
- package/apps/web/components/playground/CapabilityTabs.tsx +150 -0
- package/apps/web/components/playground/ConnectionStatusV2.tsx +322 -0
- package/apps/web/components/playground/EmptyStates.tsx +305 -0
- package/apps/web/components/playground/ExecutionLog.tsx +260 -0
- package/apps/web/components/playground/ExecutionLogV2.tsx +378 -0
- package/apps/web/components/playground/JsonViewer.tsx +388 -0
- package/apps/web/components/playground/PlaygroundLayout.tsx +244 -0
- package/apps/web/components/playground/PromptsPanel.tsx +385 -0
- package/apps/web/components/playground/ResourcesPanel.tsx +378 -0
- package/apps/web/components/playground/SchemaForm.tsx +477 -0
- package/apps/web/components/playground/ServerStatus.tsx +151 -0
- package/apps/web/components/playground/ShareButton.tsx +239 -0
- package/apps/web/components/playground/ToolsPanel.tsx +309 -0
- package/apps/web/components/playground/TransportConfigurator.tsx +563 -0
- package/apps/web/components/playground/index.ts +74 -0
- package/apps/web/components/playground/types.ts +202 -0
- package/apps/web/components/streaming/StreamingProgress.tsx +441 -0
- package/apps/web/components/streaming/index.ts +7 -0
- package/apps/web/components/ui/badge.tsx +42 -0
- package/apps/web/components/ui/button.tsx +88 -0
- package/apps/web/components/ui/card.tsx +75 -0
- package/apps/web/components/ui/code-block.tsx +122 -0
- package/apps/web/components/ui/index.ts +12 -0
- package/apps/web/components/ui/input.tsx +55 -0
- package/apps/web/components/ui/tabs.tsx +61 -0
- package/apps/web/hooks/index.ts +85 -0
- package/apps/web/hooks/types.ts +1173 -0
- package/apps/web/hooks/use-conversion.ts +133 -0
- package/apps/web/hooks/use-execution-history.ts +376 -0
- package/apps/web/hooks/use-generation-progress.ts +147 -0
- package/apps/web/hooks/use-local-storage.ts +88 -0
- package/apps/web/hooks/use-mcp-client.ts +623 -0
- package/apps/web/hooks/use-mcp-connection.ts +500 -0
- package/apps/web/hooks/use-mcp-execution.ts +282 -0
- package/apps/web/hooks/use-mcp-prompts.ts +441 -0
- package/apps/web/hooks/use-mcp-resources.ts +430 -0
- package/apps/web/hooks/use-mcp-tools.ts +540 -0
- package/apps/web/hooks/use-playground-store.ts +299 -0
- package/apps/web/hooks/use-playground.ts +184 -0
- package/apps/web/hooks/use-streaming-conversion.ts +227 -0
- package/apps/web/hooks/useBatchConversion.ts +271 -0
- package/apps/web/hooks/useDockerConfig.ts +161 -0
- package/apps/web/hooks/usePlatformDetection.ts +80 -0
- package/apps/web/hooks/useStreaming.ts +199 -0
- package/apps/web/lib/api/errors.ts +386 -0
- package/apps/web/lib/api/index.ts +137 -0
- package/apps/web/lib/api/logger.ts +187 -0
- package/apps/web/lib/api/middleware.ts +364 -0
- package/apps/web/lib/api/openapi.ts +977 -0
- package/apps/web/lib/api/session-manager.ts +594 -0
- package/apps/web/lib/api/types.ts +433 -0
- package/apps/web/lib/api/validation.ts +523 -0
- package/apps/web/lib/constants.ts +114 -0
- package/apps/web/lib/mcp/client.ts +1137 -0
- package/apps/web/lib/mcp/events.ts +651 -0
- package/apps/web/lib/mcp/index.ts +347 -0
- package/apps/web/lib/mcp/logger.ts +428 -0
- package/apps/web/lib/mcp/metrics.ts +703 -0
- package/apps/web/lib/mcp/retry.ts +616 -0
- package/apps/web/lib/mcp/session-manager.ts +779 -0
- package/apps/web/lib/mcp/transports.ts +988 -0
- package/apps/web/lib/mcp/types.ts +594 -0
- package/apps/web/lib/mcp-client-enhanced.ts +871 -0
- package/apps/web/lib/mcp-client.ts +778 -0
- package/apps/web/lib/mcp-errors.ts +489 -0
- package/apps/web/lib/mcp-sandbox.ts +593 -0
- package/apps/web/lib/mcp-testing.ts +428 -0
- package/apps/web/lib/mcp-types.ts +448 -0
- package/apps/web/lib/playground-store.tsx +1147 -0
- package/apps/web/lib/utils.ts +439 -0
- package/apps/web/next-env.d.ts +5 -0
- package/apps/web/next.config.js +23 -0
- package/apps/web/package.json +55 -0
- package/apps/web/postcss.config.js +6 -0
- package/apps/web/public/.well-known/ai-plugin.json +17 -0
- package/apps/web/public/logo.svg +6 -0
- package/apps/web/public/robots.txt +22 -0
- package/apps/web/public/schema.json +27 -0
- package/apps/web/tailwind.config.js +26 -0
- package/apps/web/tailwind.config.ts +123 -0
- package/apps/web/tsconfig.json +20 -0
- package/apps/web/types/deploy.ts +139 -0
- package/apps/web/types/index.ts +247 -0
- package/apps/web/vercel.json +39 -0
- package/eslint.config.mjs +23 -0
- package/llms.txt +102 -0
- package/mkdocs/docs/api/core.md +318 -0
- package/mkdocs/docs/api/index.md +128 -0
- package/mkdocs/docs/api/mcp-server.md +301 -0
- package/mkdocs/docs/api/openapi-parser.md +254 -0
- package/mkdocs/docs/assets/logo.svg +7 -0
- package/mkdocs/docs/changelog.md +118 -0
- package/mkdocs/docs/cli/generate.md +148 -0
- package/mkdocs/docs/cli/index.md +52 -0
- package/mkdocs/docs/cli/inspect.md +164 -0
- package/mkdocs/docs/cli/serve.md +136 -0
- package/mkdocs/docs/concepts/classification.md +254 -0
- package/mkdocs/docs/concepts/how-it-works.md +299 -0
- package/mkdocs/docs/concepts/index.md +77 -0
- package/mkdocs/docs/concepts/mcp-protocol.md +362 -0
- package/mkdocs/docs/concepts/tool-types.md +382 -0
- package/mkdocs/docs/contributing/architecture.md +262 -0
- package/mkdocs/docs/contributing/development.md +245 -0
- package/mkdocs/docs/contributing/index.md +73 -0
- package/mkdocs/docs/contributing/testing.md +320 -0
- package/mkdocs/docs/getting-started/configuration.md +235 -0
- package/mkdocs/docs/getting-started/index.md +54 -0
- package/mkdocs/docs/getting-started/installation.md +145 -0
- package/mkdocs/docs/getting-started/quickstart.md +160 -0
- package/mkdocs/docs/guides/batch.md +375 -0
- package/mkdocs/docs/guides/claude-desktop.md +227 -0
- package/mkdocs/docs/guides/cursor.md +188 -0
- package/mkdocs/docs/guides/custom-tools.md +367 -0
- package/mkdocs/docs/guides/index.md +78 -0
- package/mkdocs/docs/guides/private-repos.md +221 -0
- package/mkdocs/docs/guides/vscode.md +247 -0
- package/mkdocs/docs/index.md +175 -0
- package/mkdocs/docs/reference/config.md +223 -0
- package/mkdocs/docs/reference/env.md +192 -0
- package/mkdocs/docs/reference/index.md +102 -0
- package/mkdocs/docs/reference/tools.md +309 -0
- package/mkdocs/docs/stylesheets/extra.css +231 -0
- package/mkdocs/mkdocs.yml +204 -0
- package/mkdocs/overrides/.gitkeep +1 -0
- package/mkdocs/overrides/main.html +7 -0
- package/mkdocs/python-deps.txt +7 -0
- package/mkdocs/vercel.json +11 -0
- package/package.json +63 -0
- package/packages/core/package.json +61 -0
- package/packages/core/src/__tests__/bitbucket-client.test.ts +366 -0
- package/packages/core/src/__tests__/cli.test.ts +235 -0
- package/packages/core/src/__tests__/code-extractor.test.ts +378 -0
- package/packages/core/src/__tests__/docker-generator.test.ts +255 -0
- package/packages/core/src/__tests__/github-client.test.ts +390 -0
- package/packages/core/src/__tests__/gitlab-client.test.ts +319 -0
- package/packages/core/src/__tests__/go-extractor.test.ts +351 -0
- package/packages/core/src/__tests__/graphql-extractor.test.ts +330 -0
- package/packages/core/src/__tests__/java-extractor.test.ts +497 -0
- package/packages/core/src/__tests__/plugins.test.ts +467 -0
- package/packages/core/src/__tests__/readme-extractor.test.ts +258 -0
- package/packages/core/src/__tests__/redis-cache.test.ts +307 -0
- package/packages/core/src/__tests__/rust-extractor.test.ts +252 -0
- package/packages/core/src/__tests__/streaming.test.ts +251 -0
- package/packages/core/src/additional-extractors.ts +333 -0
- package/packages/core/src/cache/cache-interface.ts +179 -0
- package/packages/core/src/cache/index.ts +210 -0
- package/packages/core/src/cache/redis-cache.ts +291 -0
- package/packages/core/src/cache/upstash-cache.ts +379 -0
- package/packages/core/src/cache.ts +251 -0
- package/packages/core/src/cli.ts +822 -0
- package/packages/core/src/code-extractor.ts +696 -0
- package/packages/core/src/docker-generator.ts +470 -0
- package/packages/core/src/edge-compatible.ts +491 -0
- package/packages/core/src/extractors/go-extractor.ts +791 -0
- package/packages/core/src/extractors/index.ts +9 -0
- package/packages/core/src/extractors/java-extractor.ts +937 -0
- package/packages/core/src/extractors/rust-extractor.ts +744 -0
- package/packages/core/src/github-client.ts +319 -0
- package/packages/core/src/go-generator.ts +356 -0
- package/packages/core/src/graphql-extractor.ts +358 -0
- package/packages/core/src/index.ts +797 -0
- package/packages/core/src/langchain-exporter.ts +617 -0
- package/packages/core/src/language-parsers.ts +1114 -0
- package/packages/core/src/mcp-introspector.ts +279 -0
- package/packages/core/src/monorepo-detector.ts +378 -0
- package/packages/core/src/plugins/index.ts +370 -0
- package/packages/core/src/plugins/registry.ts +404 -0
- package/packages/core/src/plugins/types.ts +215 -0
- package/packages/core/src/providers/base-provider.ts +246 -0
- package/packages/core/src/providers/bitbucket-client.ts +464 -0
- package/packages/core/src/providers/gitlab-client.ts +388 -0
- package/packages/core/src/providers/index.ts +176 -0
- package/packages/core/src/python-generator.ts +260 -0
- package/packages/core/src/queue/index.ts +100 -0
- package/packages/core/src/queue/memory-queue.ts +445 -0
- package/packages/core/src/queue/redis-queue.ts +578 -0
- package/packages/core/src/queue/types.ts +251 -0
- package/packages/core/src/readme-extractor.ts +409 -0
- package/packages/core/src/schema-generator.ts +638 -0
- package/packages/core/src/streaming.ts +999 -0
- package/packages/core/src/types.ts +289 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/core/tsup.config.ts +25 -0
- package/packages/mcp-server/README.md +297 -0
- package/packages/mcp-server/package.json +55 -0
- package/packages/mcp-server/src/__tests__/mcp-server.test.ts +177 -0
- package/packages/mcp-server/src/__tests__/tools.test.ts +217 -0
- package/packages/mcp-server/src/index.ts +1206 -0
- package/packages/mcp-server/src/prompts/index.ts +601 -0
- package/packages/mcp-server/src/tools/export-docker.ts +362 -0
- package/packages/mcp-server/src/tools/generate-openapi.ts +162 -0
- package/packages/mcp-server/src/tools/monitor-mcp-server.ts +448 -0
- package/packages/mcp-server/src/tools/stream-convert.ts +398 -0
- package/packages/mcp-server/src/tools/test-mcp-tool.ts +531 -0
- package/packages/mcp-server/tsconfig.json +12 -0
- package/packages/mcp-server/tsup.config.ts +14 -0
- package/packages/openapi-parser/package-lock.json +3028 -0
- package/packages/openapi-parser/package.json +41 -0
- package/packages/openapi-parser/src/analyzer.ts +700 -0
- package/packages/openapi-parser/src/asyncapi-parser.ts +475 -0
- package/packages/openapi-parser/src/cli.ts +302 -0
- package/packages/openapi-parser/src/generator.ts +570 -0
- package/packages/openapi-parser/src/generators/express-analyzer.ts +649 -0
- package/packages/openapi-parser/src/generators/fastapi-analyzer.ts +960 -0
- package/packages/openapi-parser/src/generators/index.ts +200 -0
- package/packages/openapi-parser/src/generators/nextjs-analyzer.ts +768 -0
- package/packages/openapi-parser/src/generators/openapi-builder.ts +527 -0
- package/packages/openapi-parser/src/generators/types.ts +298 -0
- package/packages/openapi-parser/src/graphql-parser.ts +462 -0
- package/packages/openapi-parser/src/grpc-parser.ts +649 -0
- package/packages/openapi-parser/src/har-parser.ts +723 -0
- package/packages/openapi-parser/src/index.ts +635 -0
- package/packages/openapi-parser/src/insomnia-parser.ts +614 -0
- package/packages/openapi-parser/src/parser.ts +231 -0
- package/packages/openapi-parser/src/postman-parser.ts +611 -0
- package/packages/openapi-parser/src/ref-resolver.ts +313 -0
- package/packages/openapi-parser/src/transformer.ts +459 -0
- package/packages/openapi-parser/tests/generators/express.test.ts +209 -0
- package/packages/openapi-parser/tests/generators/fastapi.test.ts +236 -0
- package/packages/openapi-parser/tests/generators/nextjs.test.ts +273 -0
- package/packages/openapi-parser/tests/parsers.test.ts +847 -0
- package/packages/openapi-parser/tsconfig.json +9 -0
- package/packages/openapi-parser/tsup.config.ts +11 -0
- package/packages/registry/package.json +59 -0
- package/packages/registry/src/cli.ts +456 -0
- package/packages/registry/src/index.ts +44 -0
- package/packages/registry/src/popular/github.json +47 -0
- package/packages/registry/src/popular/index.ts +55 -0
- package/packages/registry/src/popular/linear.json +42 -0
- package/packages/registry/src/popular/notion.json +42 -0
- package/packages/registry/src/popular/openai.json +40 -0
- package/packages/registry/src/popular/resend.json +38 -0
- package/packages/registry/src/popular/slack.json +42 -0
- package/packages/registry/src/popular/stripe.json +163 -0
- package/packages/registry/src/popular/supabase.json +42 -0
- package/packages/registry/src/popular/twilio.json +40 -0
- package/packages/registry/src/popular/vercel.json +40 -0
- package/packages/registry/src/registry.ts +492 -0
- package/packages/registry/src/storage.ts +334 -0
- package/packages/registry/src/types.ts +275 -0
- package/packages/registry/src/updater.ts +208 -0
- package/packages/registry/tsconfig.json +10 -0
- package/packages/registry/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +3 -0
- package/scripts/build-docs.sh +16 -0
- package/server.json +9 -0
- package/templates/Dockerfile.python.template +60 -0
- package/templates/Dockerfile.typescript.template +60 -0
- package/templates/docker-compose.template.yml +68 -0
- package/tests/fixtures/express-app/index.js +34 -0
- package/tests/fixtures/express-app/routes/posts.js +43 -0
- package/tests/fixtures/express-app/routes/users.js +58 -0
- package/tests/fixtures/fastapi-app/main.py +125 -0
- package/tests/fixtures/fastapi-app/routes/admin.py +42 -0
- package/tests/fixtures/graphql/simple-schema.graphql +65 -0
- package/tests/fixtures/mocks/github-api-responses.json +63 -0
- package/tests/fixtures/nextjs-app/app/api/posts/route.ts +55 -0
- package/tests/fixtures/nextjs-app/app/api/users/[id]/route.ts +63 -0
- package/tests/fixtures/nextjs-app/app/api/users/route.ts +44 -0
- package/tests/fixtures/nextjs-app/pages/api/health.ts +28 -0
- package/tests/fixtures/openapi/petstore.yaml +179 -0
- package/tests/integration/langchain-export.test.ts +405 -0
- package/tests/integration/openapi-conversion.test.ts +221 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +32 -0
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Next.js API route analyzer
|
|
3
|
+
* Parses Next.js API routes from app/api and pages/api directories
|
|
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 Next.js route detection
|
|
23
|
+
*/
|
|
24
|
+
const ROUTE_PATTERNS = {
|
|
25
|
+
// App Router: export async function GET/POST/PUT/PATCH/DELETE
|
|
26
|
+
appRouterHandler: /export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*\(/gi,
|
|
27
|
+
|
|
28
|
+
// App Router: export const GET/POST = async (req) =>
|
|
29
|
+
appRouterArrowHandler: /export\s+const\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s*=\s*(?:async\s*)?\(/gi,
|
|
30
|
+
|
|
31
|
+
// Pages Router: export default function handler
|
|
32
|
+
pagesRouterDefault: /export\s+default\s+(?:async\s+)?function\s+(\w+)?\s*\(/gi,
|
|
33
|
+
|
|
34
|
+
// Pages Router: handler with switch on method
|
|
35
|
+
methodSwitch: /(?:req|request)\.method\s*===?\s*['"](\w+)['"]/gi,
|
|
36
|
+
|
|
37
|
+
// NextRequest/NextResponse imports
|
|
38
|
+
nextImports: /import\s+{[^}]*(?:NextRequest|NextResponse)[^}]*}\s+from\s+['"]next\/server['"]/gi,
|
|
39
|
+
|
|
40
|
+
// Route segment config
|
|
41
|
+
routeConfig: /export\s+const\s+(dynamic|runtime|revalidate|fetchCache|preferredRegion)\s*=/gi,
|
|
42
|
+
|
|
43
|
+
// Zod schema
|
|
44
|
+
zodSchema: /(?:const|let|var)\s+(\w+)Schema?\s*=\s*z\.object\s*\((\{[\s\S]*?\})\)/gi,
|
|
45
|
+
|
|
46
|
+
// Request body parsing
|
|
47
|
+
bodyParsing: /(?:await\s+)?(?:req|request)\.json\s*\(\)/gi,
|
|
48
|
+
|
|
49
|
+
// URL search params
|
|
50
|
+
searchParams: /(?:searchParams|url\.searchParams)\.get\s*\(\s*['"](\w+)['"]\)/gi,
|
|
51
|
+
|
|
52
|
+
// Dynamic route params
|
|
53
|
+
dynamicParams: /params\.(\w+)/gi,
|
|
54
|
+
|
|
55
|
+
// Response with status
|
|
56
|
+
responseStatus: /(?:NextResponse\.json|Response\.json)\s*\([^,]+,\s*\{\s*status:\s*(\d+)/gi,
|
|
57
|
+
|
|
58
|
+
// TypeScript interface
|
|
59
|
+
tsInterface: /interface\s+(\w+)\s*{([^}]+)}/gi,
|
|
60
|
+
|
|
61
|
+
// Type annotation
|
|
62
|
+
typeAnnotation: /:\s*(\w+)(?:<[^>]+>)?/g,
|
|
63
|
+
|
|
64
|
+
// JSDoc comment
|
|
65
|
+
jsdocComment: /\/\*\*[\s\S]*?\*\//g,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Next.js API route analyzer implementation
|
|
70
|
+
*/
|
|
71
|
+
export class NextJSAnalyzer implements RouteAnalyzer {
|
|
72
|
+
name = 'nextjs';
|
|
73
|
+
|
|
74
|
+
private schemas: Record<string, JsonSchemaDefinition> = {};
|
|
75
|
+
private warnings: string[] = [];
|
|
76
|
+
private errors: string[] = [];
|
|
77
|
+
private routerType: 'app' | 'pages' | 'unknown' = 'unknown';
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if files contain Next.js API routes
|
|
81
|
+
*/
|
|
82
|
+
canAnalyze(files: FileContent[]): boolean {
|
|
83
|
+
return files.some(file => {
|
|
84
|
+
const content = file.content;
|
|
85
|
+
const path = file.path.toLowerCase();
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
// App Router
|
|
89
|
+
(path.includes('/app/') && path.includes('/api/') && path.includes('route.')) ||
|
|
90
|
+
// Pages Router
|
|
91
|
+
(path.includes('/pages/api/')) ||
|
|
92
|
+
// Next.js imports
|
|
93
|
+
content.includes("from 'next/server'") ||
|
|
94
|
+
content.includes('from "next/server"') ||
|
|
95
|
+
content.includes('NextApiRequest') ||
|
|
96
|
+
content.includes('NextRequest')
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Analyze Next.js files and extract routes
|
|
103
|
+
*/
|
|
104
|
+
async analyze(files: FileContent[]): Promise<AnalysisResult> {
|
|
105
|
+
this.schemas = {};
|
|
106
|
+
this.warnings = [];
|
|
107
|
+
this.errors = [];
|
|
108
|
+
|
|
109
|
+
const routes: AnalyzedRoute[] = [];
|
|
110
|
+
const filesAnalyzed: string[] = [];
|
|
111
|
+
|
|
112
|
+
// Detect router type
|
|
113
|
+
this.detectRouterType(files);
|
|
114
|
+
|
|
115
|
+
// First pass: extract schemas
|
|
116
|
+
for (const file of files) {
|
|
117
|
+
this.extractSchemas(file);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Second pass: extract routes
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
if (this.isApiRouteFile(file)) {
|
|
123
|
+
filesAnalyzed.push(file.path);
|
|
124
|
+
const fileRoutes = this.extractRoutes(file);
|
|
125
|
+
routes.push(...fileRoutes);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
routes,
|
|
131
|
+
schemas: this.schemas,
|
|
132
|
+
securitySchemes: this.detectSecuritySchemes(files),
|
|
133
|
+
warnings: this.warnings,
|
|
134
|
+
errors: this.errors,
|
|
135
|
+
framework: 'nextjs',
|
|
136
|
+
filesAnalyzed,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Detect whether it's App Router or Pages Router
|
|
142
|
+
*/
|
|
143
|
+
private detectRouterType(files: FileContent[]): void {
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
const path = file.path.toLowerCase();
|
|
146
|
+
|
|
147
|
+
if (path.includes('/app/') && path.includes('route.')) {
|
|
148
|
+
this.routerType = 'app';
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (path.includes('/pages/api/')) {
|
|
152
|
+
this.routerType = 'pages';
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check if a file is an API route file
|
|
160
|
+
*/
|
|
161
|
+
private isApiRouteFile(file: FileContent): boolean {
|
|
162
|
+
const path = file.path.toLowerCase();
|
|
163
|
+
|
|
164
|
+
// App Router: route.ts/js in app/api
|
|
165
|
+
if (path.includes('/api/') && /route\.(ts|js|tsx|jsx)$/.test(path)) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Pages Router: any file in pages/api
|
|
170
|
+
if (path.includes('/pages/api/') && /\.(ts|js|tsx|jsx)$/.test(path)) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Extract routes from a file
|
|
179
|
+
*/
|
|
180
|
+
private extractRoutes(file: FileContent): AnalyzedRoute[] {
|
|
181
|
+
const path = file.path.toLowerCase();
|
|
182
|
+
|
|
183
|
+
if (path.includes('/app/') && path.includes('route.')) {
|
|
184
|
+
return this.extractAppRouterRoutes(file);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return this.extractPagesRouterRoutes(file);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Extract routes from App Router file
|
|
192
|
+
*/
|
|
193
|
+
private extractAppRouterRoutes(file: FileContent): AnalyzedRoute[] {
|
|
194
|
+
const routes: AnalyzedRoute[] = [];
|
|
195
|
+
const content = file.content;
|
|
196
|
+
const apiPath = this.getApiPathFromFilePath(file.path, 'app');
|
|
197
|
+
|
|
198
|
+
// Find all exported HTTP method handlers
|
|
199
|
+
ROUTE_PATTERNS.appRouterHandler.lastIndex = 0;
|
|
200
|
+
ROUTE_PATTERNS.appRouterArrowHandler.lastIndex = 0;
|
|
201
|
+
|
|
202
|
+
let match;
|
|
203
|
+
|
|
204
|
+
// Match function declarations
|
|
205
|
+
while ((match = ROUTE_PATTERNS.appRouterHandler.exec(content)) !== null) {
|
|
206
|
+
const method = match[1].toLowerCase() as HttpMethod;
|
|
207
|
+
const line = this.getLineNumber(content, match.index);
|
|
208
|
+
const jsDoc = this.findPrecedingJsDoc(content, match.index);
|
|
209
|
+
|
|
210
|
+
routes.push(this.createRoute(file, method, apiPath, line, jsDoc, content));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Match arrow function exports
|
|
214
|
+
while ((match = ROUTE_PATTERNS.appRouterArrowHandler.exec(content)) !== null) {
|
|
215
|
+
const method = match[1].toLowerCase() as HttpMethod;
|
|
216
|
+
const line = this.getLineNumber(content, match.index);
|
|
217
|
+
const jsDoc = this.findPrecedingJsDoc(content, match.index);
|
|
218
|
+
|
|
219
|
+
routes.push(this.createRoute(file, method, apiPath, line, jsDoc, content));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return routes;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Extract routes from Pages Router file
|
|
227
|
+
*/
|
|
228
|
+
private extractPagesRouterRoutes(file: FileContent): AnalyzedRoute[] {
|
|
229
|
+
const routes: AnalyzedRoute[] = [];
|
|
230
|
+
const content = file.content;
|
|
231
|
+
const apiPath = this.getApiPathFromFilePath(file.path, 'pages');
|
|
232
|
+
|
|
233
|
+
// Check for method switch pattern
|
|
234
|
+
ROUTE_PATTERNS.methodSwitch.lastIndex = 0;
|
|
235
|
+
const methods = new Set<HttpMethod>();
|
|
236
|
+
|
|
237
|
+
let match;
|
|
238
|
+
while ((match = ROUTE_PATTERNS.methodSwitch.exec(content)) !== null) {
|
|
239
|
+
methods.add(match[1].toLowerCase() as HttpMethod);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// If no specific methods found, assume it handles all common methods
|
|
243
|
+
if (methods.size === 0) {
|
|
244
|
+
// Check if there's a default export (handler)
|
|
245
|
+
if (/export\s+default/.test(content)) {
|
|
246
|
+
methods.add('get');
|
|
247
|
+
methods.add('post');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const jsDoc = this.findPrecedingJsDoc(content, 0);
|
|
252
|
+
|
|
253
|
+
for (const method of methods) {
|
|
254
|
+
routes.push(this.createRoute(file, method, apiPath, 1, jsDoc, content));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return routes;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get API path from file path
|
|
262
|
+
*/
|
|
263
|
+
private getApiPathFromFilePath(filePath: string, routerType: 'app' | 'pages'): string {
|
|
264
|
+
let path = filePath;
|
|
265
|
+
|
|
266
|
+
// Normalize path
|
|
267
|
+
path = path.replace(/\\/g, '/');
|
|
268
|
+
|
|
269
|
+
if (routerType === 'app') {
|
|
270
|
+
// Extract path from app/api/... /route.ts
|
|
271
|
+
const match = path.match(/\/app(\/api\/[^/]+(?:\/[^/]+)*?)\/route\.[^/]+$/i);
|
|
272
|
+
if (match) {
|
|
273
|
+
path = match[1];
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
// Extract path from pages/api/...
|
|
277
|
+
const match = path.match(/\/pages(\/api\/[^/]+(?:\/[^/]+)*?)\.[^/]+$/i);
|
|
278
|
+
if (match) {
|
|
279
|
+
path = match[1];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Convert dynamic segments
|
|
284
|
+
// [param] -> {param}
|
|
285
|
+
// [[...slug]] -> {slug}
|
|
286
|
+
// [...slug] -> {slug}
|
|
287
|
+
path = path
|
|
288
|
+
.replace(/\[\[\.\.\.(\w+)\]\]/g, '{$1}')
|
|
289
|
+
.replace(/\[\.\.\.(\w+)\]/g, '{$1}')
|
|
290
|
+
.replace(/\[(\w+)\]/g, '{$1}');
|
|
291
|
+
|
|
292
|
+
return path;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Create an analyzed route
|
|
297
|
+
*/
|
|
298
|
+
private createRoute(
|
|
299
|
+
file: FileContent,
|
|
300
|
+
method: HttpMethod,
|
|
301
|
+
path: string,
|
|
302
|
+
line: number,
|
|
303
|
+
jsDoc: string | undefined,
|
|
304
|
+
content: string
|
|
305
|
+
): AnalyzedRoute {
|
|
306
|
+
const { summary, description, tags, deprecated } = this.parseJsDoc(jsDoc);
|
|
307
|
+
const pathParams = this.extractPathParameters(path);
|
|
308
|
+
const queryParams = this.extractQueryParameters(content);
|
|
309
|
+
const responses = this.extractResponses(content, method);
|
|
310
|
+
|
|
311
|
+
const route: AnalyzedRoute = {
|
|
312
|
+
method,
|
|
313
|
+
path,
|
|
314
|
+
openApiPath: path,
|
|
315
|
+
operationId: this.generateOperationId(method, path),
|
|
316
|
+
summary,
|
|
317
|
+
description,
|
|
318
|
+
tags: tags.length > 0 ? tags : this.inferTags(path),
|
|
319
|
+
pathParameters: pathParams,
|
|
320
|
+
queryParameters: queryParams,
|
|
321
|
+
headerParameters: this.extractHeaderParameters(content),
|
|
322
|
+
responses: responses.length > 0 ? responses : this.inferResponses(method),
|
|
323
|
+
deprecated,
|
|
324
|
+
sourceFile: file.path,
|
|
325
|
+
sourceLine: line,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// Add request body for mutation methods
|
|
329
|
+
if (['post', 'put', 'patch'].includes(method)) {
|
|
330
|
+
route.requestBody = this.extractRequestBody(content);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return route;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Extract path parameters from path
|
|
338
|
+
*/
|
|
339
|
+
private extractPathParameters(path: string): RouteParameter[] {
|
|
340
|
+
const params: RouteParameter[] = [];
|
|
341
|
+
const matches = path.matchAll(/\{(\w+)\}/g);
|
|
342
|
+
|
|
343
|
+
for (const match of matches) {
|
|
344
|
+
params.push({
|
|
345
|
+
name: match[1],
|
|
346
|
+
location: 'path',
|
|
347
|
+
required: true,
|
|
348
|
+
type: 'string',
|
|
349
|
+
description: `Path parameter: ${match[1]}`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return params;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Extract query parameters from code
|
|
358
|
+
*/
|
|
359
|
+
private extractQueryParameters(content: string): RouteParameter[] {
|
|
360
|
+
const params: RouteParameter[] = [];
|
|
361
|
+
const paramNames = new Set<string>();
|
|
362
|
+
|
|
363
|
+
// Find searchParams.get() calls
|
|
364
|
+
ROUTE_PATTERNS.searchParams.lastIndex = 0;
|
|
365
|
+
let match;
|
|
366
|
+
|
|
367
|
+
while ((match = ROUTE_PATTERNS.searchParams.exec(content)) !== null) {
|
|
368
|
+
const name = match[1];
|
|
369
|
+
if (!paramNames.has(name)) {
|
|
370
|
+
paramNames.add(name);
|
|
371
|
+
params.push({
|
|
372
|
+
name,
|
|
373
|
+
location: 'query',
|
|
374
|
+
required: false,
|
|
375
|
+
type: 'string',
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return params;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Extract header parameters from code
|
|
385
|
+
*/
|
|
386
|
+
private extractHeaderParameters(content: string): RouteParameter[] {
|
|
387
|
+
const params: RouteParameter[] = [];
|
|
388
|
+
|
|
389
|
+
// Look for header access patterns
|
|
390
|
+
const headerPatterns = [
|
|
391
|
+
/headers\.get\s*\(\s*['"]([^'"]+)['"]\)/gi,
|
|
392
|
+
/request\.headers\.get\s*\(\s*['"]([^'"]+)['"]\)/gi,
|
|
393
|
+
];
|
|
394
|
+
|
|
395
|
+
const headerNames = new Set<string>();
|
|
396
|
+
|
|
397
|
+
for (const pattern of headerPatterns) {
|
|
398
|
+
pattern.lastIndex = 0;
|
|
399
|
+
let match;
|
|
400
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
401
|
+
const name = match[1].toLowerCase();
|
|
402
|
+
// Skip common headers that shouldn't be in the spec
|
|
403
|
+
if (!['content-type', 'content-length', 'host'].includes(name) && !headerNames.has(name)) {
|
|
404
|
+
headerNames.add(name);
|
|
405
|
+
params.push({
|
|
406
|
+
name: match[1],
|
|
407
|
+
location: 'header',
|
|
408
|
+
required: false,
|
|
409
|
+
type: 'string',
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return params;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Extract request body schema from code
|
|
420
|
+
*/
|
|
421
|
+
private extractRequestBody(content: string): RouteBody {
|
|
422
|
+
// Look for Zod schema usage
|
|
423
|
+
const zodMatch = content.match(/(\w+)Schema\.parse\s*\(/);
|
|
424
|
+
if (zodMatch && this.schemas[zodMatch[1]]) {
|
|
425
|
+
return {
|
|
426
|
+
contentType: 'application/json',
|
|
427
|
+
required: true,
|
|
428
|
+
schema: { $ref: `#/components/schemas/${zodMatch[1]}` },
|
|
429
|
+
description: 'Request body',
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Look for TypeScript type assertion
|
|
434
|
+
const typeMatch = content.match(/await\s+(?:req|request)\.json\s*\(\)\s*(?:as\s+(\w+))?/);
|
|
435
|
+
if (typeMatch && typeMatch[1] && this.schemas[typeMatch[1]]) {
|
|
436
|
+
return {
|
|
437
|
+
contentType: 'application/json',
|
|
438
|
+
required: true,
|
|
439
|
+
schema: { $ref: `#/components/schemas/${typeMatch[1]}` },
|
|
440
|
+
description: 'Request body',
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Default request body
|
|
445
|
+
return {
|
|
446
|
+
contentType: 'application/json',
|
|
447
|
+
required: true,
|
|
448
|
+
schema: { type: 'object' },
|
|
449
|
+
description: 'Request body',
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Extract responses from code
|
|
455
|
+
*/
|
|
456
|
+
private extractResponses(content: string, method: HttpMethod): RouteResponse[] {
|
|
457
|
+
const responses: RouteResponse[] = [];
|
|
458
|
+
const statusCodes = new Set<number>();
|
|
459
|
+
|
|
460
|
+
// Find NextResponse.json with status
|
|
461
|
+
ROUTE_PATTERNS.responseStatus.lastIndex = 0;
|
|
462
|
+
let match;
|
|
463
|
+
|
|
464
|
+
while ((match = ROUTE_PATTERNS.responseStatus.exec(content)) !== null) {
|
|
465
|
+
const status = parseInt(match[1], 10);
|
|
466
|
+
if (!statusCodes.has(status)) {
|
|
467
|
+
statusCodes.add(status);
|
|
468
|
+
responses.push({
|
|
469
|
+
statusCode: status,
|
|
470
|
+
description: this.getStatusDescription(status),
|
|
471
|
+
contentType: 'application/json',
|
|
472
|
+
schema: { type: 'object' },
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// If no explicit status codes found, add defaults
|
|
478
|
+
if (responses.length === 0) {
|
|
479
|
+
return this.inferResponses(method);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return responses;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Infer responses based on method
|
|
487
|
+
*/
|
|
488
|
+
private inferResponses(method: HttpMethod): RouteResponse[] {
|
|
489
|
+
const responses: RouteResponse[] = [];
|
|
490
|
+
|
|
491
|
+
switch (method) {
|
|
492
|
+
case 'get':
|
|
493
|
+
responses.push({
|
|
494
|
+
statusCode: 200,
|
|
495
|
+
description: 'Successful response',
|
|
496
|
+
contentType: 'application/json',
|
|
497
|
+
schema: { type: 'object' },
|
|
498
|
+
});
|
|
499
|
+
break;
|
|
500
|
+
case 'post':
|
|
501
|
+
responses.push({
|
|
502
|
+
statusCode: 201,
|
|
503
|
+
description: 'Resource created',
|
|
504
|
+
contentType: 'application/json',
|
|
505
|
+
schema: { type: 'object' },
|
|
506
|
+
});
|
|
507
|
+
break;
|
|
508
|
+
case 'put':
|
|
509
|
+
case 'patch':
|
|
510
|
+
responses.push({
|
|
511
|
+
statusCode: 200,
|
|
512
|
+
description: 'Resource updated',
|
|
513
|
+
contentType: 'application/json',
|
|
514
|
+
schema: { type: 'object' },
|
|
515
|
+
});
|
|
516
|
+
break;
|
|
517
|
+
case 'delete':
|
|
518
|
+
responses.push({
|
|
519
|
+
statusCode: 204,
|
|
520
|
+
description: 'Resource deleted',
|
|
521
|
+
});
|
|
522
|
+
break;
|
|
523
|
+
default:
|
|
524
|
+
responses.push({
|
|
525
|
+
statusCode: 200,
|
|
526
|
+
description: 'Successful response',
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Add common error responses
|
|
531
|
+
responses.push({
|
|
532
|
+
statusCode: 400,
|
|
533
|
+
description: 'Bad request',
|
|
534
|
+
contentType: 'application/json',
|
|
535
|
+
schema: {
|
|
536
|
+
type: 'object',
|
|
537
|
+
properties: {
|
|
538
|
+
error: { type: 'string' },
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
responses.push({
|
|
543
|
+
statusCode: 500,
|
|
544
|
+
description: 'Internal server error',
|
|
545
|
+
contentType: 'application/json',
|
|
546
|
+
schema: {
|
|
547
|
+
type: 'object',
|
|
548
|
+
properties: {
|
|
549
|
+
error: { type: 'string' },
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
return responses;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Get status description
|
|
559
|
+
*/
|
|
560
|
+
private getStatusDescription(status: number): string {
|
|
561
|
+
const descriptions: Record<number, string> = {
|
|
562
|
+
200: 'Successful response',
|
|
563
|
+
201: 'Resource created',
|
|
564
|
+
204: 'No content',
|
|
565
|
+
400: 'Bad request',
|
|
566
|
+
401: 'Unauthorized',
|
|
567
|
+
403: 'Forbidden',
|
|
568
|
+
404: 'Not found',
|
|
569
|
+
405: 'Method not allowed',
|
|
570
|
+
422: 'Unprocessable entity',
|
|
571
|
+
429: 'Too many requests',
|
|
572
|
+
500: 'Internal server error',
|
|
573
|
+
};
|
|
574
|
+
return descriptions[status] || `HTTP ${status}`;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Parse JSDoc comment
|
|
579
|
+
*/
|
|
580
|
+
private parseJsDoc(jsDoc?: string): {
|
|
581
|
+
summary?: string;
|
|
582
|
+
description?: string;
|
|
583
|
+
tags: string[];
|
|
584
|
+
deprecated: boolean;
|
|
585
|
+
} {
|
|
586
|
+
const result = {
|
|
587
|
+
summary: undefined as string | undefined,
|
|
588
|
+
description: undefined as string | undefined,
|
|
589
|
+
tags: [] as string[],
|
|
590
|
+
deprecated: false,
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
if (!jsDoc) return result;
|
|
594
|
+
|
|
595
|
+
// Extract summary (first line)
|
|
596
|
+
const summaryMatch = jsDoc.match(/\/\*\*\s*\n?\s*\*\s*([^\n@]+)/);
|
|
597
|
+
if (summaryMatch) {
|
|
598
|
+
result.summary = summaryMatch[1].trim();
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Extract @tag
|
|
602
|
+
const tagMatches = jsDoc.matchAll(/@tag\s+(\w+)/gi);
|
|
603
|
+
for (const match of tagMatches) {
|
|
604
|
+
result.tags.push(match[1]);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Check for @deprecated
|
|
608
|
+
if (jsDoc.includes('@deprecated')) {
|
|
609
|
+
result.deprecated = true;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return result;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Find preceding JSDoc comment
|
|
617
|
+
*/
|
|
618
|
+
private findPrecedingJsDoc(content: string, position: number): string | undefined {
|
|
619
|
+
const before = content.slice(0, position);
|
|
620
|
+
const match = before.match(/\/\*\*[\s\S]*?\*\/\s*$/);
|
|
621
|
+
return match ? match[0] : undefined;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Extract schemas from file
|
|
626
|
+
*/
|
|
627
|
+
private extractSchemas(file: FileContent): void {
|
|
628
|
+
const content = file.content;
|
|
629
|
+
|
|
630
|
+
// Extract TypeScript interfaces
|
|
631
|
+
ROUTE_PATTERNS.tsInterface.lastIndex = 0;
|
|
632
|
+
let match;
|
|
633
|
+
|
|
634
|
+
while ((match = ROUTE_PATTERNS.tsInterface.exec(content)) !== null) {
|
|
635
|
+
const name = match[1];
|
|
636
|
+
const body = match[2];
|
|
637
|
+
this.schemas[name] = this.parseInterfaceBody(body);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Extract Zod schemas
|
|
641
|
+
ROUTE_PATTERNS.zodSchema.lastIndex = 0;
|
|
642
|
+
while ((match = ROUTE_PATTERNS.zodSchema.exec(content)) !== null) {
|
|
643
|
+
const name = match[1];
|
|
644
|
+
// Simple Zod parsing
|
|
645
|
+
this.schemas[name] = { type: 'object' };
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Parse TypeScript interface body to JSON Schema
|
|
651
|
+
*/
|
|
652
|
+
private parseInterfaceBody(body: string): JsonSchemaDefinition {
|
|
653
|
+
const properties: Record<string, JsonSchemaDefinition> = {};
|
|
654
|
+
const required: string[] = [];
|
|
655
|
+
|
|
656
|
+
const propMatches = body.matchAll(/(\w+)(\?)?:\s*([^;]+);?/g);
|
|
657
|
+
for (const match of propMatches) {
|
|
658
|
+
const propName = match[1];
|
|
659
|
+
const isOptional = !!match[2];
|
|
660
|
+
const propType = match[3].trim();
|
|
661
|
+
|
|
662
|
+
properties[propName] = this.tsTypeToJsonSchema(propType);
|
|
663
|
+
if (!isOptional) {
|
|
664
|
+
required.push(propName);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return {
|
|
669
|
+
type: 'object',
|
|
670
|
+
properties,
|
|
671
|
+
required: required.length > 0 ? required : undefined,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Convert TypeScript type to JSON Schema
|
|
677
|
+
*/
|
|
678
|
+
private tsTypeToJsonSchema(tsType: string): JsonSchemaDefinition {
|
|
679
|
+
const type = tsType.toLowerCase().trim();
|
|
680
|
+
|
|
681
|
+
if (type === 'string') return { type: 'string' };
|
|
682
|
+
if (type === 'number') return { type: 'number' };
|
|
683
|
+
if (type === 'boolean') return { type: 'boolean' };
|
|
684
|
+
if (type.endsWith('[]')) {
|
|
685
|
+
const itemType = type.slice(0, -2);
|
|
686
|
+
return { type: 'array', items: this.tsTypeToJsonSchema(itemType) };
|
|
687
|
+
}
|
|
688
|
+
if (type === 'any' || type === 'unknown') return { type: 'object' };
|
|
689
|
+
|
|
690
|
+
// Check if it's a reference to a known schema
|
|
691
|
+
const originalType = tsType.trim();
|
|
692
|
+
if (this.schemas[originalType]) {
|
|
693
|
+
return { $ref: `#/components/schemas/${originalType}` };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return { type: 'object' };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Generate operation ID
|
|
701
|
+
*/
|
|
702
|
+
private generateOperationId(method: HttpMethod, path: string): string {
|
|
703
|
+
const cleanPath = path
|
|
704
|
+
.replace(/\/api\//g, '_')
|
|
705
|
+
.replace(/[/{}]/g, '_')
|
|
706
|
+
.replace(/_+/g, '_')
|
|
707
|
+
.replace(/^_|_$/g, '');
|
|
708
|
+
return `${method}_${cleanPath || 'root'}`;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Infer tags from path
|
|
713
|
+
*/
|
|
714
|
+
private inferTags(path: string): string[] {
|
|
715
|
+
const parts = path.split('/').filter(p => p && p !== 'api' && !p.startsWith('{'));
|
|
716
|
+
if (parts.length > 0) {
|
|
717
|
+
return [parts[0].charAt(0).toUpperCase() + parts[0].slice(1)];
|
|
718
|
+
}
|
|
719
|
+
return ['API'];
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Get line number from character index
|
|
724
|
+
*/
|
|
725
|
+
private getLineNumber(content: string, index: number): number {
|
|
726
|
+
return content.slice(0, index).split('\n').length;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Detect security schemes
|
|
731
|
+
*/
|
|
732
|
+
private detectSecuritySchemes(files: FileContent[]): Record<string, OpenAPIV3_1.SecuritySchemeObject> {
|
|
733
|
+
const schemes: Record<string, OpenAPIV3_1.SecuritySchemeObject> = {};
|
|
734
|
+
|
|
735
|
+
for (const file of files) {
|
|
736
|
+
const content = file.content;
|
|
737
|
+
|
|
738
|
+
// Check for authorization header usage
|
|
739
|
+
if (content.includes('authorization') || content.includes('Authorization')) {
|
|
740
|
+
schemes.bearerAuth = {
|
|
741
|
+
type: 'http',
|
|
742
|
+
scheme: 'bearer',
|
|
743
|
+
bearerFormat: 'JWT',
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Check for API key usage
|
|
748
|
+
if (content.includes('x-api-key') || content.includes('X-API-Key')) {
|
|
749
|
+
schemes.apiKey = {
|
|
750
|
+
type: 'apiKey',
|
|
751
|
+
in: 'header',
|
|
752
|
+
name: 'X-API-Key',
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Check for NextAuth
|
|
757
|
+
if (content.includes('next-auth') || content.includes('getServerSession')) {
|
|
758
|
+
schemes.session = {
|
|
759
|
+
type: 'apiKey',
|
|
760
|
+
in: 'cookie',
|
|
761
|
+
name: 'next-auth.session-token',
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return schemes;
|
|
767
|
+
}
|
|
768
|
+
}
|