codesift-mcp 0.2.18 → 0.4.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/README.md +143 -20
- package/dist/cache/hono-cache.d.ts +50 -0
- package/dist/cache/hono-cache.d.ts.map +1 -0
- package/dist/cache/hono-cache.js +132 -0
- package/dist/cache/hono-cache.js.map +1 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +17 -2
- package/dist/cli/setup.js.map +1 -1
- package/dist/formatters-shortening.d.ts +13 -0
- package/dist/formatters-shortening.d.ts.map +1 -1
- package/dist/formatters-shortening.js +131 -0
- package/dist/formatters-shortening.js.map +1 -1
- package/dist/formatters.d.ts +38 -0
- package/dist/formatters.d.ts.map +1 -1
- package/dist/formatters.js +498 -0
- package/dist/formatters.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.d.ts.map +1 -1
- package/dist/instructions.js +27 -26
- package/dist/instructions.js.map +1 -1
- package/dist/lsp/lsp-servers.d.ts.map +1 -1
- package/dist/lsp/lsp-servers.js +5 -0
- package/dist/lsp/lsp-servers.js.map +1 -1
- package/dist/lsp/lsp-tools.d.ts.map +1 -1
- package/dist/lsp/lsp-tools.js +1 -0
- package/dist/lsp/lsp-tools.js.map +1 -1
- package/dist/parser/astro-template.d.ts +47 -0
- package/dist/parser/astro-template.d.ts.map +1 -0
- package/dist/parser/astro-template.js +171 -0
- package/dist/parser/astro-template.js.map +1 -0
- package/dist/parser/extractors/_shared.d.ts +4 -0
- package/dist/parser/extractors/_shared.d.ts.map +1 -1
- package/dist/parser/extractors/_shared.js +8 -0
- package/dist/parser/extractors/_shared.js.map +1 -1
- package/dist/parser/extractors/astro.d.ts +4 -5
- package/dist/parser/extractors/astro.d.ts.map +1 -1
- package/dist/parser/extractors/astro.js +102 -26
- package/dist/parser/extractors/astro.js.map +1 -1
- package/dist/parser/extractors/gradle-kts.d.ts +4 -0
- package/dist/parser/extractors/gradle-kts.d.ts.map +1 -0
- package/dist/parser/extractors/gradle-kts.js +246 -0
- package/dist/parser/extractors/gradle-kts.js.map +1 -0
- package/dist/parser/extractors/hono-inline-analyzer.d.ts +34 -0
- package/dist/parser/extractors/hono-inline-analyzer.d.ts.map +1 -0
- package/dist/parser/extractors/hono-inline-analyzer.js +465 -0
- package/dist/parser/extractors/hono-inline-analyzer.js.map +1 -0
- package/dist/parser/extractors/hono-model.d.ts +196 -0
- package/dist/parser/extractors/hono-model.d.ts.map +1 -0
- package/dist/parser/extractors/hono-model.js +10 -0
- package/dist/parser/extractors/hono-model.js.map +1 -0
- package/dist/parser/extractors/hono.d.ts +118 -0
- package/dist/parser/extractors/hono.d.ts.map +1 -0
- package/dist/parser/extractors/hono.js +1527 -0
- package/dist/parser/extractors/hono.js.map +1 -0
- package/dist/parser/extractors/kotlin.d.ts +4 -0
- package/dist/parser/extractors/kotlin.d.ts.map +1 -0
- package/dist/parser/extractors/kotlin.js +521 -0
- package/dist/parser/extractors/kotlin.js.map +1 -0
- package/dist/parser/extractors/php.d.ts +22 -0
- package/dist/parser/extractors/php.d.ts.map +1 -0
- package/dist/parser/extractors/php.js +326 -0
- package/dist/parser/extractors/php.js.map +1 -0
- package/dist/parser/extractors/python.d.ts.map +1 -1
- package/dist/parser/extractors/python.js +234 -11
- package/dist/parser/extractors/python.js.map +1 -1
- package/dist/parser/extractors/sql.d.ts +33 -0
- package/dist/parser/extractors/sql.d.ts.map +1 -0
- package/dist/parser/extractors/sql.js +506 -0
- package/dist/parser/extractors/sql.js.map +1 -0
- package/dist/parser/extractors/typescript.d.ts.map +1 -1
- package/dist/parser/extractors/typescript.js +166 -3
- package/dist/parser/extractors/typescript.js.map +1 -1
- package/dist/parser/languages/tree-sitter-javascript.wasm +0 -0
- package/dist/parser/languages/tree-sitter-kotlin.wasm +0 -0
- package/dist/parser/languages/tree-sitter-php.wasm +0 -0
- package/dist/parser/languages/tree-sitter-php_only.wasm +0 -0
- package/dist/parser/languages/tree-sitter-python.wasm +0 -0
- package/dist/parser/parser-manager.d.ts +32 -0
- package/dist/parser/parser-manager.d.ts.map +1 -1
- package/dist/parser/parser-manager.js +82 -3
- package/dist/parser/parser-manager.js.map +1 -1
- package/dist/parser/symbol-extractor.d.ts.map +1 -1
- package/dist/parser/symbol-extractor.js +16 -0
- package/dist/parser/symbol-extractor.js.map +1 -1
- package/dist/register-tools.d.ts +37 -1
- package/dist/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +2657 -191
- package/dist/register-tools.js.map +1 -1
- package/dist/search/reranker.js +1 -1
- package/dist/search/reranker.js.map +1 -1
- package/dist/server-helpers.d.ts.map +1 -1
- package/dist/server-helpers.js +11 -0
- package/dist/server-helpers.js.map +1 -1
- package/dist/server.js +28 -1
- package/dist/server.js.map +1 -1
- package/dist/storage/index-store.d.ts +15 -1
- package/dist/storage/index-store.d.ts.map +1 -1
- package/dist/storage/index-store.js +27 -1
- package/dist/storage/index-store.js.map +1 -1
- package/dist/storage/session-state.d.ts +1 -1
- package/dist/storage/session-state.d.ts.map +1 -1
- package/dist/storage/session-state.js +6 -4
- package/dist/storage/session-state.js.map +1 -1
- package/dist/tools/agent-config-tools.d.ts +24 -0
- package/dist/tools/agent-config-tools.d.ts.map +1 -0
- package/dist/tools/agent-config-tools.js +119 -0
- package/dist/tools/agent-config-tools.js.map +1 -0
- package/dist/tools/architecture-tools.d.ts +23 -0
- package/dist/tools/architecture-tools.d.ts.map +1 -0
- package/dist/tools/architecture-tools.js +140 -0
- package/dist/tools/architecture-tools.js.map +1 -0
- package/dist/tools/astro-config.d.ts +33 -0
- package/dist/tools/astro-config.d.ts.map +1 -0
- package/dist/tools/astro-config.js +260 -0
- package/dist/tools/astro-config.js.map +1 -0
- package/dist/tools/astro-islands.d.ts +61 -0
- package/dist/tools/astro-islands.d.ts.map +1 -0
- package/dist/tools/astro-islands.js +240 -0
- package/dist/tools/astro-islands.js.map +1 -0
- package/dist/tools/astro-routes.d.ts +49 -0
- package/dist/tools/astro-routes.d.ts.map +1 -0
- package/dist/tools/astro-routes.js +119 -0
- package/dist/tools/astro-routes.js.map +1 -0
- package/dist/tools/audit-tools.d.ts +38 -0
- package/dist/tools/audit-tools.d.ts.map +1 -0
- package/dist/tools/audit-tools.js +248 -0
- package/dist/tools/audit-tools.js.map +1 -0
- package/dist/tools/celery-tools.d.ts +38 -0
- package/dist/tools/celery-tools.d.ts.map +1 -0
- package/dist/tools/celery-tools.js +154 -0
- package/dist/tools/celery-tools.js.map +1 -0
- package/dist/tools/clone-tools.js +1 -1
- package/dist/tools/clone-tools.js.map +1 -1
- package/dist/tools/complexity-tools.d.ts +4 -0
- package/dist/tools/complexity-tools.d.ts.map +1 -1
- package/dist/tools/complexity-tools.js +78 -4
- package/dist/tools/complexity-tools.js.map +1 -1
- package/dist/tools/compose-tools.d.ts +60 -0
- package/dist/tools/compose-tools.d.ts.map +1 -0
- package/dist/tools/compose-tools.js +203 -0
- package/dist/tools/compose-tools.js.map +1 -0
- package/dist/tools/coupling-tools.d.ts +50 -0
- package/dist/tools/coupling-tools.d.ts.map +1 -0
- package/dist/tools/coupling-tools.js +262 -0
- package/dist/tools/coupling-tools.js.map +1 -0
- package/dist/tools/dependency-audit-tools.d.ts +65 -0
- package/dist/tools/dependency-audit-tools.d.ts.map +1 -0
- package/dist/tools/dependency-audit-tools.js +553 -0
- package/dist/tools/dependency-audit-tools.js.map +1 -0
- package/dist/tools/django-settings.d.ts +22 -0
- package/dist/tools/django-settings.d.ts.map +1 -0
- package/dist/tools/django-settings.js +301 -0
- package/dist/tools/django-settings.js.map +1 -0
- package/dist/tools/frequency-tools.js +1 -1
- package/dist/tools/frequency-tools.js.map +1 -1
- package/dist/tools/graph-tools.d.ts +8 -2
- package/dist/tools/graph-tools.d.ts.map +1 -1
- package/dist/tools/graph-tools.js +44 -3
- package/dist/tools/graph-tools.js.map +1 -1
- package/dist/tools/hilt-tools.d.ts +55 -0
- package/dist/tools/hilt-tools.d.ts.map +1 -0
- package/dist/tools/hilt-tools.js +258 -0
- package/dist/tools/hilt-tools.js.map +1 -0
- package/dist/tools/hono-analyze-app.d.ts +48 -0
- package/dist/tools/hono-analyze-app.d.ts.map +1 -0
- package/dist/tools/hono-analyze-app.js +102 -0
- package/dist/tools/hono-analyze-app.js.map +1 -0
- package/dist/tools/hono-api-contract.d.ts +22 -0
- package/dist/tools/hono-api-contract.d.ts.map +1 -0
- package/dist/tools/hono-api-contract.js +80 -0
- package/dist/tools/hono-api-contract.js.map +1 -0
- package/dist/tools/hono-conditional-middleware.d.ts +27 -0
- package/dist/tools/hono-conditional-middleware.d.ts.map +1 -0
- package/dist/tools/hono-conditional-middleware.js +62 -0
- package/dist/tools/hono-conditional-middleware.js.map +1 -0
- package/dist/tools/hono-context-flow.d.ts +24 -0
- package/dist/tools/hono-context-flow.d.ts.map +1 -0
- package/dist/tools/hono-context-flow.js +78 -0
- package/dist/tools/hono-context-flow.js.map +1 -0
- package/dist/tools/hono-dead-routes.d.ts +26 -0
- package/dist/tools/hono-dead-routes.d.ts.map +1 -0
- package/dist/tools/hono-dead-routes.js +109 -0
- package/dist/tools/hono-dead-routes.js.map +1 -0
- package/dist/tools/hono-env-regression.d.ts +29 -0
- package/dist/tools/hono-env-regression.d.ts.map +1 -0
- package/dist/tools/hono-env-regression.js +157 -0
- package/dist/tools/hono-env-regression.js.map +1 -0
- package/dist/tools/hono-inline-analyze.d.ts +31 -0
- package/dist/tools/hono-inline-analyze.d.ts.map +1 -0
- package/dist/tools/hono-inline-analyze.js +67 -0
- package/dist/tools/hono-inline-analyze.js.map +1 -0
- package/dist/tools/hono-middleware-chain.d.ts +22 -0
- package/dist/tools/hono-middleware-chain.d.ts.map +1 -0
- package/dist/tools/hono-middleware-chain.js +84 -0
- package/dist/tools/hono-middleware-chain.js.map +1 -0
- package/dist/tools/hono-modules.d.ts +22 -0
- package/dist/tools/hono-modules.d.ts.map +1 -0
- package/dist/tools/hono-modules.js +126 -0
- package/dist/tools/hono-modules.js.map +1 -0
- package/dist/tools/hono-response-types.d.ts +37 -0
- package/dist/tools/hono-response-types.d.ts.map +1 -0
- package/dist/tools/hono-response-types.js +84 -0
- package/dist/tools/hono-response-types.js.map +1 -0
- package/dist/tools/hono-rpc-types.d.ts +21 -0
- package/dist/tools/hono-rpc-types.d.ts.map +1 -0
- package/dist/tools/hono-rpc-types.js +57 -0
- package/dist/tools/hono-rpc-types.js.map +1 -0
- package/dist/tools/hono-security.d.ts +21 -0
- package/dist/tools/hono-security.d.ts.map +1 -0
- package/dist/tools/hono-security.js +98 -0
- package/dist/tools/hono-security.js.map +1 -0
- package/dist/tools/hono-visualize.d.ts +13 -0
- package/dist/tools/hono-visualize.d.ts.map +1 -0
- package/dist/tools/hono-visualize.js +72 -0
- package/dist/tools/hono-visualize.js.map +1 -0
- package/dist/tools/hotspot-tools.d.ts.map +1 -1
- package/dist/tools/hotspot-tools.js +9 -7
- package/dist/tools/hotspot-tools.js.map +1 -1
- package/dist/tools/index-tools.d.ts +17 -0
- package/dist/tools/index-tools.d.ts.map +1 -1
- package/dist/tools/index-tools.js +210 -10
- package/dist/tools/index-tools.js.map +1 -1
- package/dist/tools/kotlin-tools.d.ts +142 -0
- package/dist/tools/kotlin-tools.d.ts.map +1 -0
- package/dist/tools/kotlin-tools.js +572 -0
- package/dist/tools/kotlin-tools.js.map +1 -0
- package/dist/tools/legacy-hono-conventions.d.ts +14 -0
- package/dist/tools/legacy-hono-conventions.d.ts.map +1 -0
- package/dist/tools/legacy-hono-conventions.js +152 -0
- package/dist/tools/legacy-hono-conventions.js.map +1 -0
- package/dist/tools/migration-lint-tools.d.ts +26 -0
- package/dist/tools/migration-lint-tools.d.ts.map +1 -0
- package/dist/tools/migration-lint-tools.js +247 -0
- package/dist/tools/migration-lint-tools.js.map +1 -0
- package/dist/tools/model-tools.d.ts +30 -0
- package/dist/tools/model-tools.d.ts.map +1 -0
- package/dist/tools/model-tools.js +145 -0
- package/dist/tools/model-tools.js.map +1 -0
- package/dist/tools/nest-ext-tools.d.ts +92 -0
- package/dist/tools/nest-ext-tools.d.ts.map +1 -0
- package/dist/tools/nest-ext-tools.js +359 -0
- package/dist/tools/nest-ext-tools.js.map +1 -0
- package/dist/tools/nest-tools.d.ts +171 -0
- package/dist/tools/nest-tools.d.ts.map +1 -0
- package/dist/tools/nest-tools.js +1042 -0
- package/dist/tools/nest-tools.js.map +1 -0
- package/dist/tools/nextjs-api-contract-readers.d.ts +14 -0
- package/dist/tools/nextjs-api-contract-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-api-contract-readers.js +204 -0
- package/dist/tools/nextjs-api-contract-readers.js.map +1 -0
- package/dist/tools/nextjs-api-contract-tools.d.ts +57 -0
- package/dist/tools/nextjs-api-contract-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-api-contract-tools.js +144 -0
- package/dist/tools/nextjs-api-contract-tools.js.map +1 -0
- package/dist/tools/nextjs-boundary-tools.d.ts +39 -0
- package/dist/tools/nextjs-boundary-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-boundary-tools.js +152 -0
- package/dist/tools/nextjs-boundary-tools.js.map +1 -0
- package/dist/tools/nextjs-component-tools.d.ts +121 -0
- package/dist/tools/nextjs-component-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-component-tools.js +460 -0
- package/dist/tools/nextjs-component-tools.js.map +1 -0
- package/dist/tools/nextjs-data-flow-tools.d.ts +42 -0
- package/dist/tools/nextjs-data-flow-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-data-flow-tools.js +158 -0
- package/dist/tools/nextjs-data-flow-tools.js.map +1 -0
- package/dist/tools/nextjs-framework-audit-tools.d.ts +37 -0
- package/dist/tools/nextjs-framework-audit-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-framework-audit-tools.js +211 -0
- package/dist/tools/nextjs-framework-audit-tools.js.map +1 -0
- package/dist/tools/nextjs-link-tools.d.ts +41 -0
- package/dist/tools/nextjs-link-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-link-tools.js +157 -0
- package/dist/tools/nextjs-link-tools.js.map +1 -0
- package/dist/tools/nextjs-metadata-tools.d.ts +74 -0
- package/dist/tools/nextjs-metadata-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-metadata-tools.js +252 -0
- package/dist/tools/nextjs-metadata-tools.js.map +1 -0
- package/dist/tools/nextjs-middleware-coverage-tools.d.ts +41 -0
- package/dist/tools/nextjs-middleware-coverage-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-middleware-coverage-tools.js +88 -0
- package/dist/tools/nextjs-middleware-coverage-tools.js.map +1 -0
- package/dist/tools/nextjs-route-tools.d.ts +100 -0
- package/dist/tools/nextjs-route-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-route-tools.js +493 -0
- package/dist/tools/nextjs-route-tools.js.map +1 -0
- package/dist/tools/nextjs-security-readers.d.ts +22 -0
- package/dist/tools/nextjs-security-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-security-readers.js +318 -0
- package/dist/tools/nextjs-security-readers.js.map +1 -0
- package/dist/tools/nextjs-security-scoring.d.ts +15 -0
- package/dist/tools/nextjs-security-scoring.d.ts.map +1 -0
- package/dist/tools/nextjs-security-scoring.js +65 -0
- package/dist/tools/nextjs-security-scoring.js.map +1 -0
- package/dist/tools/nextjs-security-tools.d.ts +75 -0
- package/dist/tools/nextjs-security-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-security-tools.js +153 -0
- package/dist/tools/nextjs-security-tools.js.map +1 -0
- package/dist/tools/nextjs-tools.d.ts +15 -0
- package/dist/tools/nextjs-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-tools.js +15 -0
- package/dist/tools/nextjs-tools.js.map +1 -0
- package/dist/tools/outline-tools.d.ts.map +1 -1
- package/dist/tools/outline-tools.js +20 -0
- package/dist/tools/outline-tools.js.map +1 -1
- package/dist/tools/pattern-tools.d.ts +8 -0
- package/dist/tools/pattern-tools.d.ts.map +1 -1
- package/dist/tools/pattern-tools.js +561 -3
- package/dist/tools/pattern-tools.js.map +1 -1
- package/dist/tools/perf-tools.d.ts +32 -0
- package/dist/tools/perf-tools.d.ts.map +1 -0
- package/dist/tools/perf-tools.js +227 -0
- package/dist/tools/perf-tools.js.map +1 -0
- package/dist/tools/php-tools.d.ts +176 -0
- package/dist/tools/php-tools.d.ts.map +1 -0
- package/dist/tools/php-tools.js +543 -0
- package/dist/tools/php-tools.js.map +1 -0
- package/dist/tools/prisma-schema-tools.d.ts +44 -0
- package/dist/tools/prisma-schema-tools.d.ts.map +1 -0
- package/dist/tools/prisma-schema-tools.js +358 -0
- package/dist/tools/prisma-schema-tools.js.map +1 -0
- package/dist/tools/project-tools.d.ts +115 -6
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +594 -217
- package/dist/tools/project-tools.js.map +1 -1
- package/dist/tools/pyproject-tools.d.ts +23 -0
- package/dist/tools/pyproject-tools.d.ts.map +1 -0
- package/dist/tools/pyproject-tools.js +133 -0
- package/dist/tools/pyproject-tools.js.map +1 -0
- package/dist/tools/pytest-tools.d.ts +20 -0
- package/dist/tools/pytest-tools.d.ts.map +1 -0
- package/dist/tools/pytest-tools.js +106 -0
- package/dist/tools/pytest-tools.js.map +1 -0
- package/dist/tools/python-callers.d.ts +28 -0
- package/dist/tools/python-callers.d.ts.map +1 -0
- package/dist/tools/python-callers.js +110 -0
- package/dist/tools/python-callers.js.map +1 -0
- package/dist/tools/python-circular-imports.d.ts +19 -0
- package/dist/tools/python-circular-imports.d.ts.map +1 -0
- package/dist/tools/python-circular-imports.js +126 -0
- package/dist/tools/python-circular-imports.js.map +1 -0
- package/dist/tools/python-deps-analyzer.d.ts +46 -0
- package/dist/tools/python-deps-analyzer.d.ts.map +1 -0
- package/dist/tools/python-deps-analyzer.js +227 -0
- package/dist/tools/python-deps-analyzer.js.map +1 -0
- package/dist/tools/query-tools.d.ts +23 -0
- package/dist/tools/query-tools.d.ts.map +1 -0
- package/dist/tools/query-tools.js +256 -0
- package/dist/tools/query-tools.js.map +1 -0
- package/dist/tools/react-tools.d.ts +218 -0
- package/dist/tools/react-tools.d.ts.map +1 -0
- package/dist/tools/react-tools.js +714 -0
- package/dist/tools/react-tools.js.map +1 -0
- package/dist/tools/report-tools.js +47 -0
- package/dist/tools/report-tools.js.map +1 -1
- package/dist/tools/review-diff-tools.d.ts +2 -6
- package/dist/tools/review-diff-tools.d.ts.map +1 -1
- package/dist/tools/review-diff-tools.js +51 -66
- package/dist/tools/review-diff-tools.js.map +1 -1
- package/dist/tools/room-tools.d.ts +36 -0
- package/dist/tools/room-tools.d.ts.map +1 -0
- package/dist/tools/room-tools.js +147 -0
- package/dist/tools/room-tools.js.map +1 -0
- package/dist/tools/route-tools.d.ts +27 -1
- package/dist/tools/route-tools.d.ts.map +1 -1
- package/dist/tools/route-tools.js +744 -18
- package/dist/tools/route-tools.js.map +1 -1
- package/dist/tools/ruff-tools.d.ts +32 -0
- package/dist/tools/ruff-tools.d.ts.map +1 -0
- package/dist/tools/ruff-tools.js +114 -0
- package/dist/tools/ruff-tools.js.map +1 -0
- package/dist/tools/search-ranker.d.ts.map +1 -1
- package/dist/tools/search-ranker.js +7 -0
- package/dist/tools/search-ranker.js.map +1 -1
- package/dist/tools/serialization-tools.d.ts +24 -0
- package/dist/tools/serialization-tools.d.ts.map +1 -0
- package/dist/tools/serialization-tools.js +156 -0
- package/dist/tools/serialization-tools.js.map +1 -0
- package/dist/tools/sql-tools.d.ts +234 -0
- package/dist/tools/sql-tools.d.ts.map +1 -0
- package/dist/tools/sql-tools.js +1037 -0
- package/dist/tools/sql-tools.js.map +1 -0
- package/dist/tools/status-tools.d.ts +10 -0
- package/dist/tools/status-tools.d.ts.map +1 -0
- package/dist/tools/status-tools.js +32 -0
- package/dist/tools/status-tools.js.map +1 -0
- package/dist/tools/symbol-tools.d.ts +19 -0
- package/dist/tools/symbol-tools.d.ts.map +1 -1
- package/dist/tools/symbol-tools.js +78 -4
- package/dist/tools/symbol-tools.js.map +1 -1
- package/dist/tools/test-impact-tools.d.ts +29 -0
- package/dist/tools/test-impact-tools.d.ts.map +1 -0
- package/dist/tools/test-impact-tools.js +156 -0
- package/dist/tools/test-impact-tools.js.map +1 -0
- package/dist/tools/typecheck-tools.d.ts +39 -0
- package/dist/tools/typecheck-tools.d.ts.map +1 -0
- package/dist/tools/typecheck-tools.js +191 -0
- package/dist/tools/typecheck-tools.js.map +1 -0
- package/dist/tools/wiring-tools.d.ts +19 -0
- package/dist/tools/wiring-tools.d.ts.map +1 -0
- package/dist/tools/wiring-tools.js +147 -0
- package/dist/tools/wiring-tools.js.map +1 -0
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/framework-detect.d.ts +18 -2
- package/dist/utils/framework-detect.d.ts.map +1 -1
- package/dist/utils/framework-detect.js +150 -3
- package/dist/utils/framework-detect.js.map +1 -1
- package/dist/utils/import-graph.d.ts +36 -0
- package/dist/utils/import-graph.d.ts.map +1 -1
- package/dist/utils/import-graph.js +212 -9
- package/dist/utils/import-graph.js.map +1 -1
- package/dist/utils/language-detect.d.ts +21 -0
- package/dist/utils/language-detect.d.ts.map +1 -0
- package/dist/utils/language-detect.js +183 -0
- package/dist/utils/language-detect.js.map +1 -0
- package/dist/utils/nextjs-ast-readers.d.ts +44 -0
- package/dist/utils/nextjs-ast-readers.d.ts.map +1 -0
- package/dist/utils/nextjs-ast-readers.js +341 -0
- package/dist/utils/nextjs-ast-readers.js.map +1 -0
- package/dist/utils/nextjs-audit-cache.d.ts +51 -0
- package/dist/utils/nextjs-audit-cache.d.ts.map +1 -0
- package/dist/utils/nextjs-audit-cache.js +116 -0
- package/dist/utils/nextjs-audit-cache.js.map +1 -0
- package/dist/utils/nextjs-metadata-readers.d.ts +65 -0
- package/dist/utils/nextjs-metadata-readers.d.ts.map +1 -0
- package/dist/utils/nextjs-metadata-readers.js +447 -0
- package/dist/utils/nextjs-metadata-readers.js.map +1 -0
- package/dist/utils/nextjs.d.ts +42 -0
- package/dist/utils/nextjs.d.ts.map +1 -0
- package/dist/utils/nextjs.js +284 -0
- package/dist/utils/nextjs.js.map +1 -0
- package/dist/utils/python-import-resolver.d.ts +42 -0
- package/dist/utils/python-import-resolver.d.ts.map +1 -0
- package/dist/utils/python-import-resolver.js +101 -0
- package/dist/utils/python-import-resolver.js.map +1 -0
- package/dist/utils/python-imports.d.ts +28 -0
- package/dist/utils/python-imports.d.ts.map +1 -0
- package/dist/utils/python-imports.js +117 -0
- package/dist/utils/python-imports.js.map +1 -0
- package/dist/utils/react-alias.d.ts +15 -0
- package/dist/utils/react-alias.d.ts.map +1 -0
- package/dist/utils/react-alias.js +31 -0
- package/dist/utils/react-alias.js.map +1 -0
- package/dist/utils/test-file.d.ts.map +1 -1
- package/dist/utils/test-file.js +7 -0
- package/dist/utils/test-file.js.map +1 -1
- package/dist/utils/walk.d.ts +22 -0
- package/dist/utils/walk.d.ts.map +1 -1
- package/dist/utils/walk.js +70 -2
- package/dist/utils/walk.js.map +1 -1
- package/package.json +3 -2
- package/rules/codesift.md +34 -5
- package/rules/codesift.mdc +34 -5
- package/rules/codex.md +34 -5
- package/rules/gemini.md +34 -5
- package/src/parser/languages/tree-sitter-javascript.wasm +0 -0
- package/src/parser/languages/tree-sitter-kotlin.wasm +0 -0
- package/src/parser/languages/tree-sitter-php.wasm +0 -0
- package/src/parser/languages/tree-sitter-php_only.wasm +0 -0
- package/src/parser/languages/tree-sitter-python.wasm +0 -0
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NestJS analysis tools — B1-B5 + C (nest_audit meta-orchestrator).
|
|
3
|
+
* Discoverable via discover_tools(query="nestjs").
|
|
4
|
+
*/
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { getCodeIndex } from "./index-tools.js";
|
|
8
|
+
import { extractNestConventions } from "./project-tools.js";
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// B5: nest_lifecycle_map — types + implementation
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
const LIFECYCLE_HOOKS = new Set([
|
|
13
|
+
"onModuleInit",
|
|
14
|
+
"onModuleDestroy",
|
|
15
|
+
"onApplicationBootstrap",
|
|
16
|
+
"onApplicationShutdown",
|
|
17
|
+
"beforeApplicationShutdown",
|
|
18
|
+
]);
|
|
19
|
+
export async function nestLifecycleMap(repo) {
|
|
20
|
+
const index = await getCodeIndex(repo);
|
|
21
|
+
if (!index) {
|
|
22
|
+
throw new Error(`Repository "${repo}" not found. Index it first with index_folder.`);
|
|
23
|
+
}
|
|
24
|
+
const hooks = [];
|
|
25
|
+
const errors = [];
|
|
26
|
+
for (const sym of index.symbols) {
|
|
27
|
+
if (!LIFECYCLE_HOOKS.has(sym.name))
|
|
28
|
+
continue;
|
|
29
|
+
if (sym.kind !== "method" && sym.kind !== "function")
|
|
30
|
+
continue;
|
|
31
|
+
// Determine parent class name from source or file context
|
|
32
|
+
let className = "Unknown";
|
|
33
|
+
const source = sym.source ?? "";
|
|
34
|
+
// Try to find the enclosing class via parent_id (if available)
|
|
35
|
+
const symAny = sym;
|
|
36
|
+
if (symAny.parent_id) {
|
|
37
|
+
const parentSym = index.symbols.find((s) => s.id === symAny.parent_id);
|
|
38
|
+
if (parentSym)
|
|
39
|
+
className = parentSym.name;
|
|
40
|
+
}
|
|
41
|
+
// Fallback: look for class name in source
|
|
42
|
+
if (className === "Unknown") {
|
|
43
|
+
// Check if there's a class symbol in the same file that contains this method
|
|
44
|
+
const classSym = index.symbols.find((s) => s.file === sym.file && s.kind === "class" && s.start_line <= sym.start_line && s.end_line >= sym.end_line);
|
|
45
|
+
if (classSym)
|
|
46
|
+
className = classSym.name;
|
|
47
|
+
}
|
|
48
|
+
const isAsync = /async\s/.test(source.slice(0, 50));
|
|
49
|
+
hooks.push({
|
|
50
|
+
class_name: className,
|
|
51
|
+
file: sym.file,
|
|
52
|
+
hook: sym.name,
|
|
53
|
+
is_async: isAsync,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { hooks, ...(errors.length > 0 ? { errors } : {}) };
|
|
57
|
+
}
|
|
58
|
+
export async function nestModuleGraph(repo, options) {
|
|
59
|
+
const index = await getCodeIndex(repo);
|
|
60
|
+
if (!index)
|
|
61
|
+
throw new Error(`Repository "${repo}" not found. Index it first with index_folder.`);
|
|
62
|
+
const maxModules = options?.max_modules ?? 200;
|
|
63
|
+
const moduleFiles = index.files.filter((f) => f.path.endsWith(".module.ts") || f.path.endsWith(".module.js"));
|
|
64
|
+
const modules = [];
|
|
65
|
+
const edges = [];
|
|
66
|
+
const errors = [];
|
|
67
|
+
let truncated = false;
|
|
68
|
+
// Parse each module file
|
|
69
|
+
for (const file of moduleFiles) {
|
|
70
|
+
if (modules.length >= maxModules) {
|
|
71
|
+
truncated = true;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
let source;
|
|
75
|
+
try {
|
|
76
|
+
source = await readFile(join(index.root, file.path), "utf-8");
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
errors.push({ file: file.path, reason: `readFile failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const conv = extractNestConventions(source, file.path);
|
|
83
|
+
// Find the module class name from source
|
|
84
|
+
const classMatch = /export\s+class\s+(\w+Module)\b/.exec(source);
|
|
85
|
+
const moduleName = classMatch?.[1] ?? file.path.replace(/.*\//, "").replace(/\.module\.[jt]sx?$/, "Module");
|
|
86
|
+
// Check for @Global()
|
|
87
|
+
const isGlobal = /@Global\s*\(\s*\)/.test(source) || conv.modules.some((m) => m.is_global && m.name === moduleName);
|
|
88
|
+
// Extract exports array
|
|
89
|
+
const exportNames = [];
|
|
90
|
+
const exportsMatch = /exports:\s*\[([^\]]*)\]/s.exec(source);
|
|
91
|
+
if (exportsMatch) {
|
|
92
|
+
const inner = exportsMatch[1];
|
|
93
|
+
for (const m of inner.matchAll(/(\w+)/g)) {
|
|
94
|
+
exportNames.push(m[1]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Extract provider names
|
|
98
|
+
const providerNames = [];
|
|
99
|
+
const providersMatch = /providers:\s*\[([^\]]*)\]/s.exec(source);
|
|
100
|
+
if (providersMatch) {
|
|
101
|
+
const inner = providersMatch[1];
|
|
102
|
+
for (const m of inner.matchAll(/(?:useClass:\s*)?(\w+(?:Service|Provider|Guard|Interceptor|Pipe|Filter|Factory|Strategy))\b/g)) {
|
|
103
|
+
providerNames.push(m[1]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
modules.push({
|
|
107
|
+
name: moduleName,
|
|
108
|
+
file: file.path,
|
|
109
|
+
is_global: isGlobal,
|
|
110
|
+
imports: conv.modules.map((m) => m.name),
|
|
111
|
+
exports: exportNames,
|
|
112
|
+
providers: providerNames,
|
|
113
|
+
controllers: conv.controllers,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Build edges: module → imported module
|
|
117
|
+
const moduleNames = new Set(modules.map((m) => m.name));
|
|
118
|
+
for (const mod of modules) {
|
|
119
|
+
for (const imp of mod.imports) {
|
|
120
|
+
if (moduleNames.has(imp)) {
|
|
121
|
+
edges.push({ from: mod.name, to: imp });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Detect circular deps via DFS
|
|
126
|
+
const circular_deps = detectCycles(modules.map((m) => m.name), edges);
|
|
127
|
+
return {
|
|
128
|
+
modules,
|
|
129
|
+
edges,
|
|
130
|
+
circular_deps,
|
|
131
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
132
|
+
...(truncated ? { truncated } : {}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** DFS cycle detection on directed graph. Exported for reuse in nest-ext-tools.ts (G12). */
|
|
136
|
+
export function detectCycles(nodes, edges) {
|
|
137
|
+
const adj = new Map();
|
|
138
|
+
for (const n of nodes)
|
|
139
|
+
adj.set(n, []);
|
|
140
|
+
for (const e of edges)
|
|
141
|
+
adj.get(e.from)?.push(e.to);
|
|
142
|
+
const cycles = [];
|
|
143
|
+
const visited = new Set();
|
|
144
|
+
const inStack = new Set();
|
|
145
|
+
const path = [];
|
|
146
|
+
function dfs(node) {
|
|
147
|
+
if (inStack.has(node)) {
|
|
148
|
+
const cycleStart = path.indexOf(node);
|
|
149
|
+
if (cycleStart >= 0)
|
|
150
|
+
cycles.push(path.slice(cycleStart).concat(node));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (visited.has(node))
|
|
154
|
+
return;
|
|
155
|
+
visited.add(node);
|
|
156
|
+
inStack.add(node);
|
|
157
|
+
path.push(node);
|
|
158
|
+
for (const next of adj.get(node) ?? [])
|
|
159
|
+
dfs(next);
|
|
160
|
+
path.pop();
|
|
161
|
+
inStack.delete(node);
|
|
162
|
+
}
|
|
163
|
+
for (const n of nodes)
|
|
164
|
+
dfs(n);
|
|
165
|
+
return cycles;
|
|
166
|
+
}
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Shared helpers: constructor injection parsing (CQ14)
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
/** Extract constructor parameter body using paren counting (handles decorated params) */
|
|
171
|
+
function extractConstructorBody(source) {
|
|
172
|
+
const ctorIdx = source.indexOf("constructor(");
|
|
173
|
+
if (ctorIdx === -1)
|
|
174
|
+
return null;
|
|
175
|
+
const start = ctorIdx + "constructor(".length;
|
|
176
|
+
let depth = 1;
|
|
177
|
+
let i = start;
|
|
178
|
+
while (i < source.length && depth > 0) {
|
|
179
|
+
if (source[i] === "(")
|
|
180
|
+
depth++;
|
|
181
|
+
else if (source[i] === ")")
|
|
182
|
+
depth--;
|
|
183
|
+
i++;
|
|
184
|
+
}
|
|
185
|
+
return depth === 0 ? source.slice(start, i - 1) : null;
|
|
186
|
+
}
|
|
187
|
+
/** Extract injected type names from a constructor body string */
|
|
188
|
+
function extractInjectedTypes(ctorBody) {
|
|
189
|
+
const types = [];
|
|
190
|
+
// R-6 fix: separate depth counters for () and <> to avoid cross-corruption
|
|
191
|
+
const params = [];
|
|
192
|
+
let parenDepth = 0;
|
|
193
|
+
let angleDepth = 0;
|
|
194
|
+
let current = "";
|
|
195
|
+
for (const ch of ctorBody) {
|
|
196
|
+
if (ch === "(")
|
|
197
|
+
parenDepth++;
|
|
198
|
+
else if (ch === ")")
|
|
199
|
+
parenDepth--;
|
|
200
|
+
else if (ch === "<")
|
|
201
|
+
angleDepth++;
|
|
202
|
+
else if (ch === ">")
|
|
203
|
+
angleDepth--;
|
|
204
|
+
if (ch === "," && parenDepth === 0 && angleDepth === 0) {
|
|
205
|
+
params.push(current.trim());
|
|
206
|
+
current = "";
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
current += ch;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (current.trim())
|
|
213
|
+
params.push(current.trim());
|
|
214
|
+
for (const param of params) {
|
|
215
|
+
// Extract type after the last `:` — handles decorators before the param name
|
|
216
|
+
const colonIdx = param.lastIndexOf(":");
|
|
217
|
+
if (colonIdx === -1)
|
|
218
|
+
continue;
|
|
219
|
+
const typeStr = param.slice(colonIdx + 1).trim();
|
|
220
|
+
// G3: Detect container generics like Repository<User>, Model<Comment>, Repo<X>.
|
|
221
|
+
// For container types, unwrap and return the inner type parameter so consumers
|
|
222
|
+
// can distinguish Repository<Article> from Repository<Comment>.
|
|
223
|
+
const genericMatch = typeStr.match(/^(\w+)<\s*(\w+)\s*(?:,[^>]*)?>/);
|
|
224
|
+
if (genericMatch) {
|
|
225
|
+
const outer = genericMatch[1];
|
|
226
|
+
const inner = genericMatch[2];
|
|
227
|
+
if (/^(Repository|Repo|Model|Collection|Array|Set|Map|List|Observable|Promise|Ref|Token|Provider|Class)$/.test(outer)) {
|
|
228
|
+
types.push(inner);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Non-container type — return the outer name as before
|
|
233
|
+
const typeMatch = typeStr.match(/^(\w+)/);
|
|
234
|
+
if (typeMatch)
|
|
235
|
+
types.push(typeMatch[1]);
|
|
236
|
+
}
|
|
237
|
+
return types;
|
|
238
|
+
}
|
|
239
|
+
/** Parse @Injectable() classes from source */
|
|
240
|
+
function parseInjectableClasses(source) {
|
|
241
|
+
const results = [];
|
|
242
|
+
const re = /@Injectable\s*\(([^)]*)\)\s*(?:export\s+)?class\s+(\w+)/g;
|
|
243
|
+
let m;
|
|
244
|
+
while ((m = re.exec(source)) !== null) {
|
|
245
|
+
const args = m[1] ?? "";
|
|
246
|
+
const name = m[2];
|
|
247
|
+
const scopeMatch = args.match(/scope:\s*Scope\.(\w+)/);
|
|
248
|
+
results.push({ name, ...(scopeMatch ? { scope: scopeMatch[1] } : {}) });
|
|
249
|
+
}
|
|
250
|
+
return results;
|
|
251
|
+
}
|
|
252
|
+
export async function nestDIGraph(repo, options) {
|
|
253
|
+
const index = await getCodeIndex(repo);
|
|
254
|
+
if (!index)
|
|
255
|
+
throw new Error(`Repository "${repo}" not found. Index it first with index_folder.`);
|
|
256
|
+
const maxNodes = options?.max_nodes ?? 200;
|
|
257
|
+
const focus = options?.focus;
|
|
258
|
+
const nodes = [];
|
|
259
|
+
const edges = [];
|
|
260
|
+
const errors = [];
|
|
261
|
+
let truncated = false;
|
|
262
|
+
// Scan files for @Injectable classes
|
|
263
|
+
const candidateFiles = index.files.filter((f) => {
|
|
264
|
+
if (focus && !f.path.includes(focus))
|
|
265
|
+
return false;
|
|
266
|
+
return f.path.endsWith(".ts") || f.path.endsWith(".js");
|
|
267
|
+
});
|
|
268
|
+
for (const file of candidateFiles) {
|
|
269
|
+
if (nodes.length >= maxNodes) {
|
|
270
|
+
truncated = true;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
let source;
|
|
274
|
+
try {
|
|
275
|
+
source = await readFile(join(index.root, file.path), "utf-8");
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
errors.push({ file: file.path, reason: `readFile failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const injectables = parseInjectableClasses(source);
|
|
282
|
+
for (const inj of injectables) {
|
|
283
|
+
if (nodes.length >= maxNodes) {
|
|
284
|
+
truncated = true;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
nodes.push({
|
|
288
|
+
name: inj.name,
|
|
289
|
+
file: file.path,
|
|
290
|
+
kind: "provider",
|
|
291
|
+
...(inj.scope ? { scope: inj.scope } : {}),
|
|
292
|
+
});
|
|
293
|
+
// Extract constructor injection
|
|
294
|
+
// Find the class body for this specific injectable
|
|
295
|
+
const classIdx = source.indexOf(`class ${inj.name}`);
|
|
296
|
+
if (classIdx === -1)
|
|
297
|
+
continue;
|
|
298
|
+
const classSource = source.slice(classIdx);
|
|
299
|
+
const ctorBody = extractConstructorBody(classSource);
|
|
300
|
+
if (!ctorBody)
|
|
301
|
+
continue;
|
|
302
|
+
const injectedTypes = extractInjectedTypes(ctorBody);
|
|
303
|
+
for (const type of injectedTypes) {
|
|
304
|
+
edges.push({ from: inj.name, to: type, via: "inject" });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Detect cycles
|
|
309
|
+
const nodeNames = nodes.map((n) => n.name);
|
|
310
|
+
const cycles = detectCycles(nodeNames, edges.map((e) => ({ from: e.from, to: e.to })));
|
|
311
|
+
return {
|
|
312
|
+
nodes,
|
|
313
|
+
edges,
|
|
314
|
+
cycles,
|
|
315
|
+
cross_module_warnings: [], // TODO: implement cross-module warnings in future task
|
|
316
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
317
|
+
...(truncated ? { truncated } : {}),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// Shared helpers: guard/interceptor/pipe parsing (CQ14)
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
/** Parse @UseGuards(...) from source, returns guard class names.
|
|
324
|
+
* R-8 fix: handles both class-ref form @UseGuards(AuthGuard) and
|
|
325
|
+
* instantiation form @UseGuards(new ThrottlerGuard()). */
|
|
326
|
+
function parseUseGuards(source) {
|
|
327
|
+
const results = [];
|
|
328
|
+
// Match the full @UseGuards(...) arg including nested parens for `new Guard()`
|
|
329
|
+
const re = /@UseGuards\s*\(\s*([^)]*(?:\([^)]*\)[^)]*)*)\s*\)/g;
|
|
330
|
+
let m;
|
|
331
|
+
while ((m = re.exec(source)) !== null) {
|
|
332
|
+
const args = m[1];
|
|
333
|
+
// Extract class names — both bare refs and `new ClassName(...)` instantiations
|
|
334
|
+
for (const part of args.split(",")) {
|
|
335
|
+
const trimmed = part.trim();
|
|
336
|
+
if (!trimmed)
|
|
337
|
+
continue;
|
|
338
|
+
const newMatch = /new\s+(\w+)/.exec(trimmed);
|
|
339
|
+
if (newMatch) {
|
|
340
|
+
results.push(newMatch[1]);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const bareMatch = /^(\w+)$/.exec(trimmed);
|
|
344
|
+
if (bareMatch)
|
|
345
|
+
results.push(bareMatch[1]);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return results;
|
|
349
|
+
}
|
|
350
|
+
/** Parse @UseInterceptors(...) from source */
|
|
351
|
+
function parseUseInterceptors(source) {
|
|
352
|
+
const results = [];
|
|
353
|
+
const re = /@UseInterceptors\s*\(\s*([\w\s,]+)\s*\)/g;
|
|
354
|
+
let m;
|
|
355
|
+
while ((m = re.exec(source)) !== null) {
|
|
356
|
+
for (const name of m[1].split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
357
|
+
results.push(name);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return results;
|
|
361
|
+
}
|
|
362
|
+
/** Parse @UsePipes(...) from source */
|
|
363
|
+
function parseUsePipes(source) {
|
|
364
|
+
const results = [];
|
|
365
|
+
const re = /@UsePipes\s*\(\s*([\w\s,]+)\s*\)/g;
|
|
366
|
+
let m;
|
|
367
|
+
while ((m = re.exec(source)) !== null) {
|
|
368
|
+
for (const name of m[1].split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
369
|
+
results.push(name);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return results;
|
|
373
|
+
}
|
|
374
|
+
/** Parse @UseFilters(...) from source */
|
|
375
|
+
function parseUseFilters(source) {
|
|
376
|
+
const results = [];
|
|
377
|
+
const re = /@UseFilters\s*\(\s*([\w\s,]+)\s*\)/g;
|
|
378
|
+
let m;
|
|
379
|
+
while ((m = re.exec(source)) !== null) {
|
|
380
|
+
for (const name of m[1].split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
381
|
+
results.push(name);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return results;
|
|
385
|
+
}
|
|
386
|
+
/** Built-in NestJS decorators that should NOT be reported as custom metadata */
|
|
387
|
+
const BUILTIN_DECORATORS = new Set([
|
|
388
|
+
"Get", "Post", "Put", "Delete", "Patch", "Options", "Head", "All",
|
|
389
|
+
"Controller", "Injectable", "Module", "Global",
|
|
390
|
+
"UseGuards", "UseInterceptors", "UsePipes", "UseFilters",
|
|
391
|
+
"Param", "Body", "Query", "Headers", "Req", "Res", "Next", "Ip", "Session", "HostParam",
|
|
392
|
+
"Version", "ApiOperation", "ApiBearerAuth", "ApiTags", "ApiResponse", "ApiProperty", "ApiParam", "ApiBody", "ApiQuery",
|
|
393
|
+
"HealthCheck", "HealthIndicator",
|
|
394
|
+
"Catch", "Optional", "Inject", "InjectRepository", "InjectModel",
|
|
395
|
+
"Resolver", "Query" /*gql*/, "Mutation", "Subscription", "Args", "ResolveField",
|
|
396
|
+
"WebSocketGateway", "SubscribeMessage", "MessageBody", "ConnectedSocket",
|
|
397
|
+
"MessagePattern", "EventPattern", "Payload", "Ctx",
|
|
398
|
+
"Cron", "Interval", "Timeout", "OnEvent",
|
|
399
|
+
"Entity", "Column", "PrimaryGeneratedColumn", "PrimaryColumn", "OneToMany", "ManyToOne", "OneToOne", "ManyToMany",
|
|
400
|
+
"JoinColumn", "JoinTable", "CreateDateColumn", "UpdateDateColumn", "DeleteDateColumn", "Index", "Unique",
|
|
401
|
+
]);
|
|
402
|
+
/**
|
|
403
|
+
* G4: Parse custom decorators on methods (e.g. @Roles('admin'), @Public(), @CurrentUser()).
|
|
404
|
+
* Returns decorator name + raw argument string (may be empty).
|
|
405
|
+
* Excludes built-in NestJS decorators (BUILTIN_DECORATORS set).
|
|
406
|
+
*/
|
|
407
|
+
function parseCustomDecorators(source) {
|
|
408
|
+
const results = [];
|
|
409
|
+
// Match @PascalCase(args) — capture name and argument text
|
|
410
|
+
const re = /@([A-Z]\w*)\s*\(([^)]*)\)/g;
|
|
411
|
+
let m;
|
|
412
|
+
while ((m = re.exec(source)) !== null) {
|
|
413
|
+
const name = m[1];
|
|
414
|
+
if (BUILTIN_DECORATORS.has(name))
|
|
415
|
+
continue;
|
|
416
|
+
// Strip quotes from simple string args for readable output
|
|
417
|
+
const args = m[2].trim().replace(/^['"`]|['"`]$/g, "");
|
|
418
|
+
results.push({ name, args });
|
|
419
|
+
}
|
|
420
|
+
return results;
|
|
421
|
+
}
|
|
422
|
+
/** G1: glob-like matching for NestJS middleware forRoutes against resolved route paths */
|
|
423
|
+
function matchMiddlewareRoute(mwRoute, routePath, routeMethod) {
|
|
424
|
+
// Method filter
|
|
425
|
+
if (mwRoute.method && mwRoute.method !== "ALL" && mwRoute.method !== routeMethod)
|
|
426
|
+
return false;
|
|
427
|
+
// Normalise both sides to leading-slash form for comparison
|
|
428
|
+
const norm = (p) => "/" + p.replace(/^\/+|\/+$/g, "");
|
|
429
|
+
const mwNorm = norm(mwRoute.path);
|
|
430
|
+
const routeNorm = norm(routePath);
|
|
431
|
+
// '*' matches everything
|
|
432
|
+
if (mwNorm === "/*" || mwRoute.path === "*")
|
|
433
|
+
return true;
|
|
434
|
+
// Convert NestJS glob (users/*) into anchored regex
|
|
435
|
+
// escape regex metachars except '*', then replace '*' with '.*'
|
|
436
|
+
const escaped = mwNorm.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
437
|
+
const re = new RegExp(`^${escaped}($|/)`);
|
|
438
|
+
return re.test(routeNorm);
|
|
439
|
+
}
|
|
440
|
+
export async function nestGuardChain(repo, options) {
|
|
441
|
+
const index = await getCodeIndex(repo);
|
|
442
|
+
if (!index)
|
|
443
|
+
throw new Error(`Repository "${repo}" not found. Index it first with index_folder.`);
|
|
444
|
+
const maxRoutes = options?.max_routes ?? 300;
|
|
445
|
+
const routes = [];
|
|
446
|
+
const errors = [];
|
|
447
|
+
let truncated = false;
|
|
448
|
+
// 1. Collect global guards/interceptors/pipes from module files + G1 middleware chains
|
|
449
|
+
const globalChain = [];
|
|
450
|
+
const middlewareEntries = [];
|
|
451
|
+
for (const file of index.files) {
|
|
452
|
+
if (!file.path.endsWith(".module.ts") && !file.path.endsWith(".module.js"))
|
|
453
|
+
continue;
|
|
454
|
+
let source;
|
|
455
|
+
try {
|
|
456
|
+
source = await readFile(join(index.root, file.path), "utf-8");
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
errors.push({ file: file.path, reason: `readFile failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const conv = extractNestConventions(source, file.path);
|
|
463
|
+
for (const g of conv.global_guards)
|
|
464
|
+
globalChain.push({ layer: "global", type: "guard", name: g.name, file: g.file });
|
|
465
|
+
for (const f of conv.global_filters)
|
|
466
|
+
globalChain.push({ layer: "global", type: "filter", name: f.name, file: f.file });
|
|
467
|
+
for (const p of conv.global_pipes)
|
|
468
|
+
globalChain.push({ layer: "global", type: "pipe", name: p.name, file: p.file });
|
|
469
|
+
for (const i of conv.global_interceptors)
|
|
470
|
+
globalChain.push({ layer: "global", type: "interceptor", name: i.name, file: i.file });
|
|
471
|
+
// G1: defensive default since middleware_chains is optional on older profiles
|
|
472
|
+
for (const mw of conv.middleware_chains ?? []) {
|
|
473
|
+
middlewareEntries.push({ middleware: mw.middleware, routes: mw.routes, file: mw.file });
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// 2. Scan controller files
|
|
477
|
+
const controllerFiles = index.files.filter((f) => f.path.endsWith(".controller.ts") || f.path.endsWith(".controller.js"));
|
|
478
|
+
const methods = ["Get", "Post", "Put", "Delete", "Patch"];
|
|
479
|
+
for (const file of controllerFiles) {
|
|
480
|
+
if (routes.length >= maxRoutes) {
|
|
481
|
+
truncated = true;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
let source;
|
|
485
|
+
try {
|
|
486
|
+
source = await readFile(join(index.root, file.path), "utf-8");
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
errors.push({ file: file.path, reason: `readFile failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
// Controller-level info
|
|
493
|
+
const ctrlMatch = /@Controller\s*\(\s*['"`]([^'"`]*)['"`]/.exec(source);
|
|
494
|
+
const ctrlPrefix = ctrlMatch?.[1] ?? "";
|
|
495
|
+
const ctrlClassMatch = /class\s+(\w+)/.exec(source);
|
|
496
|
+
const ctrlClass = ctrlClassMatch?.[1] ?? "UnknownController";
|
|
497
|
+
// Controller-level decorators (before class body — find source before first method)
|
|
498
|
+
const classIdx = source.indexOf(`class ${ctrlClass}`);
|
|
499
|
+
const ctrlHeader = classIdx >= 0 ? source.slice(0, classIdx) : "";
|
|
500
|
+
const ctrlGuards = parseUseGuards(ctrlHeader).map((n) => ({ layer: "controller", type: "guard", name: n }));
|
|
501
|
+
const ctrlInterceptors = parseUseInterceptors(ctrlHeader).map((n) => ({ layer: "controller", type: "interceptor", name: n }));
|
|
502
|
+
const ctrlPipes = parseUsePipes(ctrlHeader).map((n) => ({ layer: "controller", type: "pipe", name: n }));
|
|
503
|
+
const ctrlFilters = parseUseFilters(ctrlHeader).map((n) => ({ layer: "controller", type: "filter", name: n }));
|
|
504
|
+
const ctrlLevelChain = [...ctrlGuards, ...ctrlInterceptors, ...ctrlPipes, ...ctrlFilters];
|
|
505
|
+
// Collect ALL method decorator positions first to bound lookback correctly
|
|
506
|
+
const allMethodPositions = [];
|
|
507
|
+
for (const method of methods) {
|
|
508
|
+
const reStr = new RegExp(`@${method}\\s*\\(\\s*['"\`]([^'"\`]*)['"\`]\\s*\\)`, "g");
|
|
509
|
+
const reEmpty = new RegExp(`@${method}\\s*\\(\\s*\\)`, "g");
|
|
510
|
+
let m;
|
|
511
|
+
while ((m = reStr.exec(source)) !== null)
|
|
512
|
+
allMethodPositions.push({ method, path: m[1] ?? "", pos: m.index });
|
|
513
|
+
while ((m = reEmpty.exec(source)) !== null)
|
|
514
|
+
allMethodPositions.push({ method, path: "", pos: m.index });
|
|
515
|
+
}
|
|
516
|
+
allMethodPositions.sort((a, b) => a.pos - b.pos);
|
|
517
|
+
for (let idx = 0; idx < allMethodPositions.length; idx++) {
|
|
518
|
+
if (routes.length >= maxRoutes) {
|
|
519
|
+
truncated = true;
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
const mm = allMethodPositions[idx];
|
|
523
|
+
// Normalise: collapse slashes, trim trailing slash (except for root "/")
|
|
524
|
+
const rawPath = `/${ctrlPrefix}/${mm.path}`.replace(/\/+/g, "/");
|
|
525
|
+
const fullPath = rawPath.length > 1 ? rawPath.replace(/\/$/, "") : rawPath;
|
|
526
|
+
if (options?.path && fullPath !== options.path)
|
|
527
|
+
continue;
|
|
528
|
+
// Lookback window: from previous method decorator (or class start) to current
|
|
529
|
+
const prevEnd = idx > 0 ? allMethodPositions[idx - 1].pos + 10 : (classIdx >= 0 ? classIdx : 0);
|
|
530
|
+
const methodCtx = source.slice(Math.max(prevEnd, 0), mm.pos);
|
|
531
|
+
const methodGuards = parseUseGuards(methodCtx).map((n) => ({ layer: "method", type: "guard", name: n }));
|
|
532
|
+
const methodInterceptors = parseUseInterceptors(methodCtx).map((n) => ({ layer: "method", type: "interceptor", name: n }));
|
|
533
|
+
const methodPipes = parseUsePipes(methodCtx).map((n) => ({ layer: "method", type: "pipe", name: n }));
|
|
534
|
+
const methodFilters = parseUseFilters(methodCtx).map((n) => ({ layer: "method", type: "filter", name: n }));
|
|
535
|
+
// G4: custom decorators (e.g. @Roles('admin'), @Public()) — method-level only
|
|
536
|
+
const methodMetadata = parseCustomDecorators(methodCtx).map((d) => ({
|
|
537
|
+
layer: "method",
|
|
538
|
+
type: "metadata",
|
|
539
|
+
name: d.name,
|
|
540
|
+
...(d.args ? { args: d.args } : {}),
|
|
541
|
+
}));
|
|
542
|
+
// G1: match middleware entries against this route
|
|
543
|
+
const middlewareChain = [];
|
|
544
|
+
for (const mw of middlewareEntries) {
|
|
545
|
+
for (const mwRoute of mw.routes) {
|
|
546
|
+
if (matchMiddlewareRoute(mwRoute, fullPath, mm.method.toUpperCase())) {
|
|
547
|
+
middlewareChain.push({ layer: "middleware", type: "guard", name: mw.middleware, file: mw.file });
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
routes.push({
|
|
553
|
+
route: fullPath,
|
|
554
|
+
method: mm.method.toUpperCase(),
|
|
555
|
+
controller: ctrlClass,
|
|
556
|
+
file: file.path,
|
|
557
|
+
chain: [...globalChain, ...middlewareChain, ...ctrlLevelChain, ...methodGuards, ...methodInterceptors, ...methodPipes, ...methodFilters, ...methodMetadata],
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
routes,
|
|
563
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
564
|
+
...(truncated ? { truncated } : {}),
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
export async function nestRouteInventory(repo, options) {
|
|
568
|
+
const index = await getCodeIndex(repo);
|
|
569
|
+
if (!index)
|
|
570
|
+
throw new Error(`Repository "${repo}" not found. Index it first with index_folder.`);
|
|
571
|
+
const maxRoutes = options?.max_routes ?? 500;
|
|
572
|
+
const errors = [];
|
|
573
|
+
let truncated = false;
|
|
574
|
+
// Use findNestJSHandlers with wildcard path to get ALL routes
|
|
575
|
+
// We pass "/**" as a wildcard — but findNestJSHandlers uses matchPath which
|
|
576
|
+
// doesn't support wildcards. Instead, we scan controllers ourselves.
|
|
577
|
+
const controllerFiles = index.files.filter((f) => f.path.endsWith(".controller.ts") || f.path.endsWith(".controller.js"));
|
|
578
|
+
const routes = [];
|
|
579
|
+
const methods = ["Get", "Post", "Put", "Delete", "Patch"];
|
|
580
|
+
for (const file of controllerFiles) {
|
|
581
|
+
if (routes.length >= maxRoutes) {
|
|
582
|
+
truncated = true;
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
let source;
|
|
586
|
+
try {
|
|
587
|
+
source = await readFile(join(index.root, file.path), "utf-8");
|
|
588
|
+
}
|
|
589
|
+
catch (err) {
|
|
590
|
+
errors.push({ file: file.path, reason: `readFile failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
const ctrlMatchStr = /@Controller\s*\(\s*['"`]([^'"`]*)['"`]/.exec(source);
|
|
594
|
+
// G9: also try @Controller({ path: '...', version: '...' }) object form
|
|
595
|
+
const ctrlObjMatch = !ctrlMatchStr ? /@Controller\s*\(\s*\{[^}]*path:\s*['"`]([^'"`]*)['"`]/.exec(source) : null;
|
|
596
|
+
const ctrlMatchEmpty = !ctrlMatchStr && !ctrlObjMatch ? /@Controller\s*\(\s*\)/.exec(source) : null;
|
|
597
|
+
const ctrlPrefix = ctrlMatchStr?.[1] ?? ctrlObjMatch?.[1] ?? (ctrlMatchEmpty ? "" : "");
|
|
598
|
+
const ctrlClassMatch = /class\s+(\w+)/.exec(source);
|
|
599
|
+
const ctrlClass = ctrlClassMatch?.[1] ?? "UnknownController";
|
|
600
|
+
// Guards at controller level
|
|
601
|
+
const classIdx = source.indexOf(`class ${ctrlClass}`);
|
|
602
|
+
const ctrlHeader = classIdx >= 0 ? source.slice(0, classIdx) : "";
|
|
603
|
+
const ctrlGuards = parseUseGuards(ctrlHeader);
|
|
604
|
+
// G9: controller-level version — method can override
|
|
605
|
+
const ctrlVersion = parseControllerVersion(ctrlHeader);
|
|
606
|
+
// Collect all method decorator positions first — enables bounded lookback per method
|
|
607
|
+
const allMethodDecoratorPositions = [];
|
|
608
|
+
for (const method of methods) {
|
|
609
|
+
const reStr = new RegExp(`@${method}\\s*\\(\\s*['"\`]([^'"\`]*)['"\`]\\s*\\)`, "g");
|
|
610
|
+
const reEmpty = new RegExp(`@${method}\\s*\\(\\s*\\)`, "g");
|
|
611
|
+
let m;
|
|
612
|
+
while ((m = reStr.exec(source)) !== null) {
|
|
613
|
+
allMethodDecoratorPositions.push({ method, path: m[1] ?? "", pos: m.index, matchLen: m[0].length });
|
|
614
|
+
}
|
|
615
|
+
while ((m = reEmpty.exec(source)) !== null) {
|
|
616
|
+
allMethodDecoratorPositions.push({ method, path: "", pos: m.index, matchLen: m[0].length });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
allMethodDecoratorPositions.sort((a, b) => a.pos - b.pos);
|
|
620
|
+
for (let idx = 0; idx < allMethodDecoratorPositions.length; idx++) {
|
|
621
|
+
if (routes.length >= maxRoutes) {
|
|
622
|
+
truncated = true;
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
const mm = allMethodDecoratorPositions[idx];
|
|
626
|
+
const handler = resolveHandlerName(source, mm.pos + mm.matchLen);
|
|
627
|
+
if (!handler)
|
|
628
|
+
continue;
|
|
629
|
+
const rawPath = mm.path
|
|
630
|
+
? `/${ctrlPrefix}/${mm.path}`.replace(/\/+/g, "/")
|
|
631
|
+
: `/${ctrlPrefix}`.replace(/\/+/g, "/") || "/";
|
|
632
|
+
const fullPath = rawPath.length > 1 ? rawPath.replace(/\/$/, "") : rawPath;
|
|
633
|
+
if (routes.some((r) => r.file === file.path && r.handler === handler && r.method === mm.method.toUpperCase()))
|
|
634
|
+
continue;
|
|
635
|
+
// Bounded backward lookback: from previous decorator end to current decorator start
|
|
636
|
+
const prevEnd = idx > 0
|
|
637
|
+
? allMethodDecoratorPositions[idx - 1].pos + allMethodDecoratorPositions[idx - 1].matchLen + 20
|
|
638
|
+
: (classIdx >= 0 ? classIdx : 0);
|
|
639
|
+
routes.push(buildRouteBounded(mm.method, fullPath, handler, mm.pos, prevEnd));
|
|
640
|
+
}
|
|
641
|
+
// buildRoute is replaced by buildRouteBounded which takes the lookback start
|
|
642
|
+
function buildRouteBounded(method, fullPath, handler, methodIdx, lookbackStart) {
|
|
643
|
+
const before = source.slice(Math.max(lookbackStart, 0), methodIdx);
|
|
644
|
+
let endPos = methodIdx + 800;
|
|
645
|
+
const hNameRe = new RegExp(`\\b${handler}\\s*\\(`);
|
|
646
|
+
const hMatch = hNameRe.exec(source.slice(methodIdx, methodIdx + 800));
|
|
647
|
+
if (hMatch)
|
|
648
|
+
endPos = methodIdx + hMatch.index;
|
|
649
|
+
const after = source.slice(methodIdx, Math.min(source.length, endPos));
|
|
650
|
+
const methodCtx = before + after;
|
|
651
|
+
// Bound paramCtx to the method's own signature via paren counting —
|
|
652
|
+
// prevents leakage from adjacent methods in the same file.
|
|
653
|
+
const paramCtx = extractMethodSignature(source, endPos);
|
|
654
|
+
const methodGuards = parseUseGuards(methodCtx);
|
|
655
|
+
const allGuards = [...ctrlGuards, ...methodGuards];
|
|
656
|
+
const methodVersion = parseVersionFromContext(methodCtx);
|
|
657
|
+
const swagger = parseSwaggerFromContext(methodCtx, ctrlHeader);
|
|
658
|
+
const healthCheck = isHealthCheck(methodCtx);
|
|
659
|
+
const inlinePipes = parseInlinePipes(methodCtx);
|
|
660
|
+
const entry = {
|
|
661
|
+
method: method.toUpperCase(),
|
|
662
|
+
path: fullPath,
|
|
663
|
+
handler,
|
|
664
|
+
controller: ctrlClass,
|
|
665
|
+
file: file.path,
|
|
666
|
+
guards: allGuards,
|
|
667
|
+
params: parseParamDecorators(paramCtx),
|
|
668
|
+
};
|
|
669
|
+
const version = methodVersion ?? ctrlVersion;
|
|
670
|
+
if (version)
|
|
671
|
+
entry.version = version;
|
|
672
|
+
if (swagger)
|
|
673
|
+
entry.swagger = swagger;
|
|
674
|
+
if (healthCheck)
|
|
675
|
+
entry.is_health_check = true;
|
|
676
|
+
if (inlinePipes.length > 0)
|
|
677
|
+
entry.inline_pipes = inlinePipes;
|
|
678
|
+
return entry;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const protectedCount = routes.filter((r) => r.guards.length > 0).length;
|
|
682
|
+
return {
|
|
683
|
+
routes,
|
|
684
|
+
stats: {
|
|
685
|
+
total_routes: routes.length,
|
|
686
|
+
protected: protectedCount,
|
|
687
|
+
unprotected: routes.length - protectedCount,
|
|
688
|
+
},
|
|
689
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
690
|
+
...(truncated ? { truncated } : {}),
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Extract the method parameter signature starting at the handler name position.
|
|
695
|
+
* Paren-counts to find the matching `)` for the parameter list — prevents
|
|
696
|
+
* scanning into adjacent methods which would produce duplicate @Param/@Body.
|
|
697
|
+
*/
|
|
698
|
+
function extractMethodSignature(source, handlerNamePos) {
|
|
699
|
+
// Find the opening paren of the handler's parameter list
|
|
700
|
+
let i = handlerNamePos;
|
|
701
|
+
// Skip handler name + whitespace
|
|
702
|
+
while (i < source.length && /[\w\s]/.test(source[i]))
|
|
703
|
+
i++;
|
|
704
|
+
if (source[i] !== "(")
|
|
705
|
+
return source.slice(handlerNamePos, Math.min(source.length, handlerNamePos + 400));
|
|
706
|
+
// Paren-count to find matching close
|
|
707
|
+
let depth = 1;
|
|
708
|
+
const start = i + 1;
|
|
709
|
+
i++;
|
|
710
|
+
while (i < source.length && depth > 0) {
|
|
711
|
+
if (source[i] === "(")
|
|
712
|
+
depth++;
|
|
713
|
+
else if (source[i] === ")")
|
|
714
|
+
depth--;
|
|
715
|
+
if (depth === 0)
|
|
716
|
+
break;
|
|
717
|
+
i++;
|
|
718
|
+
}
|
|
719
|
+
return source.slice(start, i);
|
|
720
|
+
}
|
|
721
|
+
/** Parse @Param/@Body/@Query decorators from source context */
|
|
722
|
+
function parseParamDecorators(source) {
|
|
723
|
+
const params = [];
|
|
724
|
+
const re = /@(Param|Body|Query|Headers)\s*\(\s*(?:['"`](\w+)['"`])?\s*\)/g;
|
|
725
|
+
let m;
|
|
726
|
+
while ((m = re.exec(source)) !== null) {
|
|
727
|
+
params.push({ decorator: m[1], name: m[2] ?? "" });
|
|
728
|
+
}
|
|
729
|
+
return params;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Resolve the method handler name after a @Get/@Post/etc decorator.
|
|
733
|
+
* Skips intermediate stacked decorators (@Version, @ApiOperation, @UseGuards, etc.)
|
|
734
|
+
* using paren counting to handle nested args like @UsePipes(new Pipe({...})).
|
|
735
|
+
*/
|
|
736
|
+
function resolveHandlerName(source, afterDecoratorPos) {
|
|
737
|
+
let pos = afterDecoratorPos;
|
|
738
|
+
const maxScan = pos + 1000;
|
|
739
|
+
while (pos < Math.min(source.length, maxScan)) {
|
|
740
|
+
// Skip whitespace and newlines
|
|
741
|
+
while (pos < source.length && /\s/.test(source[pos]))
|
|
742
|
+
pos++;
|
|
743
|
+
if (pos >= source.length)
|
|
744
|
+
return undefined;
|
|
745
|
+
// If we hit another decorator, skip the whole decorator including args (paren count)
|
|
746
|
+
if (source[pos] === "@") {
|
|
747
|
+
pos++; // skip @
|
|
748
|
+
// skip decorator name
|
|
749
|
+
while (pos < source.length && /\w/.test(source[pos]))
|
|
750
|
+
pos++;
|
|
751
|
+
// skip args if present (paren-counted)
|
|
752
|
+
while (pos < source.length && /\s/.test(source[pos]))
|
|
753
|
+
pos++;
|
|
754
|
+
if (source[pos] === "(") {
|
|
755
|
+
let depth = 1;
|
|
756
|
+
pos++;
|
|
757
|
+
while (pos < source.length && depth > 0) {
|
|
758
|
+
if (source[pos] === "(")
|
|
759
|
+
depth++;
|
|
760
|
+
else if (source[pos] === ")")
|
|
761
|
+
depth--;
|
|
762
|
+
pos++;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
// Skip keywords like async, public, private
|
|
768
|
+
const kwMatch = /^(?:async|public|private|protected)\s+/.exec(source.slice(pos));
|
|
769
|
+
if (kwMatch) {
|
|
770
|
+
pos += kwMatch[0].length;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
// Should now be at the method name
|
|
774
|
+
const nameMatch = /^(\w+)\s*\(/.exec(source.slice(pos));
|
|
775
|
+
if (nameMatch)
|
|
776
|
+
return nameMatch[1];
|
|
777
|
+
return undefined;
|
|
778
|
+
}
|
|
779
|
+
return undefined;
|
|
780
|
+
}
|
|
781
|
+
/** G9: Extract @Version('n') or @Controller({ version: 'n' }) — method-level takes precedence */
|
|
782
|
+
function parseVersionFromContext(methodCtx) {
|
|
783
|
+
const m = /@Version\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/.exec(methodCtx);
|
|
784
|
+
return m ? m[1] : undefined;
|
|
785
|
+
}
|
|
786
|
+
function parseControllerVersion(ctrlHeader) {
|
|
787
|
+
const m = /@Controller\s*\(\s*\{[^}]*version:\s*['"`]([^'"`]+)['"`]/.exec(ctrlHeader);
|
|
788
|
+
return m ? m[1] : undefined;
|
|
789
|
+
}
|
|
790
|
+
/** G10: Extract Swagger annotations */
|
|
791
|
+
function parseSwaggerFromContext(methodCtx, ctrlHeader) {
|
|
792
|
+
const result = {};
|
|
793
|
+
const summaryMatch = /@ApiOperation\s*\(\s*\{[^}]*summary:\s*['"`]([^'"`]+)['"`]/.exec(methodCtx);
|
|
794
|
+
if (summaryMatch)
|
|
795
|
+
result.summary = summaryMatch[1];
|
|
796
|
+
if (/@ApiBearerAuth\s*\(/.test(methodCtx) || /@ApiBearerAuth\s*\(/.test(ctrlHeader)) {
|
|
797
|
+
result.bearer = true;
|
|
798
|
+
}
|
|
799
|
+
const tagsMatch = /@ApiTags\s*\(\s*((?:['"`][^'"`]+['"`]\s*,?\s*)+)\)/.exec(ctrlHeader);
|
|
800
|
+
if (tagsMatch) {
|
|
801
|
+
const tags = [...tagsMatch[1].matchAll(/['"`]([^'"`]+)['"`]/g)].map((m) => m[1]);
|
|
802
|
+
if (tags.length > 0)
|
|
803
|
+
result.tags = tags;
|
|
804
|
+
}
|
|
805
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
806
|
+
}
|
|
807
|
+
/** G13: Check for @HealthCheck() decorator on method */
|
|
808
|
+
function isHealthCheck(methodCtx) {
|
|
809
|
+
return /@HealthCheck\s*\(\s*\)/.test(methodCtx);
|
|
810
|
+
}
|
|
811
|
+
/** G11: Extract inline pipe constructions from @UsePipes(new ValidationPipe(...)) */
|
|
812
|
+
function parseInlinePipes(methodCtx) {
|
|
813
|
+
const pipes = [];
|
|
814
|
+
const re = /@UsePipes\s*\(\s*new\s+(\w+Pipe)\s*\(/g;
|
|
815
|
+
let m;
|
|
816
|
+
while ((m = re.exec(methodCtx)) !== null)
|
|
817
|
+
pipes.push(m[1]);
|
|
818
|
+
return pipes;
|
|
819
|
+
}
|
|
820
|
+
const ALL_NEST_CHECKS = [
|
|
821
|
+
"modules", "routes", "di", "guards", "lifecycle", "patterns",
|
|
822
|
+
"graphql", "websocket", "schedule", "typeorm", "microservice",
|
|
823
|
+
];
|
|
824
|
+
export async function nestAudit(repo, options) {
|
|
825
|
+
const index = await getCodeIndex(repo);
|
|
826
|
+
if (!index)
|
|
827
|
+
throw new Error(`Repository "${repo}" not found. Index it first with index_folder.`);
|
|
828
|
+
// Check if this is a NestJS repo
|
|
829
|
+
const { detectFrameworks } = await import("../utils/framework-detect.js");
|
|
830
|
+
const frameworks = detectFrameworks(index);
|
|
831
|
+
if (!frameworks.has("nestjs")) {
|
|
832
|
+
return {
|
|
833
|
+
framework_detected: false,
|
|
834
|
+
summary: { total_routes: 0, cycles: 0, violations: 0, anti_pattern_hits: 0, failed_checks: 0, truncated_checks: [] },
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const enabledChecks = new Set((options?.checks ?? [...ALL_NEST_CHECKS]));
|
|
838
|
+
const tasks = [];
|
|
839
|
+
if (enabledChecks.has("lifecycle")) {
|
|
840
|
+
tasks.push(nestLifecycleMap(repo).then((r) => ({ name: "lifecycle", result: r }))
|
|
841
|
+
.catch((e) => ({ name: "lifecycle", error: e instanceof Error ? e.message : String(e) })));
|
|
842
|
+
}
|
|
843
|
+
if (enabledChecks.has("modules")) {
|
|
844
|
+
tasks.push(nestModuleGraph(repo).then((r) => ({ name: "modules", result: r }))
|
|
845
|
+
.catch((e) => ({ name: "modules", error: e instanceof Error ? e.message : String(e) })));
|
|
846
|
+
}
|
|
847
|
+
if (enabledChecks.has("di")) {
|
|
848
|
+
tasks.push(nestDIGraph(repo).then((r) => ({ name: "di", result: r }))
|
|
849
|
+
.catch((e) => ({ name: "di", error: e instanceof Error ? e.message : String(e) })));
|
|
850
|
+
}
|
|
851
|
+
if (enabledChecks.has("guards")) {
|
|
852
|
+
tasks.push(nestGuardChain(repo).then((r) => ({ name: "guards", result: r }))
|
|
853
|
+
.catch((e) => ({ name: "guards", error: e instanceof Error ? e.message : String(e) })));
|
|
854
|
+
}
|
|
855
|
+
if (enabledChecks.has("routes")) {
|
|
856
|
+
tasks.push(nestRouteInventory(repo).then((r) => ({ name: "routes", result: r }))
|
|
857
|
+
.catch((e) => ({ name: "routes", error: e instanceof Error ? e.message : String(e) })));
|
|
858
|
+
}
|
|
859
|
+
if (enabledChecks.has("patterns")) {
|
|
860
|
+
tasks.push((async () => {
|
|
861
|
+
const { searchPatterns, listPatterns } = await import("./pattern-tools.js");
|
|
862
|
+
const nestPatterns = listPatterns().filter((p) => p.name.startsWith("nest-"));
|
|
863
|
+
const results = [];
|
|
864
|
+
for (const p of nestPatterns) {
|
|
865
|
+
const r = await searchPatterns(repo, p.name);
|
|
866
|
+
if (r.matches.length > 0)
|
|
867
|
+
results.push({ pattern: p.name, count: r.matches.length });
|
|
868
|
+
}
|
|
869
|
+
return { name: "patterns", result: results };
|
|
870
|
+
})().catch((e) => ({ name: "patterns", error: e instanceof Error ? e.message : String(e) })));
|
|
871
|
+
}
|
|
872
|
+
// Wave 2 checks — lazy import to avoid eager loading if nest-ext-tools isn't needed
|
|
873
|
+
if (enabledChecks.has("graphql")) {
|
|
874
|
+
tasks.push((async () => {
|
|
875
|
+
const { nestGraphQLMap } = await import("./nest-ext-tools.js");
|
|
876
|
+
return { name: "graphql", result: await nestGraphQLMap(repo) };
|
|
877
|
+
})().catch((e) => ({ name: "graphql", error: e instanceof Error ? e.message : String(e) })));
|
|
878
|
+
}
|
|
879
|
+
if (enabledChecks.has("websocket")) {
|
|
880
|
+
tasks.push((async () => {
|
|
881
|
+
const { nestWebSocketMap } = await import("./nest-ext-tools.js");
|
|
882
|
+
return { name: "websocket", result: await nestWebSocketMap(repo) };
|
|
883
|
+
})().catch((e) => ({ name: "websocket", error: e instanceof Error ? e.message : String(e) })));
|
|
884
|
+
}
|
|
885
|
+
if (enabledChecks.has("schedule")) {
|
|
886
|
+
tasks.push((async () => {
|
|
887
|
+
const { nestScheduleMap } = await import("./nest-ext-tools.js");
|
|
888
|
+
return { name: "schedule", result: await nestScheduleMap(repo) };
|
|
889
|
+
})().catch((e) => ({ name: "schedule", error: e instanceof Error ? e.message : String(e) })));
|
|
890
|
+
}
|
|
891
|
+
if (enabledChecks.has("typeorm")) {
|
|
892
|
+
tasks.push((async () => {
|
|
893
|
+
const { nestTypeOrmMap } = await import("./nest-ext-tools.js");
|
|
894
|
+
return { name: "typeorm", result: await nestTypeOrmMap(repo) };
|
|
895
|
+
})().catch((e) => ({ name: "typeorm", error: e instanceof Error ? e.message : String(e) })));
|
|
896
|
+
}
|
|
897
|
+
if (enabledChecks.has("microservice")) {
|
|
898
|
+
tasks.push((async () => {
|
|
899
|
+
const { nestMicroserviceMap } = await import("./nest-ext-tools.js");
|
|
900
|
+
return { name: "microservice", result: await nestMicroserviceMap(repo) };
|
|
901
|
+
})().catch((e) => ({ name: "microservice", error: e instanceof Error ? e.message : String(e) })));
|
|
902
|
+
}
|
|
903
|
+
const settled = await Promise.all(tasks);
|
|
904
|
+
// Aggregate
|
|
905
|
+
const auditErrors = [];
|
|
906
|
+
const warnings = [];
|
|
907
|
+
const truncatedChecks = [];
|
|
908
|
+
let lifecycleResult;
|
|
909
|
+
let moduleResult;
|
|
910
|
+
let diResult;
|
|
911
|
+
let guardResult;
|
|
912
|
+
let routeResult;
|
|
913
|
+
let patternResults;
|
|
914
|
+
let graphqlResult;
|
|
915
|
+
let websocketResult;
|
|
916
|
+
let scheduleResult;
|
|
917
|
+
let typeormResult;
|
|
918
|
+
let microserviceResult;
|
|
919
|
+
for (const item of settled) {
|
|
920
|
+
if (item.error) {
|
|
921
|
+
auditErrors.push({ check: item.name, reason: item.error });
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
switch (item.name) {
|
|
925
|
+
case "lifecycle":
|
|
926
|
+
lifecycleResult = item.result;
|
|
927
|
+
break;
|
|
928
|
+
case "modules": {
|
|
929
|
+
const r = item.result;
|
|
930
|
+
moduleResult = r;
|
|
931
|
+
if (r.truncated)
|
|
932
|
+
truncatedChecks.push("modules");
|
|
933
|
+
if (r.errors)
|
|
934
|
+
warnings.push(...r.errors);
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
case "di": {
|
|
938
|
+
const r = item.result;
|
|
939
|
+
diResult = r;
|
|
940
|
+
if (r.truncated)
|
|
941
|
+
truncatedChecks.push("di");
|
|
942
|
+
if (r.errors)
|
|
943
|
+
warnings.push(...r.errors);
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
case "guards": {
|
|
947
|
+
const r = item.result;
|
|
948
|
+
guardResult = r;
|
|
949
|
+
if (r.truncated)
|
|
950
|
+
truncatedChecks.push("guards");
|
|
951
|
+
if (r.errors)
|
|
952
|
+
warnings.push(...r.errors);
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
case "routes": {
|
|
956
|
+
const r = item.result;
|
|
957
|
+
routeResult = r;
|
|
958
|
+
if (r.truncated)
|
|
959
|
+
truncatedChecks.push("routes");
|
|
960
|
+
if (r.errors)
|
|
961
|
+
warnings.push(...r.errors);
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
case "patterns":
|
|
965
|
+
patternResults = item.result;
|
|
966
|
+
break;
|
|
967
|
+
case "graphql": {
|
|
968
|
+
const r = item.result;
|
|
969
|
+
graphqlResult = r;
|
|
970
|
+
if (r.truncated)
|
|
971
|
+
truncatedChecks.push("graphql");
|
|
972
|
+
if (r.errors)
|
|
973
|
+
warnings.push(...r.errors);
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
case "websocket": {
|
|
977
|
+
const r = item.result;
|
|
978
|
+
websocketResult = r;
|
|
979
|
+
if (r.truncated)
|
|
980
|
+
truncatedChecks.push("websocket");
|
|
981
|
+
if (r.errors)
|
|
982
|
+
warnings.push(...r.errors);
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
985
|
+
case "schedule": {
|
|
986
|
+
const r = item.result;
|
|
987
|
+
scheduleResult = r;
|
|
988
|
+
if (r.truncated)
|
|
989
|
+
truncatedChecks.push("schedule");
|
|
990
|
+
if (r.errors)
|
|
991
|
+
warnings.push(...r.errors);
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
case "typeorm": {
|
|
995
|
+
const r = item.result;
|
|
996
|
+
typeormResult = r;
|
|
997
|
+
if (r.truncated)
|
|
998
|
+
truncatedChecks.push("typeorm");
|
|
999
|
+
if (r.errors)
|
|
1000
|
+
warnings.push(...r.errors);
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
case "microservice": {
|
|
1004
|
+
const r = item.result;
|
|
1005
|
+
microserviceResult = r;
|
|
1006
|
+
if (r.truncated)
|
|
1007
|
+
truncatedChecks.push("microservice");
|
|
1008
|
+
if (r.errors)
|
|
1009
|
+
warnings.push(...r.errors);
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const totalRoutes = routeResult?.stats.total_routes ?? 0;
|
|
1015
|
+
const cycles = (moduleResult?.circular_deps.length ?? 0) + (diResult?.cycles.length ?? 0) + (typeormResult?.cycles.length ?? 0);
|
|
1016
|
+
const antiPatternHits = patternResults?.reduce((sum, p) => sum + p.count, 0) ?? 0;
|
|
1017
|
+
return {
|
|
1018
|
+
framework_detected: true,
|
|
1019
|
+
...(lifecycleResult ? { lifecycle_map: lifecycleResult } : {}),
|
|
1020
|
+
...(moduleResult ? { module_graph: moduleResult } : {}),
|
|
1021
|
+
...(diResult ? { di_graph: diResult } : {}),
|
|
1022
|
+
...(guardResult ? { guard_chain: guardResult } : {}),
|
|
1023
|
+
...(routeResult ? { route_inventory: routeResult } : {}),
|
|
1024
|
+
...(graphqlResult ? { graphql_map: graphqlResult } : {}),
|
|
1025
|
+
...(websocketResult ? { websocket_map: websocketResult } : {}),
|
|
1026
|
+
...(scheduleResult ? { schedule_map: scheduleResult } : {}),
|
|
1027
|
+
...(typeormResult ? { typeorm_map: typeormResult } : {}),
|
|
1028
|
+
...(microserviceResult ? { microservice_map: microserviceResult } : {}),
|
|
1029
|
+
...(patternResults ? { anti_patterns: patternResults } : {}),
|
|
1030
|
+
summary: {
|
|
1031
|
+
total_routes: totalRoutes,
|
|
1032
|
+
cycles,
|
|
1033
|
+
violations: 0, // TODO: boundary violations from module_graph
|
|
1034
|
+
anti_pattern_hits: antiPatternHits,
|
|
1035
|
+
failed_checks: auditErrors.length,
|
|
1036
|
+
truncated_checks: truncatedChecks,
|
|
1037
|
+
},
|
|
1038
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
1039
|
+
...(auditErrors.length > 0 ? { errors: auditErrors } : {}),
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
//# sourceMappingURL=nest-tools.js.map
|