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,649 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Express.js route analyzer
|
|
3
|
+
* Parses Express.js routes and extracts API information
|
|
4
|
+
* @copyright Copyright (c) 2024-2026 nirholas
|
|
5
|
+
* @license MIT
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
RouteAnalyzer,
|
|
10
|
+
FileContent,
|
|
11
|
+
AnalysisResult,
|
|
12
|
+
AnalyzedRoute,
|
|
13
|
+
RouteParameter,
|
|
14
|
+
RouteBody,
|
|
15
|
+
RouteResponse,
|
|
16
|
+
JsonSchemaDefinition,
|
|
17
|
+
HttpMethod,
|
|
18
|
+
ParameterLocation,
|
|
19
|
+
} from './types.js';
|
|
20
|
+
import { OpenAPIV3_1 } from 'openapi-types';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Regular expressions for Express.js route detection
|
|
24
|
+
*/
|
|
25
|
+
const ROUTE_PATTERNS = {
|
|
26
|
+
// app.get('/path', handler)
|
|
27
|
+
appMethod: /(?:app|router)\s*\.\s*(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
28
|
+
|
|
29
|
+
// router.route('/path').get(handler).post(handler)
|
|
30
|
+
routeChain: /\.route\s*\(\s*['"`]([^'"`]+)['"`]\s*\)((?:\s*\.\s*(?:get|post|put|patch|delete|head|options)\s*\([^)]*\))+)/gi,
|
|
31
|
+
|
|
32
|
+
// Router declaration
|
|
33
|
+
routerDeclaration: /(?:const|let|var)\s+(\w+)\s*=\s*(?:express\.)?Router\s*\(\)/gi,
|
|
34
|
+
|
|
35
|
+
// Middleware with path
|
|
36
|
+
useWithPath: /(?:app|router)\s*\.\s*use\s*\(\s*['"`]([^'"`]+)['"`]/gi,
|
|
37
|
+
|
|
38
|
+
// Path parameters :param
|
|
39
|
+
pathParam: /:(\w+)(?:\([^)]*\))?/g,
|
|
40
|
+
|
|
41
|
+
// Query parameters from comments or validation
|
|
42
|
+
queryParam: /@query\s+{(\w+)}\s+(\w+)(?:\s+-\s+(.+))?/gi,
|
|
43
|
+
|
|
44
|
+
// Body schema from comments
|
|
45
|
+
bodySchema: /@body\s+{(\w+)}\s+(.+)/gi,
|
|
46
|
+
|
|
47
|
+
// Response from comments
|
|
48
|
+
responseSchema: /@response\s+{(\d+)}\s+(.+)/gi,
|
|
49
|
+
|
|
50
|
+
// JSDoc comment block
|
|
51
|
+
jsdocComment: /\/\*\*[\s\S]*?\*\//g,
|
|
52
|
+
|
|
53
|
+
// TypeScript interface
|
|
54
|
+
tsInterface: /interface\s+(\w+)\s*{([^}]+)}/gi,
|
|
55
|
+
|
|
56
|
+
// Zod schema
|
|
57
|
+
zodSchema: /(?:const|let|var)\s+(\w+)\s*=\s*z\.object\s*\((\{[\s\S]*?\})\)/gi,
|
|
58
|
+
|
|
59
|
+
// Express validator
|
|
60
|
+
expressValidator: /(?:body|query|param)\s*\(\s*['"`](\w+)['"`]\s*\)(?:\s*\.\s*(\w+)\s*\([^)]*\))*/gi,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Express.js route analyzer implementation
|
|
65
|
+
*/
|
|
66
|
+
export class ExpressAnalyzer implements RouteAnalyzer {
|
|
67
|
+
name = 'express';
|
|
68
|
+
|
|
69
|
+
private schemas: Record<string, JsonSchemaDefinition> = {};
|
|
70
|
+
private warnings: string[] = [];
|
|
71
|
+
private errors: string[] = [];
|
|
72
|
+
private basePaths: Map<string, string> = new Map();
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if files contain Express.js code
|
|
76
|
+
*/
|
|
77
|
+
canAnalyze(files: FileContent[]): boolean {
|
|
78
|
+
return files.some(file => {
|
|
79
|
+
const content = file.content;
|
|
80
|
+
return (
|
|
81
|
+
content.includes("require('express')") ||
|
|
82
|
+
content.includes('require("express")') ||
|
|
83
|
+
content.includes("from 'express'") ||
|
|
84
|
+
content.includes('from "express"') ||
|
|
85
|
+
/(?:app|router)\s*\.\s*(get|post|put|patch|delete)\s*\(/.test(content)
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Analyze Express.js files and extract routes
|
|
92
|
+
*/
|
|
93
|
+
async analyze(files: FileContent[]): Promise<AnalysisResult> {
|
|
94
|
+
this.schemas = {};
|
|
95
|
+
this.warnings = [];
|
|
96
|
+
this.errors = [];
|
|
97
|
+
this.basePaths.clear();
|
|
98
|
+
|
|
99
|
+
const routes: AnalyzedRoute[] = [];
|
|
100
|
+
const filesAnalyzed: string[] = [];
|
|
101
|
+
|
|
102
|
+
// First pass: extract schemas and base paths
|
|
103
|
+
for (const file of files) {
|
|
104
|
+
this.extractSchemas(file);
|
|
105
|
+
this.extractBasePaths(file);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Second pass: extract routes
|
|
109
|
+
for (const file of files) {
|
|
110
|
+
if (this.isExpressFile(file)) {
|
|
111
|
+
filesAnalyzed.push(file.path);
|
|
112
|
+
const fileRoutes = this.extractRoutes(file);
|
|
113
|
+
routes.push(...fileRoutes);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
routes,
|
|
119
|
+
schemas: this.schemas,
|
|
120
|
+
securitySchemes: this.detectSecuritySchemes(files),
|
|
121
|
+
warnings: this.warnings,
|
|
122
|
+
errors: this.errors,
|
|
123
|
+
framework: 'express',
|
|
124
|
+
filesAnalyzed,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if a file contains Express.js routes
|
|
130
|
+
*/
|
|
131
|
+
private isExpressFile(file: FileContent): boolean {
|
|
132
|
+
const content = file.content;
|
|
133
|
+
return (
|
|
134
|
+
/(?:app|router)\s*\.\s*(get|post|put|patch|delete|head|options|use)\s*\(/.test(content) ||
|
|
135
|
+
/\.route\s*\(/.test(content)
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Extract base paths from router mounting
|
|
141
|
+
*/
|
|
142
|
+
private extractBasePaths(file: FileContent): void {
|
|
143
|
+
const content = file.content;
|
|
144
|
+
const useMatches = content.matchAll(ROUTE_PATTERNS.useWithPath);
|
|
145
|
+
|
|
146
|
+
for (const match of useMatches) {
|
|
147
|
+
const path = match[1];
|
|
148
|
+
// Try to find the router being mounted
|
|
149
|
+
const beforeUse = content.slice(0, match.index);
|
|
150
|
+
const routerMatch = beforeUse.match(/(\w+)\s*=\s*require\s*\(['"`][^'"`]+['"`]\)/);
|
|
151
|
+
if (routerMatch) {
|
|
152
|
+
this.basePaths.set(routerMatch[1], path);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Extract routes from a file
|
|
159
|
+
*/
|
|
160
|
+
private extractRoutes(file: FileContent): AnalyzedRoute[] {
|
|
161
|
+
const routes: AnalyzedRoute[] = [];
|
|
162
|
+
const content = file.content;
|
|
163
|
+
const lines = content.split('\n');
|
|
164
|
+
|
|
165
|
+
// Extract routes using app.method() pattern
|
|
166
|
+
const appMethodMatches = content.matchAll(ROUTE_PATTERNS.appMethod);
|
|
167
|
+
for (const match of appMethodMatches) {
|
|
168
|
+
const method = match[1].toLowerCase() as HttpMethod;
|
|
169
|
+
const path = match[2];
|
|
170
|
+
const line = this.getLineNumber(content, match.index!);
|
|
171
|
+
|
|
172
|
+
const jsDoc = this.findPrecedingJsDoc(lines, line);
|
|
173
|
+
const route = this.createRoute(file, method, path, line, jsDoc);
|
|
174
|
+
routes.push(route);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Extract routes using router.route().method() pattern
|
|
178
|
+
const routeChainMatches = content.matchAll(ROUTE_PATTERNS.routeChain);
|
|
179
|
+
for (const match of routeChainMatches) {
|
|
180
|
+
const path = match[1];
|
|
181
|
+
const chainedMethods = match[2];
|
|
182
|
+
const line = this.getLineNumber(content, match.index!);
|
|
183
|
+
|
|
184
|
+
// Extract each method from the chain
|
|
185
|
+
const methodMatches = chainedMethods.matchAll(/\.(get|post|put|patch|delete|head|options)\s*\(/gi);
|
|
186
|
+
for (const methodMatch of methodMatches) {
|
|
187
|
+
const method = methodMatch[1].toLowerCase() as HttpMethod;
|
|
188
|
+
const jsDoc = this.findPrecedingJsDoc(lines, line);
|
|
189
|
+
const route = this.createRoute(file, method, path, line, jsDoc);
|
|
190
|
+
routes.push(route);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return routes;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Create an analyzed route
|
|
199
|
+
*/
|
|
200
|
+
private createRoute(
|
|
201
|
+
file: FileContent,
|
|
202
|
+
method: HttpMethod,
|
|
203
|
+
path: string,
|
|
204
|
+
line: number,
|
|
205
|
+
jsDoc?: string
|
|
206
|
+
): AnalyzedRoute {
|
|
207
|
+
const openApiPath = this.convertToOpenApiPath(path);
|
|
208
|
+
const pathParams = this.extractPathParameters(path);
|
|
209
|
+
const { summary, description, tags, queryParams, bodySchema, responses, deprecated, operationId } =
|
|
210
|
+
this.parseJsDoc(jsDoc);
|
|
211
|
+
|
|
212
|
+
// Merge path parameters with any from JSDoc
|
|
213
|
+
const allQueryParams = this.mergeQueryParameters(queryParams, file.content, path);
|
|
214
|
+
|
|
215
|
+
const route: AnalyzedRoute = {
|
|
216
|
+
method,
|
|
217
|
+
path,
|
|
218
|
+
openApiPath,
|
|
219
|
+
operationId: operationId || this.generateOperationId(method, path),
|
|
220
|
+
summary,
|
|
221
|
+
description,
|
|
222
|
+
tags: tags.length > 0 ? tags : this.inferTags(path),
|
|
223
|
+
pathParameters: pathParams,
|
|
224
|
+
queryParameters: allQueryParams,
|
|
225
|
+
headerParameters: this.extractHeaderParameters(file.content, path),
|
|
226
|
+
responses: responses.length > 0 ? responses : this.inferResponses(method),
|
|
227
|
+
deprecated,
|
|
228
|
+
sourceFile: file.path,
|
|
229
|
+
sourceLine: line,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Add request body for methods that typically have one
|
|
233
|
+
if (['post', 'put', 'patch'].includes(method)) {
|
|
234
|
+
route.requestBody = bodySchema || this.inferRequestBody(file.content, path);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return route;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Convert Express path to OpenAPI path format
|
|
242
|
+
*/
|
|
243
|
+
private convertToOpenApiPath(path: string): string {
|
|
244
|
+
// Convert :param to {param}
|
|
245
|
+
return path.replace(/:(\w+)(?:\([^)]*\))?/g, '{$1}');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Extract path parameters from Express path
|
|
250
|
+
*/
|
|
251
|
+
private extractPathParameters(path: string): RouteParameter[] {
|
|
252
|
+
const params: RouteParameter[] = [];
|
|
253
|
+
const matches = path.matchAll(ROUTE_PATTERNS.pathParam);
|
|
254
|
+
|
|
255
|
+
for (const match of matches) {
|
|
256
|
+
params.push({
|
|
257
|
+
name: match[1],
|
|
258
|
+
location: 'path',
|
|
259
|
+
required: true,
|
|
260
|
+
type: 'string',
|
|
261
|
+
description: `Path parameter: ${match[1]}`,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return params;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Parse JSDoc comment for route metadata
|
|
270
|
+
*/
|
|
271
|
+
private parseJsDoc(jsDoc?: string): {
|
|
272
|
+
summary?: string;
|
|
273
|
+
description?: string;
|
|
274
|
+
tags: string[];
|
|
275
|
+
queryParams: RouteParameter[];
|
|
276
|
+
bodySchema?: RouteBody;
|
|
277
|
+
responses: RouteResponse[];
|
|
278
|
+
deprecated: boolean;
|
|
279
|
+
operationId?: string;
|
|
280
|
+
} {
|
|
281
|
+
const result = {
|
|
282
|
+
summary: undefined as string | undefined,
|
|
283
|
+
description: undefined as string | undefined,
|
|
284
|
+
tags: [] as string[],
|
|
285
|
+
queryParams: [] as RouteParameter[],
|
|
286
|
+
bodySchema: undefined as RouteBody | undefined,
|
|
287
|
+
responses: [] as RouteResponse[],
|
|
288
|
+
deprecated: false,
|
|
289
|
+
operationId: undefined as string | undefined,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
if (!jsDoc) return result;
|
|
293
|
+
|
|
294
|
+
// Extract summary (first line after /**)
|
|
295
|
+
const summaryMatch = jsDoc.match(/\/\*\*\s*\n?\s*\*\s*([^\n@]+)/);
|
|
296
|
+
if (summaryMatch) {
|
|
297
|
+
result.summary = summaryMatch[1].trim();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Extract description (text before @tags)
|
|
301
|
+
const descMatch = jsDoc.match(/\/\*\*[\s\S]*?\*\s+([^@]+?)(?=\s*@|\s*\*\/)/);
|
|
302
|
+
if (descMatch) {
|
|
303
|
+
result.description = descMatch[1]
|
|
304
|
+
.replace(/^\s*\*\s*/gm, '')
|
|
305
|
+
.trim();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Extract @tag annotations
|
|
309
|
+
const tagMatches = jsDoc.matchAll(/@tag\s+(\w+)/gi);
|
|
310
|
+
for (const match of tagMatches) {
|
|
311
|
+
result.tags.push(match[1]);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Extract @param for query parameters
|
|
315
|
+
const paramMatches = jsDoc.matchAll(/@param\s+{(\w+)}\s+(?:req\.query\.)?(\w+)(?:\s+-\s+(.+))?/gi);
|
|
316
|
+
for (const match of paramMatches) {
|
|
317
|
+
result.queryParams.push({
|
|
318
|
+
name: match[2],
|
|
319
|
+
location: 'query',
|
|
320
|
+
required: !match[0].includes('?'),
|
|
321
|
+
type: this.mapJsDocType(match[1]),
|
|
322
|
+
description: match[3]?.trim(),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Extract @deprecated
|
|
327
|
+
if (jsDoc.includes('@deprecated')) {
|
|
328
|
+
result.deprecated = true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Extract @operationId
|
|
332
|
+
const opIdMatch = jsDoc.match(/@operationId\s+(\w+)/i);
|
|
333
|
+
if (opIdMatch) {
|
|
334
|
+
result.operationId = opIdMatch[1];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Extract @returns for response
|
|
338
|
+
const returnsMatch = jsDoc.match(/@returns?\s+{(\w+)}\s*(.+)?/i);
|
|
339
|
+
if (returnsMatch) {
|
|
340
|
+
result.responses.push({
|
|
341
|
+
statusCode: 200,
|
|
342
|
+
description: returnsMatch[2]?.trim() || 'Successful response',
|
|
343
|
+
contentType: 'application/json',
|
|
344
|
+
schema: { type: this.mapJsDocType(returnsMatch[1]) },
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Map JSDoc type to JSON Schema type
|
|
353
|
+
*/
|
|
354
|
+
private mapJsDocType(jsDocType: string): 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' {
|
|
355
|
+
const typeMap: Record<string, 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object'> = {
|
|
356
|
+
string: 'string',
|
|
357
|
+
number: 'number',
|
|
358
|
+
int: 'integer',
|
|
359
|
+
integer: 'integer',
|
|
360
|
+
boolean: 'boolean',
|
|
361
|
+
bool: 'boolean',
|
|
362
|
+
array: 'array',
|
|
363
|
+
object: 'object',
|
|
364
|
+
any: 'object',
|
|
365
|
+
};
|
|
366
|
+
return typeMap[jsDocType.toLowerCase()] || 'string';
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Generate operation ID from method and path
|
|
371
|
+
*/
|
|
372
|
+
private generateOperationId(method: HttpMethod, path: string): string {
|
|
373
|
+
const cleanPath = path
|
|
374
|
+
.replace(/[/:{}]/g, '_')
|
|
375
|
+
.replace(/_+/g, '_')
|
|
376
|
+
.replace(/^_|_$/g, '');
|
|
377
|
+
return `${method}_${cleanPath || 'root'}`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Infer tags from path
|
|
382
|
+
*/
|
|
383
|
+
private inferTags(path: string): string[] {
|
|
384
|
+
const parts = path.split('/').filter(p => p && !p.startsWith(':') && !p.startsWith('{'));
|
|
385
|
+
if (parts.length > 0) {
|
|
386
|
+
// Use first path segment as tag
|
|
387
|
+
return [parts[0].charAt(0).toUpperCase() + parts[0].slice(1)];
|
|
388
|
+
}
|
|
389
|
+
return ['Default'];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Infer default responses based on method
|
|
394
|
+
*/
|
|
395
|
+
private inferResponses(method: HttpMethod): RouteResponse[] {
|
|
396
|
+
const responses: RouteResponse[] = [];
|
|
397
|
+
|
|
398
|
+
switch (method) {
|
|
399
|
+
case 'get':
|
|
400
|
+
responses.push({
|
|
401
|
+
statusCode: 200,
|
|
402
|
+
description: 'Successful response',
|
|
403
|
+
contentType: 'application/json',
|
|
404
|
+
});
|
|
405
|
+
break;
|
|
406
|
+
case 'post':
|
|
407
|
+
responses.push({
|
|
408
|
+
statusCode: 201,
|
|
409
|
+
description: 'Resource created',
|
|
410
|
+
contentType: 'application/json',
|
|
411
|
+
});
|
|
412
|
+
break;
|
|
413
|
+
case 'put':
|
|
414
|
+
case 'patch':
|
|
415
|
+
responses.push({
|
|
416
|
+
statusCode: 200,
|
|
417
|
+
description: 'Resource updated',
|
|
418
|
+
contentType: 'application/json',
|
|
419
|
+
});
|
|
420
|
+
break;
|
|
421
|
+
case 'delete':
|
|
422
|
+
responses.push({
|
|
423
|
+
statusCode: 204,
|
|
424
|
+
description: 'Resource deleted',
|
|
425
|
+
});
|
|
426
|
+
break;
|
|
427
|
+
default:
|
|
428
|
+
responses.push({
|
|
429
|
+
statusCode: 200,
|
|
430
|
+
description: 'Successful response',
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Add common error responses
|
|
435
|
+
responses.push({
|
|
436
|
+
statusCode: 400,
|
|
437
|
+
description: 'Bad request',
|
|
438
|
+
contentType: 'application/json',
|
|
439
|
+
});
|
|
440
|
+
responses.push({
|
|
441
|
+
statusCode: 500,
|
|
442
|
+
description: 'Internal server error',
|
|
443
|
+
contentType: 'application/json',
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
return responses;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Extract schemas from TypeScript interfaces and Zod schemas
|
|
451
|
+
*/
|
|
452
|
+
private extractSchemas(file: FileContent): void {
|
|
453
|
+
const content = file.content;
|
|
454
|
+
|
|
455
|
+
// Extract TypeScript interfaces
|
|
456
|
+
const interfaceMatches = content.matchAll(ROUTE_PATTERNS.tsInterface);
|
|
457
|
+
for (const match of interfaceMatches) {
|
|
458
|
+
const name = match[1];
|
|
459
|
+
const body = match[2];
|
|
460
|
+
this.schemas[name] = this.parseInterfaceBody(body);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Extract Zod schemas
|
|
464
|
+
const zodMatches = content.matchAll(ROUTE_PATTERNS.zodSchema);
|
|
465
|
+
for (const match of zodMatches) {
|
|
466
|
+
const name = match[1];
|
|
467
|
+
// Simple Zod parsing - would need more sophisticated parsing in production
|
|
468
|
+
this.schemas[name] = { type: 'object' };
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Parse TypeScript interface body to JSON Schema
|
|
474
|
+
*/
|
|
475
|
+
private parseInterfaceBody(body: string): JsonSchemaDefinition {
|
|
476
|
+
const properties: Record<string, JsonSchemaDefinition> = {};
|
|
477
|
+
const required: string[] = [];
|
|
478
|
+
|
|
479
|
+
const propMatches = body.matchAll(/(\w+)(\?)?:\s*([^;]+);/g);
|
|
480
|
+
for (const match of propMatches) {
|
|
481
|
+
const propName = match[1];
|
|
482
|
+
const isOptional = !!match[2];
|
|
483
|
+
const propType = match[3].trim();
|
|
484
|
+
|
|
485
|
+
properties[propName] = this.tsTypeToJsonSchema(propType);
|
|
486
|
+
if (!isOptional) {
|
|
487
|
+
required.push(propName);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
type: 'object',
|
|
493
|
+
properties,
|
|
494
|
+
required: required.length > 0 ? required : undefined,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Convert TypeScript type to JSON Schema
|
|
500
|
+
*/
|
|
501
|
+
private tsTypeToJsonSchema(tsType: string): JsonSchemaDefinition {
|
|
502
|
+
const type = tsType.toLowerCase().trim();
|
|
503
|
+
|
|
504
|
+
if (type === 'string') return { type: 'string' };
|
|
505
|
+
if (type === 'number') return { type: 'number' };
|
|
506
|
+
if (type === 'boolean') return { type: 'boolean' };
|
|
507
|
+
if (type.endsWith('[]')) {
|
|
508
|
+
const itemType = type.slice(0, -2);
|
|
509
|
+
return { type: 'array', items: this.tsTypeToJsonSchema(itemType) };
|
|
510
|
+
}
|
|
511
|
+
if (type === 'any' || type === 'unknown') return { type: 'object' };
|
|
512
|
+
|
|
513
|
+
// Check if it's a reference to a known schema
|
|
514
|
+
if (this.schemas[tsType]) {
|
|
515
|
+
return { $ref: `#/components/schemas/${tsType}` };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return { type: 'object' };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Find JSDoc comment preceding a line
|
|
523
|
+
*/
|
|
524
|
+
private findPrecedingJsDoc(lines: string[], lineNumber: number): string | undefined {
|
|
525
|
+
let jsDocEnd = -1;
|
|
526
|
+
|
|
527
|
+
// Search backwards for JSDoc
|
|
528
|
+
for (let i = lineNumber - 2; i >= 0; i--) {
|
|
529
|
+
const line = lines[i].trim();
|
|
530
|
+
if (line.endsWith('*/')) {
|
|
531
|
+
jsDocEnd = i;
|
|
532
|
+
}
|
|
533
|
+
if (line.startsWith('/**') && jsDocEnd !== -1) {
|
|
534
|
+
return lines.slice(i, jsDocEnd + 1).join('\n');
|
|
535
|
+
}
|
|
536
|
+
// Stop searching if we hit a non-comment line before finding start
|
|
537
|
+
if (jsDocEnd === -1 && line && !line.startsWith('//') && !line.startsWith('*')) {
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Get line number from character index
|
|
547
|
+
*/
|
|
548
|
+
private getLineNumber(content: string, index: number): number {
|
|
549
|
+
return content.slice(0, index).split('\n').length;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Merge query parameters from multiple sources
|
|
554
|
+
*/
|
|
555
|
+
private mergeQueryParameters(
|
|
556
|
+
fromJsDoc: RouteParameter[],
|
|
557
|
+
content: string,
|
|
558
|
+
_path: string
|
|
559
|
+
): RouteParameter[] {
|
|
560
|
+
const params = [...fromJsDoc];
|
|
561
|
+
const existingNames = new Set(params.map(p => p.name));
|
|
562
|
+
|
|
563
|
+
// Look for express-validator usage
|
|
564
|
+
const validatorMatches = content.matchAll(ROUTE_PATTERNS.expressValidator);
|
|
565
|
+
for (const match of validatorMatches) {
|
|
566
|
+
const name = match[1];
|
|
567
|
+
if (!existingNames.has(name)) {
|
|
568
|
+
params.push({
|
|
569
|
+
name,
|
|
570
|
+
location: 'query',
|
|
571
|
+
required: false,
|
|
572
|
+
type: 'string',
|
|
573
|
+
});
|
|
574
|
+
existingNames.add(name);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return params;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Extract header parameters from code
|
|
583
|
+
*/
|
|
584
|
+
private extractHeaderParameters(_content: string, _path: string): RouteParameter[] {
|
|
585
|
+
// Look for common header access patterns
|
|
586
|
+
const params: RouteParameter[] = [];
|
|
587
|
+
|
|
588
|
+
// Always include authorization header if auth middleware is detected
|
|
589
|
+
// This is a simplified detection
|
|
590
|
+
return params;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Infer request body from code
|
|
595
|
+
*/
|
|
596
|
+
private inferRequestBody(_content: string, _path: string): RouteBody | undefined {
|
|
597
|
+
// Default request body for mutation endpoints
|
|
598
|
+
return {
|
|
599
|
+
contentType: 'application/json',
|
|
600
|
+
required: true,
|
|
601
|
+
schema: { type: 'object' },
|
|
602
|
+
description: 'Request body',
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Detect security schemes from code
|
|
608
|
+
*/
|
|
609
|
+
private detectSecuritySchemes(files: FileContent[]): Record<string, OpenAPIV3_1.SecuritySchemeObject> {
|
|
610
|
+
const schemes: Record<string, OpenAPIV3_1.SecuritySchemeObject> = {};
|
|
611
|
+
|
|
612
|
+
for (const file of files) {
|
|
613
|
+
const content = file.content;
|
|
614
|
+
|
|
615
|
+
// Check for JWT/Bearer auth
|
|
616
|
+
if (
|
|
617
|
+
content.includes('passport-jwt') ||
|
|
618
|
+
content.includes('jsonwebtoken') ||
|
|
619
|
+
content.includes('Bearer') ||
|
|
620
|
+
content.includes('authorization')
|
|
621
|
+
) {
|
|
622
|
+
schemes.bearerAuth = {
|
|
623
|
+
type: 'http',
|
|
624
|
+
scheme: 'bearer',
|
|
625
|
+
bearerFormat: 'JWT',
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Check for API key auth
|
|
630
|
+
if (content.includes('x-api-key') || content.includes('apiKey')) {
|
|
631
|
+
schemes.apiKey = {
|
|
632
|
+
type: 'apiKey',
|
|
633
|
+
in: 'header',
|
|
634
|
+
name: 'x-api-key',
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Check for basic auth
|
|
639
|
+
if (content.includes('passport-http') || content.includes('basic-auth')) {
|
|
640
|
+
schemes.basicAuth = {
|
|
641
|
+
type: 'http',
|
|
642
|
+
scheme: 'basic',
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return schemes;
|
|
648
|
+
}
|
|
649
|
+
}
|