diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 3d6853f..0aba2c8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -6,7 +6,8 @@ "mcp__sequential__sequentialthinking", "Read(/mnt/d/git-org/tacit-code/claude-code-project-index/**)", "Read(/mnt/d/git-org/tacit-code/claude-code-project-index/**)", - "Read(/mnt/d/git-org/tacit-code/claude-code-project-index/scripts/**)" + "Read(/mnt/d/git-org/tacit-code/claude-code-project-index/scripts/**)", + "Bash(npm run build:*)" ], "deny": [], "ask": [], diff --git a/.eslintrc.json b/.eslintrc.json index 413214d..b8c1309 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,42 +1,42 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2022, - "sourceType": "module", - "project": "./tsconfig.json" - }, - "plugins": [ - "@typescript-eslint" - ], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "env": { - "node": true, - "es2022": true, - "jest": true - }, - "ignorePatterns": [ - "dist/**", - "node_modules/**", - "coverage/**", - "*.js", - "*.d.ts" - ], - "rules": { - "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-unused-vars": ["error", { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_" - }], - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "no-console": ["warn", { - "allow": ["warn", "error", "log"] - }], - "prefer-const": "error", - "no-var": "error" - } +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module", + "project": "./tsconfig.json" + }, + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "env": { + "node": true, + "es2022": true, + "jest": true + }, + "ignorePatterns": [ + "dist/**", + "node_modules/**", + "coverage/**", + "*.js", + "*.d.ts" + ], + "rules": { + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": ["error", { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + }], + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "no-console": ["warn", { + "allow": ["warn", "error", "log"] + }], + "prefer-const": "error", + "no-var": "error" + } } \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1a2845d..8b7fd21 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,28 +1,28 @@ -name: CI - - on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - - jobs: - test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: '18' - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Run tests - run: npm test +name: CI + + on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + + jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Run tests + run: npm test diff --git a/.gitignore b/.gitignore index 774eb1f..14c1669 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,44 @@ -# Node modules -node_modules/ - -# Build output -dist/ -build/ - -# Cache directories -.indexer-cache/ - -# Generated output files (new organized structure) -.indexer-output/ - -# Legacy output files (in case they still exist) -PROJECT_INDEX.json -CODE_INDEX.md -dependencies.dot -dependencies.mmd -dependencies.png -project-tree.txt -project-table.txt -service-boxes.txt -*.html - -# Test coverage -coverage/ - -# Environment files -.env -.env.local - -# IDE files -.vscode/ -.idea/ - -# OS files -.DS_Store -Thumbs.db - -# Logs -*.log -npm-debug.log* -yarn-debug.log* +# Node modules +node_modules/ + +# Build output +dist/ +build/ + +# Cache directories +.indexer-cache/ + +# Generated output files (new organized structure) +.indexer-output/ + +# Legacy output files (in case they still exist) +PROJECT_INDEX.json +CODE_INDEX.md +dependencies.dot +dependencies.mmd +dependencies.png +project-tree.txt +project-table.txt +service-boxes.txt +*.html + +# Test coverage +coverage/ + +# Environment files +.env +.env.local + +# IDE files +.vscode/ +.idea/ + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* +yarn-debug.log* yarn-error.log* \ No newline at end of file diff --git a/.indexerignore b/.indexerignore index 6f53333..a82117c 100644 --- a/.indexerignore +++ b/.indexerignore @@ -1,115 +1,115 @@ -# Dependencies -node_modules/ -vendor/ -bower_components/ - -# Build outputs -dist/ -build/ -out/ -target/ -*.exe -*.dll -*.so -*.dylib - -# IDE and editor files -.idea/ -.vscode/ -*.swp -*.swo -*~ -.DS_Store -Thumbs.db - -# Test coverage -coverage/ -.nyc_output/ -*.lcov - -# Logs -*.log -logs/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Environment files -.env -.env.local -.env.*.local - -# Temporary files -tmp/ -temp/ -*.tmp -*.temp - -# Cache directories -.cache/ -.indexer-cache/ -.parcel-cache/ -.next/ -.nuxt/ - -# Large binary files -*.zip -*.tar -*.gz -*.rar -*.7z -*.dmg -*.iso -*.jar -*.war - -# Media files -*.mp4 -*.mp3 -*.avi -*.mov -*.wmv -*.flv -*.jpg -*.jpeg -*.png -*.gif -*.ico -*.svg -*.webp - -# Database files -*.sqlite -*.db -*.mdb - -# Office documents -*.doc -*.docx -*.xls -*.xlsx -*.ppt -*.pptx -*.pdf - -# Project specific -PROJECT_INDEX.json -PROJECT_INDEX.*.json -.indexer-report.html - -# Don't index the indexer itself when running from parent directory -indexer/** - -# Minified files -*.min.js -*.min.css -*.bundle.js -*.bundle.css - -# Source maps -*.map - -# Lock files (optional - uncomment if desired) -# package-lock.json -# yarn.lock +# Dependencies +node_modules/ +vendor/ +bower_components/ + +# Build outputs +dist/ +build/ +out/ +target/ +*.exe +*.dll +*.so +*.dylib + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store +Thumbs.db + +# Test coverage +coverage/ +.nyc_output/ +*.lcov + +# Logs +*.log +logs/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment files +.env +.env.local +.env.*.local + +# Temporary files +tmp/ +temp/ +*.tmp +*.temp + +# Cache directories +.cache/ +.indexer-cache/ +.parcel-cache/ +.next/ +.nuxt/ + +# Large binary files +*.zip +*.tar +*.gz +*.rar +*.7z +*.dmg +*.iso +*.jar +*.war + +# Media files +*.mp4 +*.mp3 +*.avi +*.mov +*.wmv +*.flv +*.jpg +*.jpeg +*.png +*.gif +*.ico +*.svg +*.webp + +# Database files +*.sqlite +*.db +*.mdb + +# Office documents +*.doc +*.docx +*.xls +*.xlsx +*.ppt +*.pptx +*.pdf + +# Project specific +PROJECT_INDEX.json +PROJECT_INDEX.*.json +.indexer-report.html + +# Don't index the indexer itself when running from parent directory +indexer/** + +# Minified files +*.min.js +*.min.css +*.bundle.js +*.bundle.css + +# Source maps +*.map + +# Lock files (optional - uncomment if desired) +# package-lock.json +# yarn.lock # pnpm-lock.yaml \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 9f57655..e69de29 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,316 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Repository Overview - -The @cloneglobal/indexer is a high-performance TypeScript CLI tool that automatically analyzes codebases. Version 2.0.2 with major cleanup completed (September 2025). Removed 260+ dead code instances, consolidated parsers, replaced all console.logs with Logger class. - -## ⚡ Quick Start for Claude Instances - -If you're a new Claude instance working on this project, here's what you need to know: - -1. **Code Quality**: Clean codebase - removed 260+ dead code instances -2. **Build Status**: ~80 TypeScript errors (missing type definitions) -3. **Test Coverage**: ~25% (6 test suites passing, target: 80%) -4. **Performance**: 3.6x faster with Worker Threads -5. **Primary Files**: - - Core logic: `src/core/indexer.ts`, `src/core/smart-indexer.ts` - - Worker Pool: `src/core/worker-pool.ts`, `src/core/parser-worker.ts` - - CLI entry: `src/cli/index.ts` - - Config: `src/core/config.ts` -6. **Run Tests**: `npm test` (6 test suites working) - -### What It Does & Why It Exists - -The indexer solves a critical problem: **giving AI assistants instant, comprehensive understanding of entire codebases without reading every file**. - -**How it works:** -1. Parses every source file using language-specific AST parsers -2. Uses Worker Threads for parallel processing on multi-core systems -3. Extracts functions, classes, imports, exports, and dependencies -4. Builds a searchable knowledge graph of code relationships -5. Outputs a single `PROJECT_INDEX.json` file containing everything -6. AI assistants read this one file instead of thousands of source files - -**Key value propositions:** -- **AI Context Efficiency**: Claude understands 10,000+ files from one JSON -- **Enterprise Performance**: 10,000 files indexed in ~25 seconds -- **Cross-Repository Intelligence**: Tracks how frontend calls backend APIs across repos -- **Real-time Updates**: File watcher maintains current index as you code -- **Modern Architecture**: Worker Threads, async I/O, ES2023 optimizations - -## Recent Changes (September 2025) - -### Major Code Cleanup Completed -- ✅ **Dead Code Removal**: Eliminated 67 unused functions, 145 unused exports, 48 unused imports -- ✅ **VS Code Extension**: Removed entirely to focus on core indexing -- ✅ **Parser Consolidation**: Merged 3 Python parsers into 1 Tree-sitter parser -- ✅ **Logger Implementation**: Replaced all 398 console.logs with Logger class -- ✅ **Syntax Fixes**: Fixed 9 files with cleanup-related syntax errors -- ✅ **CLI Simplification**: Reduced from 1,181 lines to 186 lines - -## First-Run Experience - -The indexer includes a welcome wizard for new users (`src/cli/first-run.ts`): -- Automatically detects first-time usage -- Shows welcome message with quick start guide -- Displays success message after first index creation -- Creates `~/.indexer-initialized` marker file - -## Essential Commands - -### Build & Development -```bash -npm run build # TypeScript compilation to dist/ -npm run dev # Watch mode for development -npm run clean # Remove dist and cache directories -npm test # Run Jest unit tests -npm run test:unit # Same as npm test -npm run test:e2e # Run Cypress E2E tests -npm run test:open # Open Cypress interactive UI -npm run test:coverage # Generate coverage report -npm run lint # ESLint analysis -npm run format # Prettier code formatting -``` - -### API Server Commands -```bash -npm run api # Build and start API server -npm run api:dev # API server with auth disabled (development) -npm run api:prod # API server with auth enabled (production) -``` - -### CLI Commands (after build) -```bash -# Smart Mode (recommended - does everything automatically) -indexer # Analyzes everything, generates all outputs -indexer init # Same as above (defaults to smart mode) - -# Interactive Chat -indexer chat # Start interactive Claude Code chat about your codebase - -# Core commands -indexer scan [directory] # Index with Worker Threads -indexer watch # Manual file watching -indexer query "pattern" # Search across codebase -indexer health # Check project health -indexer stats # View statistics - -# Export formats -indexer export json|graphviz|markdown|mermaid|ascii - -# Integrations -indexer api # Start REST/GraphQL/WebSocket server -indexer slack --start # Start Slack bot monitoring -indexer hook --install # Install Claude Code hooks -``` - -### Running Tests -```bash -DEBUG=indexer:* npm run dev -npm test -- parsers/javascript.test.ts --verbose -``` - -### Testing Cache Behavior -```javascript -const cache = new CacheManager({ - maxSize: 100 * 1024 * 1024, // 100MB - ttl: 60 * 60 * 1000 // 1 hour -}); -``` - -### Monitoring Performance -```bash -indexer stats --json > stats.json -node --inspect dist/cli/index.js scan -``` - -## Current Implementation Status - -### ✅ Completed Features (v2.0.2) -- ✅ **Dead Code Cleanup** - Removed 260+ instances of unused code -- ✅ **Logger Implementation** - Replaced all 398 console.logs -- ✅ **Call Graph Analysis** - Bidirectional function call tracking with dead code detection -- ✅ **AI-Optimized Compression** - 50-70% size reduction with token-aware sizing -- ✅ **Unified JSON Exporter** - Single exporter supporting standard/compressed/minified formats -- ✅ **Impact Analysis** - Shows which functions are affected by changes -- ✅ **Circular Dependency Detection** - Finds and reports dependency cycles -- ✅ SmartIndexer with automatic orchestration -- ✅ Multi-language parsing (9 languages) -- ✅ Tree-sitter Python parser (consolidated from 3 parsers) -- ✅ LRU Cache Manager with 500MB limit (properly integrated) -- ✅ Worker Threads for parallel parsing -- ✅ Async I/O throughout codebase - -### Known Issues & Technical Debt - -**🔴 Critical Issues (Must Fix Before Production)** -1. **TypeScript Build Errors**: ~80 errors need fixing - - Missing: @types/jsonwebtoken, @types/bcrypt - - Import paths: ../../utils/logger should be ../utils/logger - - Null safety: Controllers need null checks -2. **Test Coverage Gap**: ~25% coverage vs 80% target - missing parser and API tests -3. **Configuration Hardcoding**: 15+ hardcoded ports/timeouts need environment config - -**🟡 High Priority Issues** -1. **Input Validation Missing**: No validation on CLI commands or API inputs (security risk) -2. **Environment Variable Validation**: Missing vars cause crashes, needs startup checks - -**🟠 Medium Priority Issues** -1. **No Incremental Parsing**: Full re-parse takes 13.2s (target: <100ms for single file) -2. **Code Quality**: Several 100+ line functions, deep nesting, magic numbers - -## Configuration - -### .indexer.yml (placed in target project root) -```yaml -version: 2 -name: project-name - -include: - - "**/*.{js,jsx,ts,tsx,py,go,sql,graphql,yaml,astro}" -ignore: - - "**/node_modules/**" - - "**/dist/**" - -performance: - parallel: 8 # Worker threads - useWorkers: true # Enable Worker pool - maxMemory: 1000 # MB - cache: true - -export: - outputDirectory: .indexer-output - formats: - json: - path: indexes/PROJECT_INDEX.json -``` - -## Environment Variables - -```bash -# Core (Node.js 20+ required) -NODE_OPTIONS="--max-old-space-size=4096" -UV_THREADPOOL_SIZE=16 - -# Performance -INDEXER_PARALLEL=8 # Worker threads -INDEXER_USE_WORKERS=true # Enable Worker pool -INDEXER_MAX_MEMORY=1000 # MB - -# AI Features (required for AI analysis) -ANTHROPIC_API_KEY=sk-ant-... # Uses Claude Opus 4.1 model exclusively -CLAUDE_MODEL=claude-opus-4-1-20250805 # Optional override - -# Integrations (optional) -SLACK_BOT_TOKEN=xoxb-... -LINEAR_API_KEY=lin_api_... -DD_AGENT_HOST=localhost -DD_DOGSTATSD_PORT=8125 -GITHUB_TOKEN=ghp_... -``` - -## Development Guidelines - -### Adding a New Language Parser -1. Create parser class in `src/parsers/[language].ts` implementing `Parser` interface -2. Register in `src/parsers/index.ts` -3. Add tests in `test/parsers/[language].test.ts` - -### TypeScript Configuration -- Target: ES2023 (updated from ES2022) -- Module: CommonJS -- Strict mode enabled (but `noImplicitAny: false`) -- Source maps enabled -- Declaration files generated - -### Testing Requirements -- Coverage thresholds: 80% lines, 80% functions, 70% branches -- Test files: `*.test.ts` or `*.spec.ts` in `src/` or `test/` -- Setup file: `test/setup.ts` -- Jest with ts-jest preset -- Test timeout: 10 seconds - -### Performance Optimization -- Parallel processing with up to 8 Worker threads -- LRU cache to prevent memory leaks (500MB limit) -- 2MB max file size limit -- Incremental mode for large codebases -- Async I/O for all file operations - -## Common Tasks - -### Debugging Parser Issues -```bash -DEBUG=indexer:* npm run dev -npm test -- parsers/javascript.test.ts --verbose -``` - -### Installing Missing Type Definitions -```bash -npm install --save-dev @types/jsonwebtoken @types/bcrypt -``` - -### Fixing Import Path Issues -Most `../../utils/logger` imports should be `../utils/logger` - -## Performance Benchmarks - -- 100 files: ~0.7s, 50MB memory (was ~2s) -- 1,000 files: ~5s, 100MB memory (was ~15s) -- 10,000 files: ~25s, 180MB memory (was ~90s) -- Query response: <50ms -- File change update: <100ms - -## Key Dependencies - -- **@anthropic-ai/sdk**: ^0.32.1 - Claude SDK -- **@babel/parser**: JavaScript/TypeScript AST parsing -- **tree-sitter**: ^0.21.1 - AST parsing framework -- **tree-sitter-python**: ^0.23.4 - Python grammar -- **commander**: ^12.0.0 - CLI framework -- **chokidar**: ^3.6.0 - File watching -- **express**: ^4.21.1 - Web framework -- **worker_threads**: Node.js built-in for parallel processing - -## API Endpoints - -### REST Endpoints -``` -POST /api/ai/analyze # Comprehensive analysis -POST /api/ai/predict-bugs # Bug prediction -POST /api/ai/detect-smells # Code smell detection -POST /api/ai/analyze-security # Security scan -POST /api/ai/generate-tests # Test generation - -GET /api/stream/analyze # SSE streaming -GET /api/stream/progress/:id # Progress tracking - -POST /api/agents/security # Security agent -POST /api/agents/performance # Performance agent -``` - -### GraphQL Schema -Available at `/graphql` when API server is running. - -## Important Notes - -### Worker Thread Architecture -The indexer automatically uses Worker Threads for projects with >50 files: -- Main thread coordinates work distribution -- Worker threads parse files in parallel -- Automatic scaling based on CPU cores (max 8 workers) -- ~3.6x performance improvement for large codebases - -### Memory Management -- LRU cache with 500MB limit prevents memory leaks -- Automatic cache eviction for least recently used items -- File size limit of 2MB per file -- Configurable memory limits via environment variables - -### Error Recovery -- All parser operations wrapped in try-catch -- Continues indexing even if individual files fail -- Error details collected and reported at end -- Fallback to basic text analysis for unparseable files \ No newline at end of file diff --git a/CLEANUP-REPORT.md b/CLEANUP-REPORT.md index 61ec19d..58a1d74 100644 --- a/CLEANUP-REPORT.md +++ b/CLEANUP-REPORT.md @@ -1,179 +1,179 @@ -# Indexer Cleanup Report -Generated: 2025-09-15 - -## Executive Summary - -Comprehensive cleanup of @cloneglobal/indexer codebase completed, addressing critical bugs and architectural inconsistencies identified in initial analysis. - -## Completed Improvements - -### Phase 1: Critical Bug Fixes ✅ - -#### 1. Logger Implementation (398 replacements) -- **Before**: 408 console.log statements scattered across codebase -- **After**: Proper Logger class with levels (debug, info, warn, error, success) -- **Files Updated**: 26 files -- **Benefits**: - - Production-ready logging with environment-based levels - - Consistent log formatting with timestamps - - Color-coded output for better readability - -#### 2. Parser Interface Standardization ✅ -- **Before**: Duplicate Parser interfaces (Parser vs IParser confusion) -- **After**: Single Parser interface in types/index.ts -- **Files Updated**: 11 parser files -- **Benefits**: - - Type safety across all language parsers - - Consistent API for parser implementations - - Reduced code duplication - -#### 3. Exporter Interface Standardization ✅ -- **Before**: Inconsistent return types (JSON returned string, others void) -- **After**: Common `Exporter` interface with void return -- **Files Updated**: 5 exporter files -- **Benefits**: - - Predictable exporter behavior - - Type-safe option handling - - Easier to add new export formats - -### Phase 2: Architecture Improvements - -#### 1. Import Path Corrections ✅ -- Fixed 40+ incorrect import paths -- Standardized relative path usage -- Eliminated circular dependencies - -#### 2. Interface Extraction ✅ -- Created common Exporter interface -- Moved shared interfaces to types/index.ts -- Reduced interface duplication by 60% - -## Metrics - -| Metric | Before | After | Improvement | -|--------|--------|-------|------------| -| Console.log statements | 408 | 10 | -97.5% | -| Duplicate interfaces | 5 | 0 | -100% | -| Import errors | 45 | 0 | -100% | -| TypeScript errors | 150+ | 78 | -48% | -| Code duplication | High | Low | ~60% reduction | - -## Code Quality Improvements - -### Maintainability -- **Before**: Score 6/10 - Multiple console.logs, duplicate code -- **After**: Score 8.5/10 - Clean logging, DRY principles - -### Type Safety -- **Before**: Mixed interface usage, type inconsistencies -- **After**: Strong typing with single source of truth - -### Developer Experience -- **Before**: Confusing duplicate interfaces, unclear logging -- **After**: Clear interfaces, professional logging system - -## File Changes Summary - -### Most Impacted Files -1. `src/cli/index.ts` - 148 console.log replacements -2. `src/cli/claude-chat.ts` - 59 replacements -3. `src/core/smart-indexer.ts` - 42 replacements -4. All parser files - Interface standardization -5. All exporter files - Return type fixes - -### New/Modified Core Files -- `src/types/index.ts` - Added Parser and Exporter interfaces -- `src/utils/logger.ts` - Already existed, now properly used -- `scripts/replace-console-logs.ts` - Automation script created - -## Remaining Technical Debt - -### High Priority (78 TypeScript errors) -- GraphQL resolver type mismatches -- Authentication middleware return types -- API route parameter validation - -### Medium Priority -- Large file refactoring (CLI: 1,181 lines) -- Duplicate Python parsers consolidation -- TypeScript config conflicts (strict vs noImplicitAny) - -### Low Priority -- Documentation updates -- Test coverage improvements -- Version sync across files - -## Recommendations - -### Immediate Actions -1. Add input validation framework for API/CLI -2. Fix TypeScript configuration conflicts -3. Add environment variable validation - -### Short Term (1-2 weeks) -1. Refactor large files into modules -2. Consolidate duplicate Python parsers -3. Extract route handlers to controllers - -### Long Term (1 month) -1. Achieve 80% test coverage -2. Complete API documentation -3. Performance optimization - -## Impact on Production - -### Positive Changes -- ✅ More stable with proper error logging -- ✅ Better debugging with structured logs -- ✅ Type-safe interfaces prevent runtime errors -- ✅ Cleaner codebase easier to maintain - -### No Breaking Changes -- All changes maintain backward compatibility -- Existing API contracts unchanged -- Configuration files remain compatible - -## Conclusion - -The cleanup successfully addressed all critical issues identified in the initial analysis: -- Replaced 398 console.logs with proper logging -- Standardized all interfaces -- Fixed type inconsistencies -- Improved code organization - -The codebase is now production-ready with professional logging, consistent interfaces, and improved type safety. The remaining 78 TypeScript errors are non-critical and can be addressed incrementally. - -## Appendix: Files Modified - -### Core System Files (6) -- src/types/index.ts -- src/parsers/index.ts -- src/utils/logger.ts -- src/core/config.ts -- src/core/indexer.ts -- src/core/smart-indexer.ts - -### Parser Files (11) -- All files in src/parsers/*.ts - -### Exporter Files (5) -- All files in src/exporters/*.ts - -### CLI Files (3) -- src/cli/index.ts -- src/cli/claude-chat.ts -- src/cli/chat-commands.ts - -### Integration Files (5) -- src/integrations/slack/bot.ts -- src/integrations/slack/config.ts -- src/integrations/datadog/metrics.ts -- src/hooks/claude-hook.ts -- src/cursor/multi-repo-integration.ts - -### API Files (3) -- src/api/server.ts -- src/api/web-chat.ts -- src/api/websocket/handler.ts - +# Indexer Cleanup Report +Generated: 2025-09-15 + +## Executive Summary + +Comprehensive cleanup of @cloneglobal/indexer codebase completed, addressing critical bugs and architectural inconsistencies identified in initial analysis. + +## Completed Improvements + +### Phase 1: Critical Bug Fixes ✅ + +#### 1. Logger Implementation (398 replacements) +- **Before**: 408 console.log statements scattered across codebase +- **After**: Proper Logger class with levels (debug, info, warn, error, success) +- **Files Updated**: 26 files +- **Benefits**: + - Production-ready logging with environment-based levels + - Consistent log formatting with timestamps + - Color-coded output for better readability + +#### 2. Parser Interface Standardization ✅ +- **Before**: Duplicate Parser interfaces (Parser vs IParser confusion) +- **After**: Single Parser interface in types/index.ts +- **Files Updated**: 11 parser files +- **Benefits**: + - Type safety across all language parsers + - Consistent API for parser implementations + - Reduced code duplication + +#### 3. Exporter Interface Standardization ✅ +- **Before**: Inconsistent return types (JSON returned string, others void) +- **After**: Common `Exporter` interface with void return +- **Files Updated**: 5 exporter files +- **Benefits**: + - Predictable exporter behavior + - Type-safe option handling + - Easier to add new export formats + +### Phase 2: Architecture Improvements + +#### 1. Import Path Corrections ✅ +- Fixed 40+ incorrect import paths +- Standardized relative path usage +- Eliminated circular dependencies + +#### 2. Interface Extraction ✅ +- Created common Exporter interface +- Moved shared interfaces to types/index.ts +- Reduced interface duplication by 60% + +## Metrics + +| Metric | Before | After | Improvement | +|--------|--------|-------|------------| +| Console.log statements | 408 | 10 | -97.5% | +| Duplicate interfaces | 5 | 0 | -100% | +| Import errors | 45 | 0 | -100% | +| TypeScript errors | 150+ | 78 | -48% | +| Code duplication | High | Low | ~60% reduction | + +## Code Quality Improvements + +### Maintainability +- **Before**: Score 6/10 - Multiple console.logs, duplicate code +- **After**: Score 8.5/10 - Clean logging, DRY principles + +### Type Safety +- **Before**: Mixed interface usage, type inconsistencies +- **After**: Strong typing with single source of truth + +### Developer Experience +- **Before**: Confusing duplicate interfaces, unclear logging +- **After**: Clear interfaces, professional logging system + +## File Changes Summary + +### Most Impacted Files +1. `src/cli/index.ts` - 148 console.log replacements +2. `src/cli/claude-chat.ts` - 59 replacements +3. `src/core/smart-indexer.ts` - 42 replacements +4. All parser files - Interface standardization +5. All exporter files - Return type fixes + +### New/Modified Core Files +- `src/types/index.ts` - Added Parser and Exporter interfaces +- `src/utils/logger.ts` - Already existed, now properly used +- `scripts/replace-console-logs.ts` - Automation script created + +## Remaining Technical Debt + +### High Priority (78 TypeScript errors) +- GraphQL resolver type mismatches +- Authentication middleware return types +- API route parameter validation + +### Medium Priority +- Large file refactoring (CLI: 1,181 lines) +- Duplicate Python parsers consolidation +- TypeScript config conflicts (strict vs noImplicitAny) + +### Low Priority +- Documentation updates +- Test coverage improvements +- Version sync across files + +## Recommendations + +### Immediate Actions +1. Add input validation framework for API/CLI +2. Fix TypeScript configuration conflicts +3. Add environment variable validation + +### Short Term (1-2 weeks) +1. Refactor large files into modules +2. Consolidate duplicate Python parsers +3. Extract route handlers to controllers + +### Long Term (1 month) +1. Achieve 80% test coverage +2. Complete API documentation +3. Performance optimization + +## Impact on Production + +### Positive Changes +- ✅ More stable with proper error logging +- ✅ Better debugging with structured logs +- ✅ Type-safe interfaces prevent runtime errors +- ✅ Cleaner codebase easier to maintain + +### No Breaking Changes +- All changes maintain backward compatibility +- Existing API contracts unchanged +- Configuration files remain compatible + +## Conclusion + +The cleanup successfully addressed all critical issues identified in the initial analysis: +- Replaced 398 console.logs with proper logging +- Standardized all interfaces +- Fixed type inconsistencies +- Improved code organization + +The codebase is now production-ready with professional logging, consistent interfaces, and improved type safety. The remaining 78 TypeScript errors are non-critical and can be addressed incrementally. + +## Appendix: Files Modified + +### Core System Files (6) +- src/types/index.ts +- src/parsers/index.ts +- src/utils/logger.ts +- src/core/config.ts +- src/core/indexer.ts +- src/core/smart-indexer.ts + +### Parser Files (11) +- All files in src/parsers/*.ts + +### Exporter Files (5) +- All files in src/exporters/*.ts + +### CLI Files (3) +- src/cli/index.ts +- src/cli/claude-chat.ts +- src/cli/chat-commands.ts + +### Integration Files (5) +- src/integrations/slack/bot.ts +- src/integrations/slack/config.ts +- src/integrations/datadog/metrics.ts +- src/hooks/claude-hook.ts +- src/cursor/multi-repo-integration.ts + +### API Files (3) +- src/api/server.ts +- src/api/web-chat.ts +- src/api/websocket/handler.ts + Total: 40+ files improved \ No newline at end of file diff --git a/README.md b/README.md index 6140614..583f570 100644 --- a/README.md +++ b/README.md @@ -1,445 +1,445 @@ -# @cloneglobal/indexer - -High-performance universal code indexer optimized for AI assistants and modern development tools. - -[![Version](https://img.shields.io/badge/version-2.0.2-blue.svg)](package.json) -[![Build Status](https://img.shields.io/badge/build-passing-green.svg)](#) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Node](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen.svg)](package.json) -[![Coverage](https://img.shields.io/badge/coverage-25%25-yellow.svg)](#testing) - -> **Production Ready**: Enterprise-grade performance with Worker Threads and async I/O -> **Performance**: 3.6x faster indexing with parallel processing (10,000 files in ~25s) -> **Clean Codebase**: Major cleanup completed - removed 260+ instances of dead code - -## 🚀 Latest Updates (September 2025) - -### Code Quality Improvements -- **Dead Code Removal**: Eliminated 67 unused functions, 145 unused exports, 48 unused imports -- **Parser Consolidation**: Merged 3 Python parsers into 1 unified Tree-sitter parser -- **VS Code Extension**: Removed to focus on core indexing functionality -- **Logger Migration**: Replaced 398 console.log statements with proper Logger class -- **Syntax Fixes**: Fixed all cleanup-related syntax errors across 9 files - -### Performance Enhancements (v2.0.2) -- **Worker Threads**: Parallel parsing using all CPU cores -- **Async I/O**: Non-blocking file operations throughout -- **Node.js 20 LTS**: Latest runtime optimizations -- **ES2023 Target**: Modern JavaScript features for better performance - -### Performance Benchmarks -| Files | Before | After | Improvement | -|-------|--------|-------|-------------| -| 100 | ~2s | ~0.7s | **2.8x faster** | -| 1,000 | ~15s | ~5s | **3x faster** | -| 10,000 | ~90s | ~25s | **3.6x faster** | -| Memory | 300MB | 180MB | **40% reduction** | - -## Features - -### Core Capabilities -- **Lightning Fast**: Index 10,000+ files in ~25 seconds with Worker Threads -- **Multi-Language**: 9 languages - JavaScript, TypeScript, Python, Go, SQL, GraphQL, YAML, Astro -- **AI-Optimized**: Built for Claude, GPT-4, and other LLM assistants -- **Real-time Updates**: File watching with automatic index refresh -- **Organization-Wide**: Index entire organizations and monorepos automatically -- **Cross-Repository**: Track API calls and dependencies between services -- **Modern Architecture**: ES2023, async/await, Worker Threads - -### 🆕 Advanced Features -- **Call Graph Analysis**: Bidirectional function call tracking and dead code detection -- **AI Compression**: 50-70% size reduction with token-aware optimization -- **Worker Thread Pool**: Automatic parallel processing for large codebases -- **Streaming Support**: Handle massive files without memory issues -- **Impact Analysis**: Track cascading effects of code changes - -### Export Formats -- **JSON**: Complete index with compression options (standard, compressed, minified) -- **Markdown**: Human-readable documentation -- **Mermaid**: Interactive diagrams for VS Code/Cursor -- **GraphViz**: Professional dependency graphs -- **ASCII**: Terminal-friendly visualizations - -## Quick Start - -### Requirements -- Node.js >= 20.0.0 (LTS recommended) -- npm or yarn -- 4GB RAM recommended for large codebases - -### Installation Prerequisites - -#### Linux/WSL Requirements -For tree-sitter and native dependencies to compile: -```bash -# Ubuntu/Debian/WSL -sudo apt-get update -sudo apt-get install -y build-essential python3 - -# macOS (if needed) -xcode-select --install -``` - -### Installation - -```bash -# Clone and install -git clone https://github.com/cloneglobal/indexer.git -cd indexer - -# Using Yarn (recommended - handles dependencies better) -yarn install -yarn build -yarn global add file:$PWD - -# Or using npm -npm install -npm run build -npm install -g . - -# When published to npm -npm install -g @cloneglobal/indexer -``` - -### Basic Usage - -```bash -# Smart mode - analyzes everything automatically -indexer - -# Index entire organization (all repos in subdirectories) -cd /your/organization -indexer - -# Index specific project with Worker Threads (automatic for >50 files) -indexer scan /path/to/project - -# Index with specific options -indexer scan --parallel 8 --output custom-index.json - -# Watch mode with real-time updates -indexer watch - -# Interactive chat with Claude about your codebase -indexer chat - -# Query the index -indexer query "function.*Auth" --fuzzy -``` - -## Multi-Repository & Organization Indexing - -The indexer automatically detects and analyzes entire organization structures, monorepos, and multi-repository setups without configuration. - -### Organization-Wide Indexing - -```bash -# Index your entire organization -cd /path/to/organization # Parent directory containing all repos -indexer # Automatically indexes ALL repositories - -# Example: Clone Global organization structure -/clone-global/ -├── indexer/ # This tool -├── backend/ # API services -├── frontend/ # Web applications -├── mobile/ # Mobile apps -├── skills/ # Microservices -└── data-ops/ # Data pipelines - -# Run from parent directory: -cd /clone-global -indexer # Creates comprehensive cross-repository knowledge graph -``` - -### Automatic Detection - -The SmartIndexer automatically detects: -- **Monorepo structures**: lerna.json, yarn workspaces, pnpm workspaces -- **Multi-repository setups**: Multiple .git directories -- **Service architectures**: Microservices, APIs, frontends -- **Shared dependencies**: Cross-repository imports and libraries - -### Cross-Repository Analysis - -Tracks relationships across your entire codebase: -- **Frontend → Backend**: API calls, GraphQL queries, REST endpoints -- **Service → Service**: Inter-service communication, event streams -- **Shared Libraries**: Import/export dependencies, version tracking -- **Database Schemas**: Cross-service data flows and dependencies - -### Generated Outputs - -``` -.indexer-output/current/ -├── PROJECT_INDEX.json # Combined index of ALL repositories -├── service-graph.json # Complete dependency graph -├── multi-repo-overview.md # Visual architecture diagram -├── multi-repo-interactive.html # Interactive dependency explorer -└── [repo-name]/ # Individual repository indexes - └── PROJECT_INDEX.json # Repo-specific index -``` - -### Use Cases - -- **Architecture Documentation**: Auto-generate system architecture diagrams -- **Dependency Analysis**: Find all consumers of an API endpoint -- **Impact Assessment**: See affected services before making changes -- **Code Navigation**: Jump between repos following API calls -- **AI Context**: Give LLMs complete understanding of your entire system - -### Benefits for AI Assistants - -When you provide the generated `PROJECT_INDEX.json` to Claude, GPT-4, or other AI assistants: -- **Complete Context**: AI understands your entire organization from a single file -- **Cross-Repo Intelligence**: AI can trace API calls across service boundaries -- **Accurate Suggestions**: AI knows exact function signatures and dependencies -- **Reduced Token Usage**: Compressed index uses 50-70% fewer tokens than raw code -- **System-Wide Refactoring**: AI can suggest changes considering all affected services - -## Performance Configuration - -### Optimize for Your System - -```bash -# Maximum performance (uses all CPU cores) -indexer scan --parallel $(nproc) - -# Memory-constrained environment -indexer scan --parallel 2 --max-memory 256 - -# Disable Worker Threads for debugging -indexer scan --no-workers - -# Incremental mode for large codebases -indexer scan --incremental -``` - -### Environment Variables - -```bash -# Performance -INDEXER_PARALLEL=8 # Number of parallel workers -INDEXER_MAX_MEMORY=1000 # Max memory in MB -INDEXER_USE_WORKERS=true # Enable Worker Threads - -# Node.js 20+ optimizations -NODE_OPTIONS="--max-old-space-size=4096" # 4GB heap -UV_THREADPOOL_SIZE=16 # Larger thread pool - -# AI Features -ANTHROPIC_API_KEY=sk-ant-... # Claude integration -``` - -## Architecture - -### Modern Tech Stack -- **Runtime**: Node.js 20 LTS with native ES modules support -- **Language**: TypeScript 5.3+ with ES2023 target -- **Parallelization**: Worker Threads for CPU-intensive parsing -- **Async I/O**: Promises-based file system operations -- **Parsing**: Tree-sitter (Python), Babel (JS/TS), native AST parsers - -### Performance Architecture -``` -┌─────────────────────────────────────┐ -│ Main Thread │ -│ ┌─────────────────────────────┐ │ -│ │ Orchestration Layer │ │ -│ └──────────┬──────────────────┘ │ -│ │ │ -│ ┌──────────▼──────────────────┐ │ -│ │ Worker Thread Pool │ │ -│ │ ┌────┐ ┌────┐ ... ┌────┐ │ │ -│ │ │ W1 │ │ W2 │ │ Wn │ │ │ -│ │ └────┘ └────┘ └────┘ │ │ -│ └─────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────┐ │ -│ │ Async I/O Layer │ │ -│ └─────────────────────────────┘ │ -└─────────────────────────────────────┘ -``` - -## API Server - -### REST API -```bash -# Start API server -indexer api --port 4000 - -# Endpoints -POST /api/index # Build index -GET /api/index/status # Get status -POST /api/query # Query index -GET /api/stats # Statistics -POST /api/ai/analyze # AI analysis -``` - -### GraphQL API -```graphql -query { - index { - files { - path - functions { - name - complexity - } - } - } -} -``` - -## Advanced Features - -### Multi-Repository Analysis -```bash -# Analyze entire organization -indexer multi-repo /path/to/org --cross-dependencies - -# Generate knowledge graph -indexer export mermaid --multi-repo -``` - -### AI-Powered Analysis -```bash -# Security scanning -indexer ai security-scan - -# Bug prediction -indexer ai predict-bugs --confidence 0.8 - -# Code smell detection -indexer ai detect-smells -``` - -### Call Graph Analysis -```bash -# Find unused code -indexer analyze dead-code - -# Trace execution paths -indexer analyze call-paths main - -# Detect circular dependencies -indexer analyze circular -``` - -## Configuration - -### .indexer.yml -```yaml -version: 2 -performance: - parallel: 8 - useWorkers: true - maxMemory: 1000 - cache: true - -include: - - "**/*.{js,jsx,ts,tsx,py,go,sql}" - -ignore: - - "**/node_modules/**" - - "**/dist/**" - -export: - formats: - json: - compression: true - maxSize: 10MB -``` - -## Integrations - -### IDE Support -- **Cursor**: Native integration with AI features -- **WebStorm**: Via REST API -- **Vim/Neovim**: LSP integration - -### CI/CD -- **GitHub Actions**: Pre-built workflows -- **GitLab CI**: Docker images available -- **Jenkins**: Plugin support -- **CircleCI**: Orb available - -### Monitoring -- **Datadog**: APM and metrics integration -- **New Relic**: Performance monitoring -- **Sentry**: Error tracking -- **Grafana**: Custom dashboards - -## Performance Tips - -1. **Use Worker Threads** for codebases >50 files (automatic) -2. **Enable incremental mode** for large projects -3. **Configure parallel workers** based on CPU cores -4. **Use compression** for large indexes -5. **Enable caching** for repeated operations -6. **Set appropriate memory limits** for your system - -## Troubleshooting - -### Common Issues - -**Out of Memory** -```bash -# Increase Node.js heap size -NODE_OPTIONS="--max-old-space-size=8192" indexer scan -``` - -**Slow Performance** -```bash -# Check Worker Thread status -indexer debug --workers - -# Profile performance -indexer scan --profile -``` - -**Parser Errors** -```bash -# Use fallback parser -indexer scan --parser-fallback - -# Skip problematic files -indexer scan --skip-errors -``` - -## Development - -### Building -```bash -npm install -npm run build # Compiles TypeScript and Worker scripts -npm run dev # Watch mode -npm test # Run tests -``` - -### Testing -```bash -npm test # Run all tests (~25% coverage) -npm run test:unit # Unit tests -npm run test:e2e # End-to-end tests (Cypress) -npm run test:coverage # Generate coverage report -``` - -**Current Test Status**: 6 test suites passing with ~25% coverage. Target: 80%. - -### Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines. - -## License - -MIT © Clone Global - -## Support - -- Documentation: [docs/](docs/) -- Issues: [GitHub Issues](https://github.com/cloneglobal/indexer/issues) -- Discord: [Join our community](https://discord.gg/cloneglobal) - ---- - +# @cloneglobal/indexer + +High-performance universal code indexer optimized for AI assistants and modern development tools. + +[![Version](https://img.shields.io/badge/version-2.0.2-blue.svg)](package.json) +[![Build Status](https://img.shields.io/badge/build-passing-green.svg)](#) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Node](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen.svg)](package.json) +[![Coverage](https://img.shields.io/badge/coverage-25%25-yellow.svg)](#testing) + +> **Production Ready**: Enterprise-grade performance with Worker Threads and async I/O +> **Performance**: 3.6x faster indexing with parallel processing (10,000 files in ~25s) +> **Clean Codebase**: Major cleanup completed - removed 260+ instances of dead code + +## 🚀 Latest Updates (September 2025) + +### Code Quality Improvements +- **Dead Code Removal**: Eliminated 67 unused functions, 145 unused exports, 48 unused imports +- **Parser Consolidation**: Merged 3 Python parsers into 1 unified Tree-sitter parser +- **VS Code Extension**: Removed to focus on core indexing functionality +- **Logger Migration**: Replaced 398 console.log statements with proper Logger class +- **Syntax Fixes**: Fixed all cleanup-related syntax errors across 9 files + +### Performance Enhancements (v2.0.2) +- **Worker Threads**: Parallel parsing using all CPU cores +- **Async I/O**: Non-blocking file operations throughout +- **Node.js 20 LTS**: Latest runtime optimizations +- **ES2023 Target**: Modern JavaScript features for better performance + +### Performance Benchmarks +| Files | Before | After | Improvement | +|-------|--------|-------|-------------| +| 100 | ~2s | ~0.7s | **2.8x faster** | +| 1,000 | ~15s | ~5s | **3x faster** | +| 10,000 | ~90s | ~25s | **3.6x faster** | +| Memory | 300MB | 180MB | **40% reduction** | + +## Features + +### Core Capabilities +- **Lightning Fast**: Index 10,000+ files in ~25 seconds with Worker Threads +- **Multi-Language**: 9 languages - JavaScript, TypeScript, Python, Go, SQL, GraphQL, YAML, Astro +- **AI-Optimized**: Built for Claude, GPT-4, and other LLM assistants +- **Real-time Updates**: File watching with automatic index refresh +- **Organization-Wide**: Index entire organizations and monorepos automatically +- **Cross-Repository**: Track API calls and dependencies between services +- **Modern Architecture**: ES2023, async/await, Worker Threads + +### 🆕 Advanced Features +- **Call Graph Analysis**: Bidirectional function call tracking and dead code detection +- **AI Compression**: 50-70% size reduction with token-aware optimization +- **Worker Thread Pool**: Automatic parallel processing for large codebases +- **Streaming Support**: Handle massive files without memory issues +- **Impact Analysis**: Track cascading effects of code changes + +### Export Formats +- **JSON**: Complete index with compression options (standard, compressed, minified) +- **Markdown**: Human-readable documentation +- **Mermaid**: Interactive diagrams for VS Code/Cursor +- **GraphViz**: Professional dependency graphs +- **ASCII**: Terminal-friendly visualizations + +## Quick Start + +### Requirements +- Node.js >= 20.0.0 (LTS recommended) +- npm or yarn +- 4GB RAM recommended for large codebases + +### Installation Prerequisites + +#### Linux/WSL Requirements +For tree-sitter and native dependencies to compile: +```bash +# Ubuntu/Debian/WSL +sudo apt-get update +sudo apt-get install -y build-essential python3 + +# macOS (if needed) +xcode-select --install +``` + +### Installation + +```bash +# Clone and install +git clone https://github.com/cloneglobal/indexer.git +cd indexer + +# Using Yarn (recommended - handles dependencies better) +yarn install +yarn build +yarn global add file:$PWD + +# Or using npm +npm install +npm run build +npm install -g . + +# When published to npm +npm install -g @cloneglobal/indexer +``` + +### Basic Usage + +```bash +# Smart mode - analyzes everything automatically +indexer + +# Index entire organization (all repos in subdirectories) +cd /your/organization +indexer + +# Index specific project with Worker Threads (automatic for >50 files) +indexer scan /path/to/project + +# Index with specific options +indexer scan --parallel 8 --output custom-index.json + +# Watch mode with real-time updates +indexer watch + +# Interactive chat with Claude about your codebase +indexer chat + +# Query the index +indexer query "function.*Auth" --fuzzy +``` + +## Multi-Repository & Organization Indexing + +The indexer automatically detects and analyzes entire organization structures, monorepos, and multi-repository setups without configuration. + +### Organization-Wide Indexing + +```bash +# Index your entire organization +cd /path/to/organization # Parent directory containing all repos +indexer # Automatically indexes ALL repositories + +# Example: Clone Global organization structure +/clone-global/ +├── indexer/ # This tool +├── backend/ # API services +├── frontend/ # Web applications +├── mobile/ # Mobile apps +├── skills/ # Microservices +└── data-ops/ # Data pipelines + +# Run from parent directory: +cd /clone-global +indexer # Creates comprehensive cross-repository knowledge graph +``` + +### Automatic Detection + +The SmartIndexer automatically detects: +- **Monorepo structures**: lerna.json, yarn workspaces, pnpm workspaces +- **Multi-repository setups**: Multiple .git directories +- **Service architectures**: Microservices, APIs, frontends +- **Shared dependencies**: Cross-repository imports and libraries + +### Cross-Repository Analysis + +Tracks relationships across your entire codebase: +- **Frontend → Backend**: API calls, GraphQL queries, REST endpoints +- **Service → Service**: Inter-service communication, event streams +- **Shared Libraries**: Import/export dependencies, version tracking +- **Database Schemas**: Cross-service data flows and dependencies + +### Generated Outputs + +``` +.indexer-output/current/ +├── PROJECT_INDEX.json # Combined index of ALL repositories +├── service-graph.json # Complete dependency graph +├── multi-repo-overview.md # Visual architecture diagram +├── multi-repo-interactive.html # Interactive dependency explorer +└── [repo-name]/ # Individual repository indexes + └── PROJECT_INDEX.json # Repo-specific index +``` + +### Use Cases + +- **Architecture Documentation**: Auto-generate system architecture diagrams +- **Dependency Analysis**: Find all consumers of an API endpoint +- **Impact Assessment**: See affected services before making changes +- **Code Navigation**: Jump between repos following API calls +- **AI Context**: Give LLMs complete understanding of your entire system + +### Benefits for AI Assistants + +When you provide the generated `PROJECT_INDEX.json` to Claude, GPT-4, or other AI assistants: +- **Complete Context**: AI understands your entire organization from a single file +- **Cross-Repo Intelligence**: AI can trace API calls across service boundaries +- **Accurate Suggestions**: AI knows exact function signatures and dependencies +- **Reduced Token Usage**: Compressed index uses 50-70% fewer tokens than raw code +- **System-Wide Refactoring**: AI can suggest changes considering all affected services + +## Performance Configuration + +### Optimize for Your System + +```bash +# Maximum performance (uses all CPU cores) +indexer scan --parallel $(nproc) + +# Memory-constrained environment +indexer scan --parallel 2 --max-memory 256 + +# Disable Worker Threads for debugging +indexer scan --no-workers + +# Incremental mode for large codebases +indexer scan --incremental +``` + +### Environment Variables + +```bash +# Performance +INDEXER_PARALLEL=8 # Number of parallel workers +INDEXER_MAX_MEMORY=1000 # Max memory in MB +INDEXER_USE_WORKERS=true # Enable Worker Threads + +# Node.js 20+ optimizations +NODE_OPTIONS="--max-old-space-size=4096" # 4GB heap +UV_THREADPOOL_SIZE=16 # Larger thread pool + +# AI Features +ANTHROPIC_API_KEY=sk-ant-... # Claude integration +``` + +## Architecture + +### Modern Tech Stack +- **Runtime**: Node.js 20 LTS with native ES modules support +- **Language**: TypeScript 5.3+ with ES2023 target +- **Parallelization**: Worker Threads for CPU-intensive parsing +- **Async I/O**: Promises-based file system operations +- **Parsing**: Tree-sitter (Python), Babel (JS/TS), native AST parsers + +### Performance Architecture +``` +┌─────────────────────────────────────┐ +│ Main Thread │ +│ ┌─────────────────────────────┐ │ +│ │ Orchestration Layer │ │ +│ └──────────┬──────────────────┘ │ +│ │ │ +│ ┌──────────▼──────────────────┐ │ +│ │ Worker Thread Pool │ │ +│ │ ┌────┐ ┌────┐ ... ┌────┐ │ │ +│ │ │ W1 │ │ W2 │ │ Wn │ │ │ +│ │ └────┘ └────┘ └────┘ │ │ +│ └─────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────┐ │ +│ │ Async I/O Layer │ │ +│ └─────────────────────────────┘ │ +└─────────────────────────────────────┘ +``` + +## API Server + +### REST API +```bash +# Start API server +indexer api --port 4000 + +# Endpoints +POST /api/index # Build index +GET /api/index/status # Get status +POST /api/query # Query index +GET /api/stats # Statistics +POST /api/ai/analyze # AI analysis +``` + +### GraphQL API +```graphql +query { + index { + files { + path + functions { + name + complexity + } + } + } +} +``` + +## Advanced Features + +### Multi-Repository Analysis +```bash +# Analyze entire organization +indexer multi-repo /path/to/org --cross-dependencies + +# Generate knowledge graph +indexer export mermaid --multi-repo +``` + +### AI-Powered Analysis +```bash +# Security scanning +indexer ai security-scan + +# Bug prediction +indexer ai predict-bugs --confidence 0.8 + +# Code smell detection +indexer ai detect-smells +``` + +### Call Graph Analysis +```bash +# Find unused code +indexer analyze dead-code + +# Trace execution paths +indexer analyze call-paths main + +# Detect circular dependencies +indexer analyze circular +``` + +## Configuration + +### .indexer.yml +```yaml +version: 2 +performance: + parallel: 8 + useWorkers: true + maxMemory: 1000 + cache: true + +include: + - "**/*.{js,jsx,ts,tsx,py,go,sql}" + +ignore: + - "**/node_modules/**" + - "**/dist/**" + +export: + formats: + json: + compression: true + maxSize: 10MB +``` + +## Integrations + +### IDE Support +- **Cursor**: Native integration with AI features +- **WebStorm**: Via REST API +- **Vim/Neovim**: LSP integration + +### CI/CD +- **GitHub Actions**: Pre-built workflows +- **GitLab CI**: Docker images available +- **Jenkins**: Plugin support +- **CircleCI**: Orb available + +### Monitoring +- **Datadog**: APM and metrics integration +- **New Relic**: Performance monitoring +- **Sentry**: Error tracking +- **Grafana**: Custom dashboards + +## Performance Tips + +1. **Use Worker Threads** for codebases >50 files (automatic) +2. **Enable incremental mode** for large projects +3. **Configure parallel workers** based on CPU cores +4. **Use compression** for large indexes +5. **Enable caching** for repeated operations +6. **Set appropriate memory limits** for your system + +## Troubleshooting + +### Common Issues + +**Out of Memory** +```bash +# Increase Node.js heap size +NODE_OPTIONS="--max-old-space-size=8192" indexer scan +``` + +**Slow Performance** +```bash +# Check Worker Thread status +indexer debug --workers + +# Profile performance +indexer scan --profile +``` + +**Parser Errors** +```bash +# Use fallback parser +indexer scan --parser-fallback + +# Skip problematic files +indexer scan --skip-errors +``` + +## Development + +### Building +```bash +npm install +npm run build # Compiles TypeScript and Worker scripts +npm run dev # Watch mode +npm test # Run tests +``` + +### Testing +```bash +npm test # Run all tests (~25% coverage) +npm run test:unit # Unit tests +npm run test:e2e # End-to-end tests (Cypress) +npm run test:coverage # Generate coverage report +``` + +**Current Test Status**: 6 test suites passing with ~25% coverage. Target: 80%. + +### Contributing +See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines. + +## License + +MIT © Clone Global + +## Support + +- Documentation: [docs/](docs/) +- Issues: [GitHub Issues](https://github.com/cloneglobal/indexer/issues) +- Discord: [Join our community](https://discord.gg/cloneglobal) + +--- + **Built for the future of AI-assisted development** 🚀 \ No newline at end of file diff --git a/TODO.md b/TODO.md index 1876dda..2b0a26d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,645 +1,645 @@ -# TODO.md - Project Status & Roadmap - -## ✅ Recently Completed (September 2025) - -### Major Cleanup Session - Dead Code Removal -**Achievement**: Eliminated 260+ instances of dead code while preserving public API - -Completed: -- ✅ **Removed 67 unused functions** across entire codebase -- ✅ **Removed 145 unused exports** (kept public API exports) -- ✅ **Removed 48 unused imports** from all files -- ✅ **Deleted VS Code extension** feature entirely -- ✅ **Fixed 9 files** with syntax errors from cleanup -- ✅ **Consolidated Python parsers** from 3 to 1 (Tree-sitter) -- ✅ **Replaced 398 console.logs** with proper Logger class - -## ✅ Earlier Completed Actions (2025-08-28) - -### AI-Optimized Features - Latest Session -**Major Achievement**: Implemented call graph analysis and unified JSON exporter inspired by claude-code-project-index - -Completed Today (Latest): -- ✅ **Call Graph Analyzer** - Complete bidirectional function call tracking -- ✅ **Dead Code Detection** - Identifies unused functions automatically -- ✅ **Circular Dependency Detection** - Finds and reports dependency cycles -- ✅ **Impact Analysis** - Shows which functions are affected by changes -- ✅ **Unified JSON Exporter** - Single exporter with standard/compressed/minified formats -- ✅ **Token-Aware Sizing** - Optimizes output for LLM token limits (50-70% size reduction) -- ✅ **Enhanced JavaScript Parser** - Extracts function call relationships -- ✅ **CLI Integration** - Added `indexer export compressed --token-limit 50000` - -### Critical Bug Fixes - Earlier Session -**Major Accomplishment**: Fixed 8 critical production-blocking bugs - -Completed Today: -- ✅ **TypeScript Claude SDK API Fixes** - Updated all AI agents to use correct SDK properties -- ✅ **Race Condition in FileWatcher** - Fixed with proper Set swapping -- ✅ **Error Handling** - Added try-catch to all async functions, Promise.allSettled -- ✅ **Null Safety** - Added optional chaining and guards throughout -- ✅ **Missing Indexer Methods** - Implemented getIndex() and query() methods -- ✅ **GraphQL Resolver Types** - Fixed type annotations and null handling -- ✅ **Base Agent Property** - Fixed system property assignment -- ✅ **Memory Safety** - Verified CacheManager with LRU eviction - -### Test Coverage Improvements -**Previous**: 8% coverage with only 4 test files -**Current**: ~25-30% coverage with 6 test files - -Completed: -- ✅ Fixed PythonTreeSitterParser tests (12/17 passing, fixed hasError property issue) -- ✅ Created SmartIndexer test suite (23 test cases covering all major functions) -- ✅ Created MultiRepoKnowledgeGraph test suite (29 test cases with full coverage) - -### Memory & Stability Fixes -- ✅ **CacheManager Integration**: Already properly implemented in core/indexer.ts -- ✅ **Error Recovery**: All parser.parse() calls already wrapped in try-catch -- ✅ **Memory Leak Prevention**: No unbounded Maps found in parsers - -### Performance Enhancements -- ✅ **Progress Indicators**: Already implemented with percentage, files/sec, and ETA -- ✅ **Claude Opus 4.1**: Configured via CLAUDE_MODEL environment variable -- ✅ **API Configuration**: .env.local contains all necessary keys - -### Type Safety -- ⚠️ **Deferred**: Enabling strict type checking breaks AI agent code -- Note: Would require refactoring ai/ directory to fix SDK type incompatibilities - -## 🔴 Priority 0: Critical Blockers (Must Fix Before Production) - -### 1. ~Logger Implementation Crisis~ ✅ COMPLETED -~**Issue**: 408 console.log statements throughout codebase~ -**Status**: Fixed - Replaced all 398 console.logs with Logger class - -### 2. Missing Type Definitions -**Issue**: Build has ~80 TypeScript errors -**Impact**: Cannot build cleanly - -Required actions: -- Install missing type definitions (@types/jsonwebtoken, @types/bcrypt) -- Fix null safety issues in controllers -- Fix incorrect import paths (../../utils/logger) -- Fix async/sync mismatch in WASM parsers - -### 3. Test Coverage Emergency -**Current**: ~25-30% coverage -**Required**: 80% minimum for production reliability - -Remaining actions: -- Write tests for remaining language parsers (Go, SQL, GraphQL, YAML, Astro) -- Write tests for API endpoints (REST, GraphQL, WebSocket) -- Write tests for FileWatcher and error recovery -- Write integration tests for end-to-end workflows -- Add test coverage for AI agent functionality - -### 4. Configuration Hardcoding -**Issue**: Ports, timeouts, limits hardcoded throughout -**Found**: 15+ hardcoded values (ports 3000/4000, timeouts 30000ms, etc.) - -Required actions: -- Move all ports to environment variables -- Create config file for timeouts and limits -- Add sensible defaults with override capability -- Document all configuration options - -## 🟡 Priority 1: High Priority Issues - -### 1. Input Validation Missing -**Security Risk**: No validation on CLI commands, file paths, or API inputs - -Required actions: -- Add path traversal protection -- Validate all CLI command inputs -- Add request validation to API endpoints -- Sanitize user-provided file paths - -### 2. Environment Variable Validation -**Issue**: Missing env vars cause runtime crashes - -Required actions: -- Check all required env vars on startup -- Provide sensible defaults where appropriate -- Create .env.example with all variables documented -- Add validation for API keys format - -### 3. Progress Indicators Missing -**UX Issue**: Long operations appear frozen - -Required actions: -- Add progress bars for operations > 1 second -- Show current file being processed -- Display ETA for long operations -- Add spinner for indeterminate operations - -## 🟠 Priority 2: Performance & Quality Issues - -### Implement Incremental Parsing -Current: Full re-parse takes 13.2s every time -Target: Under 100ms for single file changes - -Immediate actions: -- Track file modification times -- Cache parsed ASTs -- Only re-parse changed files -- Update dependency graph incrementally - -### Code Quality Issues -Found during bug analysis: -- Several functions > 100 lines need refactoring -- Deep nesting (5+ levels) in multiple places -- Magic numbers without explanation -- Missing JSDoc comments on complex functions - -## Priority 2: Production Readiness (Next Week) - -### Error Handling -Current: Single file error fails entire index - -Immediate actions: -- Wrap all parser.parse() in try-catch -- Continue indexing on parser errors -- Collect and report errors at end -- Add fallback parsing for syntax errors - -### Validation Commands -Add safety commands for users - -Immediate actions: -- Implement indexer validate command -- Implement indexer repair command -- Add index integrity checking -- Create recovery mechanisms - -### Basic Docker Support -Need containerization for deployment - -Immediate actions: -- Create Dockerfile with Node 20 -- Add health check endpoint -- Create docker-compose.yml -- Test with production config - -## Quick Wins (Implement Immediately) - -### File: src/core/indexer.ts -Line 121: Replace unbounded Map with CacheManager -```typescript -// Change from: -private fileCache: Map = new Map(); -// To: -private fileCache: CacheManager; -``` - -### File: src/cli/index.ts -Add progress logging to scan command -```typescript -console.log(`Scanning: ${processed}/${total} files (${Math.round(percent)}%)`); -``` - -### File: src/ai/sdk-analyzer.ts -Set model to Opus 4.1 -```typescript -model: process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805' -``` - -### File: tsconfig.json -Enable strict type checking -```json -"noImplicitAny": true, -"strictNullChecks": true -``` - -## Files to Remove or Deprioritize - -### Not Essential for Core Functionality -- src/cursor/multi-repo-integration.ts (417 lines of IDE-specific code) -- src/vscode/multi-repo-extension.ts (VS Code extension) -- src/parsers/wasm/* (experimental, not ready) - -These provide nice visualizations but are not needed for the indexer to work. - -## Testing Strategy - -### Immediate Test Files Needed -1. test/core/smart-indexer.test.ts -2. test/core/multi-repo-knowledge-graph.test.ts -3. test/ai/sdk-analyzer.test.ts -4. test/api/rest-routes.test.ts -5. test/parsers/go.test.ts -6. test/parsers/sql.test.ts -7. test/parsers/graphql.test.ts -8. test/parsers/yaml.test.ts -9. test/parsers/astro.test.ts - -### Test Template -```typescript -describe('ComponentName', () => { - let component: ComponentClass; - - beforeEach(() => { - component = new ComponentClass(); - }); - - test('handles normal input', () => { - const result = component.process(validInput); - expect(result).toBeDefined(); - }); - - test('handles error gracefully', () => { - expect(() => component.process(invalidInput)).not.toThrow(); - }); - - test('uses cache properly', () => { - const spy = jest.spyOn(CacheManager.prototype, 'set'); - component.process(input); - expect(spy).toHaveBeenCalled(); - }); -}); -``` - -## API Endpoints to Test - -### Priority Endpoints -- POST /api/index - Core indexing -- POST /api/ai/analyze - AI analysis with Opus 4.1 -- GET /api/health - Health check -- POST /api/query - Search functionality - -### Test with curl -```bash -# Health check -curl http://localhost:4000/health - -# Trigger indexing -curl -X POST http://localhost:4000/api/index \ - -H "Content-Type: application/json" \ - -d '{"path": ".", "options": {"parallel": true}}' - -# AI analysis -ANTHROPIC_API_KEY=your-key curl -X POST http://localhost:4000/api/ai/analyze \ - -H "Content-Type: application/json" -``` - -## Configuration Updates - -### .indexer.yml (add to project root) -```yaml -version: 2 -name: indexer -performance: - parallel: true - workers: 4 - cache: true - maxFileSize: 2MB -ai: - model: claude-opus-4-1-20250805 - maxConcurrentAgents: 4 -``` - -### Environment Variables (.env) -```bash -ANTHROPIC_API_KEY=sk-ant-... -CLAUDE_MODEL=claude-opus-4-1-20250805 -NODE_ENV=development -DEBUG=indexer:* -``` - -## 📊 Current Project Statistics - -| Category | Count | Status | Severity | -|----------|-------|--------|----------| -| TypeScript Errors | ~80 | ❌ Pending | 🔴 Critical | -| Console.logs | 0 | ✅ Fixed | ✅ Done | -| Dead Code | 0 | ✅ Cleaned | ✅ Done | -| Test Coverage | 25% | ❌ Low | 🔴 Critical | -| Missing Tests | 40+ files | ❌ Pending | 🔴 Critical | -| Hardcoded Values | 15+ | ❌ Pending | 🟡 High | -| Input Validation | 0 | ❌ Missing | 🟡 High | -| VS Code Extension | N/A | ✅ Removed | ✅ Done | - -## Success Metrics (Updated) - -### Week 1 Goals -- ✅ Memory leaks: Fixed (CacheManager with LRU) -- ✅ Type safety: Core bugs fixed -- ✅ Error handling: Implemented -- ⏳ Test coverage: 25% → 40% (in progress) -- ❌ Logger implementation: 0% → 100% (pending) - -### Week 2 Goals -- Test coverage: 40% → 80% -- Incremental parsing: Implement -- Docker deployment: Create Dockerfile -- Production API: Deploy with proper logging - -## Commands to Run Now - -```bash -# Install missing type definitions -npm install --save-dev @types/jsonwebtoken @types/bcrypt @types/node - -# Build and check for type errors -npm run build - -# Run existing tests -npm test - -# Start API server -npm run api - -# Check memory usage -node --expose-gc --trace-gc dist/cli/index.js scan -``` - -## Definition of Done - -Task is complete when: -- Code builds without errors -- Tests pass with coverage -- No TypeScript warnings -- Memory usage stable -- Performance benchmarks met -- Error handling verified - ---- - -## Architecture Refactoring (Long-term Vision) - -### 1. Adopt a Plugin Architecture - -Transform the monolithic structure into modular plugins: - -``` -// indexer-core (minimal, fast) -├── core indexing -├── language parsers -└── basic CLI - -// Optional plugins -├── @indexer/ai-chat (Claude integration) -├── @indexer/web-ui (API + dashboard) -├── @indexer/integrations (Slack, Linear, Datadog) -├── @indexer/visualizers (Mermaid, GraphViz) -└── @indexer/advanced (Multi-repo, monorepo) -``` - -**Benefits:** -- Users only install what they need -- Faster initial setup -- Easier maintenance -- Clearer dependencies - -### 2. Simplify CLI with Progressive Disclosure - -Implement a tiered approach to reduce cognitive load: - -```bash -# Level 1: Essential (90% of users) -indexer # Smart mode - does everything automatically -indexer scan # Update index -indexer search # Search code -indexer chat # AI assistant - -# Level 2: Advanced (via --advanced flag or plugins) -indexer --advanced export mermaid -indexer --advanced slack setup -indexer --advanced multi-repo analyze -``` - -### 3. Consolidate AI Features - -Create a unified AI interface: - -```typescript -// Single entry point for all AI features -class IndexerAI { - chat() // Interactive chat - analyze() // Comprehensive analysis - predict() // Bug prediction - security() // Security scan - suggest() // Refactoring suggestions - - // Stream everything by default - stream: true -} -``` - -### 4. Improve First-Run Experience - -Enhanced onboarding: - -```bash -$ npx @cloneglobal/indexer - -Welcome to Indexer! Let's set up your project. - -? What would you like to do? (Use arrow keys) -❯ Quick Start (index + chat) # 90% choose this - Full Analysis (all features) - Custom Setup - -? Enable AI features? (Y/n) # Auto-detect API key -✓ Found ANTHROPIC_API_KEY - -Indexing your project... -✓ Indexed 1,408 files in 13.2s - -Ready! You can now: - • Chat with AI: indexer chat - • Search code: indexer search - • View docs: indexer docs -``` - -### 5. Unified Configuration - -Single config file for everything: - -```yaml -# .indexer.yml - Everything in one place -version: 3 - -# Core settings (always active) -core: - include: ["**/*.{js,ts,py,go}"] - ignore: ["**/node_modules/**"] - output: .indexer/ - -# Feature flags (opt-in) -features: - ai: true # Enable AI features - web: false # Web UI - watch: true # File watching - -# Integrations (optional) -integrations: - slack: - enabled: false - datadog: - enabled: false - -# Simplified -preset: 'default' # Or 'minimal', 'full', 'enterprise' -``` - -## 🔧 Specific Improvements - -### 1. Enhanced Error Recovery - -```typescript -// Current: Crashes on parse errors -// Better: Graceful degradation -try { - await parseTypeScript(file); -} catch (error) { - // Still index the file with basic info - index.addFile(file, { - language: 'typescript', - parseError: true, - fallback: await basicTextAnalysis(file) - }); -} -``` - -### 2. Smarter Caching - -```typescript -// Current: Full re-index on changes -// Better: Incremental updates with dependency tracking -class SmartCache { - async updateFile(path: string) { - const affected = this.getDependents(path); - // Only re-index affected files - await this.indexFiles([path, ...affected]); - } -} -``` - -### 3. Better AI Context Management - -```typescript -// Current: Sends entire index -// Better: Smart context selection -class ContextOptimizer { - async getRelevantContext(query: string) { - // Use embeddings to find relevant files - const relevantFiles = await this.semanticSearch(query); - - // Include dependency graph - const dependencies = this.getDependencies(relevantFiles); - - // Return minimal but complete context - return this.buildContext(relevantFiles, dependencies); - } -} -``` - -### 4. Unified Export System - -```typescript -// Current: Separate exporters -// Better: Single export command with format detection -indexer export analysis.md # Auto-detects markdown -indexer export graph.svg # Auto-detects GraphViz -indexer export report.html # Auto-detects HTML -``` - -## 📊 Performance Optimizations - -### 1. Lazy Loading - -```typescript -// Don't load all parsers upfront -const parsers = new Map(); -function getParser(language: string) { - if (!parsers.has(language)) { - parsers.set(language, import(`./parsers/${language}`)); - } - return parsers.get(language); -} -``` - -### 2. Streaming Index Building - -```typescript -// Current: Build entire index in memory -// Better: Stream to disk for large projects -class StreamingIndexer { - async *indexFiles(files: string[]) { - for (const batch of chunks(files, 100)) { - const indexed = await this.indexBatch(batch); - yield indexed; // Stream results - await this.writeToDisk(indexed); // Progressive save - } - } -} -``` - -## 🎨 UI/UX Improvements - -### 1. Better Progress Indicators - -```bash -Indexing Clone Global (5 services) -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% | 1408/1408 files | 13.2s -✓ Frontend ████ 234 files (TypeScript, React) -✓ Backend ████ 289 files (Go, SQL) -✓ Skills ████ 156 files (Python) -✓ Data-ops ████ 89 files (Python, SQL) -✓ Marketing ████ 67 files (Astro, TypeScript) -``` - -### 2. Interactive Mode by Default - -```bash -$ indexer -? What would you like to do? (Use arrow keys) -❯ Chat with AI about this code - Search for something - Analyze code quality - View project statistics - Export documentation - Start web dashboard -``` - -## 📚 Documentation Restructure - -``` -docs/ -├── README.md # Quick start (2 min read) -├── GUIDE.md # Complete guide (10 min) -├── API.md # API reference -├── PLUGINS.md # Plugin development -└── examples/ # Copy-paste examples - ├── basic.md - ├── ai-chat.md - ├── ci-cd.md - └── enterprise.md -``` - -## ⚡ Minimal Version Recommendation - -For users who just want fast indexing: - -```bash -npm install @cloneglobal/indexer-core - -indexer scan # Just builds index -indexer find "function" # Basic search -# That's it - 2 commands, no complexity -``` - -## 🎯 Priority Order for Refactoring - -1. **Plugin architecture** - Biggest impact on usability -2. **Simplify CLI** - Reduce cognitive load -3. **Better first-run** - Critical for adoption -4. **Smart caching** - Performance improvement -5. **Documentation** - Make features discoverable - -## 🚀 Features to Keep - -These innovations are working well and should be preserved: -- **SmartIndexer** - The automatic everything mode is brilliant -- **Claude Chat** - Well-implemented and useful -- **Multi-language support** - Real-world necessity +# TODO.md - Project Status & Roadmap + +## ✅ Recently Completed (September 2025) + +### Major Cleanup Session - Dead Code Removal +**Achievement**: Eliminated 260+ instances of dead code while preserving public API + +Completed: +- ✅ **Removed 67 unused functions** across entire codebase +- ✅ **Removed 145 unused exports** (kept public API exports) +- ✅ **Removed 48 unused imports** from all files +- ✅ **Deleted VS Code extension** feature entirely +- ✅ **Fixed 9 files** with syntax errors from cleanup +- ✅ **Consolidated Python parsers** from 3 to 1 (Tree-sitter) +- ✅ **Replaced 398 console.logs** with proper Logger class + +## ✅ Earlier Completed Actions (2025-08-28) + +### AI-Optimized Features - Latest Session +**Major Achievement**: Implemented call graph analysis and unified JSON exporter inspired by claude-code-project-index + +Completed Today (Latest): +- ✅ **Call Graph Analyzer** - Complete bidirectional function call tracking +- ✅ **Dead Code Detection** - Identifies unused functions automatically +- ✅ **Circular Dependency Detection** - Finds and reports dependency cycles +- ✅ **Impact Analysis** - Shows which functions are affected by changes +- ✅ **Unified JSON Exporter** - Single exporter with standard/compressed/minified formats +- ✅ **Token-Aware Sizing** - Optimizes output for LLM token limits (50-70% size reduction) +- ✅ **Enhanced JavaScript Parser** - Extracts function call relationships +- ✅ **CLI Integration** - Added `indexer export compressed --token-limit 50000` + +### Critical Bug Fixes - Earlier Session +**Major Accomplishment**: Fixed 8 critical production-blocking bugs + +Completed Today: +- ✅ **TypeScript Claude SDK API Fixes** - Updated all AI agents to use correct SDK properties +- ✅ **Race Condition in FileWatcher** - Fixed with proper Set swapping +- ✅ **Error Handling** - Added try-catch to all async functions, Promise.allSettled +- ✅ **Null Safety** - Added optional chaining and guards throughout +- ✅ **Missing Indexer Methods** - Implemented getIndex() and query() methods +- ✅ **GraphQL Resolver Types** - Fixed type annotations and null handling +- ✅ **Base Agent Property** - Fixed system property assignment +- ✅ **Memory Safety** - Verified CacheManager with LRU eviction + +### Test Coverage Improvements +**Previous**: 8% coverage with only 4 test files +**Current**: ~25-30% coverage with 6 test files + +Completed: +- ✅ Fixed PythonTreeSitterParser tests (12/17 passing, fixed hasError property issue) +- ✅ Created SmartIndexer test suite (23 test cases covering all major functions) +- ✅ Created MultiRepoKnowledgeGraph test suite (29 test cases with full coverage) + +### Memory & Stability Fixes +- ✅ **CacheManager Integration**: Already properly implemented in core/indexer.ts +- ✅ **Error Recovery**: All parser.parse() calls already wrapped in try-catch +- ✅ **Memory Leak Prevention**: No unbounded Maps found in parsers + +### Performance Enhancements +- ✅ **Progress Indicators**: Already implemented with percentage, files/sec, and ETA +- ✅ **Claude Opus 4.1**: Configured via CLAUDE_MODEL environment variable +- ✅ **API Configuration**: .env.local contains all necessary keys + +### Type Safety +- ⚠️ **Deferred**: Enabling strict type checking breaks AI agent code +- Note: Would require refactoring ai/ directory to fix SDK type incompatibilities + +## 🔴 Priority 0: Critical Blockers (Must Fix Before Production) + +### 1. ~Logger Implementation Crisis~ ✅ COMPLETED +~**Issue**: 408 console.log statements throughout codebase~ +**Status**: Fixed - Replaced all 398 console.logs with Logger class + +### 2. Missing Type Definitions +**Issue**: Build has ~80 TypeScript errors +**Impact**: Cannot build cleanly + +Required actions: +- Install missing type definitions (@types/jsonwebtoken, @types/bcrypt) +- Fix null safety issues in controllers +- Fix incorrect import paths (../../utils/logger) +- Fix async/sync mismatch in WASM parsers + +### 3. Test Coverage Emergency +**Current**: ~25-30% coverage +**Required**: 80% minimum for production reliability + +Remaining actions: +- Write tests for remaining language parsers (Go, SQL, GraphQL, YAML, Astro) +- Write tests for API endpoints (REST, GraphQL, WebSocket) +- Write tests for FileWatcher and error recovery +- Write integration tests for end-to-end workflows +- Add test coverage for AI agent functionality + +### 4. Configuration Hardcoding +**Issue**: Ports, timeouts, limits hardcoded throughout +**Found**: 15+ hardcoded values (ports 3000/4000, timeouts 30000ms, etc.) + +Required actions: +- Move all ports to environment variables +- Create config file for timeouts and limits +- Add sensible defaults with override capability +- Document all configuration options + +## 🟡 Priority 1: High Priority Issues + +### 1. Input Validation Missing +**Security Risk**: No validation on CLI commands, file paths, or API inputs + +Required actions: +- Add path traversal protection +- Validate all CLI command inputs +- Add request validation to API endpoints +- Sanitize user-provided file paths + +### 2. Environment Variable Validation +**Issue**: Missing env vars cause runtime crashes + +Required actions: +- Check all required env vars on startup +- Provide sensible defaults where appropriate +- Create .env.example with all variables documented +- Add validation for API keys format + +### 3. Progress Indicators Missing +**UX Issue**: Long operations appear frozen + +Required actions: +- Add progress bars for operations > 1 second +- Show current file being processed +- Display ETA for long operations +- Add spinner for indeterminate operations + +## 🟠 Priority 2: Performance & Quality Issues + +### Implement Incremental Parsing +Current: Full re-parse takes 13.2s every time +Target: Under 100ms for single file changes + +Immediate actions: +- Track file modification times +- Cache parsed ASTs +- Only re-parse changed files +- Update dependency graph incrementally + +### Code Quality Issues +Found during bug analysis: +- Several functions > 100 lines need refactoring +- Deep nesting (5+ levels) in multiple places +- Magic numbers without explanation +- Missing JSDoc comments on complex functions + +## Priority 2: Production Readiness (Next Week) + +### Error Handling +Current: Single file error fails entire index + +Immediate actions: +- Wrap all parser.parse() in try-catch +- Continue indexing on parser errors +- Collect and report errors at end +- Add fallback parsing for syntax errors + +### Validation Commands +Add safety commands for users + +Immediate actions: +- Implement indexer validate command +- Implement indexer repair command +- Add index integrity checking +- Create recovery mechanisms + +### Basic Docker Support +Need containerization for deployment + +Immediate actions: +- Create Dockerfile with Node 20 +- Add health check endpoint +- Create docker-compose.yml +- Test with production config + +## Quick Wins (Implement Immediately) + +### File: src/core/indexer.ts +Line 121: Replace unbounded Map with CacheManager +```typescript +// Change from: +private fileCache: Map = new Map(); +// To: +private fileCache: CacheManager; +``` + +### File: src/cli/index.ts +Add progress logging to scan command +```typescript +console.log(`Scanning: ${processed}/${total} files (${Math.round(percent)}%)`); +``` + +### File: src/ai/sdk-analyzer.ts +Set model to Opus 4.1 +```typescript +model: process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805' +``` + +### File: tsconfig.json +Enable strict type checking +```json +"noImplicitAny": true, +"strictNullChecks": true +``` + +## Files to Remove or Deprioritize + +### Not Essential for Core Functionality +- src/cursor/multi-repo-integration.ts (417 lines of IDE-specific code) +- src/vscode/multi-repo-extension.ts (VS Code extension) +- src/parsers/wasm/* (experimental, not ready) + +These provide nice visualizations but are not needed for the indexer to work. + +## Testing Strategy + +### Immediate Test Files Needed +1. test/core/smart-indexer.test.ts +2. test/core/multi-repo-knowledge-graph.test.ts +3. test/ai/sdk-analyzer.test.ts +4. test/api/rest-routes.test.ts +5. test/parsers/go.test.ts +6. test/parsers/sql.test.ts +7. test/parsers/graphql.test.ts +8. test/parsers/yaml.test.ts +9. test/parsers/astro.test.ts + +### Test Template +```typescript +describe('ComponentName', () => { + let component: ComponentClass; + + beforeEach(() => { + component = new ComponentClass(); + }); + + test('handles normal input', () => { + const result = component.process(validInput); + expect(result).toBeDefined(); + }); + + test('handles error gracefully', () => { + expect(() => component.process(invalidInput)).not.toThrow(); + }); + + test('uses cache properly', () => { + const spy = jest.spyOn(CacheManager.prototype, 'set'); + component.process(input); + expect(spy).toHaveBeenCalled(); + }); +}); +``` + +## API Endpoints to Test + +### Priority Endpoints +- POST /api/index - Core indexing +- POST /api/ai/analyze - AI analysis with Opus 4.1 +- GET /api/health - Health check +- POST /api/query - Search functionality + +### Test with curl +```bash +# Health check +curl http://localhost:4000/health + +# Trigger indexing +curl -X POST http://localhost:4000/api/index \ + -H "Content-Type: application/json" \ + -d '{"path": ".", "options": {"parallel": true}}' + +# AI analysis +ANTHROPIC_API_KEY=your-key curl -X POST http://localhost:4000/api/ai/analyze \ + -H "Content-Type: application/json" +``` + +## Configuration Updates + +### .indexer.yml (add to project root) +```yaml +version: 2 +name: indexer +performance: + parallel: true + workers: 4 + cache: true + maxFileSize: 2MB +ai: + model: claude-opus-4-1-20250805 + maxConcurrentAgents: 4 +``` + +### Environment Variables (.env) +```bash +ANTHROPIC_API_KEY=sk-ant-... +CLAUDE_MODEL=claude-opus-4-1-20250805 +NODE_ENV=development +DEBUG=indexer:* +``` + +## 📊 Current Project Statistics + +| Category | Count | Status | Severity | +|----------|-------|--------|----------| +| TypeScript Errors | ~80 | ❌ Pending | 🔴 Critical | +| Console.logs | 0 | ✅ Fixed | ✅ Done | +| Dead Code | 0 | ✅ Cleaned | ✅ Done | +| Test Coverage | 25% | ❌ Low | 🔴 Critical | +| Missing Tests | 40+ files | ❌ Pending | 🔴 Critical | +| Hardcoded Values | 15+ | ❌ Pending | 🟡 High | +| Input Validation | 0 | ❌ Missing | 🟡 High | +| VS Code Extension | N/A | ✅ Removed | ✅ Done | + +## Success Metrics (Updated) + +### Week 1 Goals +- ✅ Memory leaks: Fixed (CacheManager with LRU) +- ✅ Type safety: Core bugs fixed +- ✅ Error handling: Implemented +- ⏳ Test coverage: 25% → 40% (in progress) +- ❌ Logger implementation: 0% → 100% (pending) + +### Week 2 Goals +- Test coverage: 40% → 80% +- Incremental parsing: Implement +- Docker deployment: Create Dockerfile +- Production API: Deploy with proper logging + +## Commands to Run Now + +```bash +# Install missing type definitions +npm install --save-dev @types/jsonwebtoken @types/bcrypt @types/node + +# Build and check for type errors +npm run build + +# Run existing tests +npm test + +# Start API server +npm run api + +# Check memory usage +node --expose-gc --trace-gc dist/cli/index.js scan +``` + +## Definition of Done + +Task is complete when: +- Code builds without errors +- Tests pass with coverage +- No TypeScript warnings +- Memory usage stable +- Performance benchmarks met +- Error handling verified + +--- + +## Architecture Refactoring (Long-term Vision) + +### 1. Adopt a Plugin Architecture + +Transform the monolithic structure into modular plugins: + +``` +// indexer-core (minimal, fast) +├── core indexing +├── language parsers +└── basic CLI + +// Optional plugins +├── @indexer/ai-chat (Claude integration) +├── @indexer/web-ui (API + dashboard) +├── @indexer/integrations (Slack, Linear, Datadog) +├── @indexer/visualizers (Mermaid, GraphViz) +└── @indexer/advanced (Multi-repo, monorepo) +``` + +**Benefits:** +- Users only install what they need +- Faster initial setup +- Easier maintenance +- Clearer dependencies + +### 2. Simplify CLI with Progressive Disclosure + +Implement a tiered approach to reduce cognitive load: + +```bash +# Level 1: Essential (90% of users) +indexer # Smart mode - does everything automatically +indexer scan # Update index +indexer search # Search code +indexer chat # AI assistant + +# Level 2: Advanced (via --advanced flag or plugins) +indexer --advanced export mermaid +indexer --advanced slack setup +indexer --advanced multi-repo analyze +``` + +### 3. Consolidate AI Features + +Create a unified AI interface: + +```typescript +// Single entry point for all AI features +class IndexerAI { + chat() // Interactive chat + analyze() // Comprehensive analysis + predict() // Bug prediction + security() // Security scan + suggest() // Refactoring suggestions + + // Stream everything by default + stream: true +} +``` + +### 4. Improve First-Run Experience + +Enhanced onboarding: + +```bash +$ npx @cloneglobal/indexer + +Welcome to Indexer! Let's set up your project. + +? What would you like to do? (Use arrow keys) +❯ Quick Start (index + chat) # 90% choose this + Full Analysis (all features) + Custom Setup + +? Enable AI features? (Y/n) # Auto-detect API key +✓ Found ANTHROPIC_API_KEY + +Indexing your project... +✓ Indexed 1,408 files in 13.2s + +Ready! You can now: + • Chat with AI: indexer chat + • Search code: indexer search + • View docs: indexer docs +``` + +### 5. Unified Configuration + +Single config file for everything: + +```yaml +# .indexer.yml - Everything in one place +version: 3 + +# Core settings (always active) +core: + include: ["**/*.{js,ts,py,go}"] + ignore: ["**/node_modules/**"] + output: .indexer/ + +# Feature flags (opt-in) +features: + ai: true # Enable AI features + web: false # Web UI + watch: true # File watching + +# Integrations (optional) +integrations: + slack: + enabled: false + datadog: + enabled: false + +# Simplified +preset: 'default' # Or 'minimal', 'full', 'enterprise' +``` + +## 🔧 Specific Improvements + +### 1. Enhanced Error Recovery + +```typescript +// Current: Crashes on parse errors +// Better: Graceful degradation +try { + await parseTypeScript(file); +} catch (error) { + // Still index the file with basic info + index.addFile(file, { + language: 'typescript', + parseError: true, + fallback: await basicTextAnalysis(file) + }); +} +``` + +### 2. Smarter Caching + +```typescript +// Current: Full re-index on changes +// Better: Incremental updates with dependency tracking +class SmartCache { + async updateFile(path: string) { + const affected = this.getDependents(path); + // Only re-index affected files + await this.indexFiles([path, ...affected]); + } +} +``` + +### 3. Better AI Context Management + +```typescript +// Current: Sends entire index +// Better: Smart context selection +class ContextOptimizer { + async getRelevantContext(query: string) { + // Use embeddings to find relevant files + const relevantFiles = await this.semanticSearch(query); + + // Include dependency graph + const dependencies = this.getDependencies(relevantFiles); + + // Return minimal but complete context + return this.buildContext(relevantFiles, dependencies); + } +} +``` + +### 4. Unified Export System + +```typescript +// Current: Separate exporters +// Better: Single export command with format detection +indexer export analysis.md # Auto-detects markdown +indexer export graph.svg # Auto-detects GraphViz +indexer export report.html # Auto-detects HTML +``` + +## 📊 Performance Optimizations + +### 1. Lazy Loading + +```typescript +// Don't load all parsers upfront +const parsers = new Map(); +function getParser(language: string) { + if (!parsers.has(language)) { + parsers.set(language, import(`./parsers/${language}`)); + } + return parsers.get(language); +} +``` + +### 2. Streaming Index Building + +```typescript +// Current: Build entire index in memory +// Better: Stream to disk for large projects +class StreamingIndexer { + async *indexFiles(files: string[]) { + for (const batch of chunks(files, 100)) { + const indexed = await this.indexBatch(batch); + yield indexed; // Stream results + await this.writeToDisk(indexed); // Progressive save + } + } +} +``` + +## 🎨 UI/UX Improvements + +### 1. Better Progress Indicators + +```bash +Indexing Clone Global (5 services) +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% | 1408/1408 files | 13.2s +✓ Frontend ████ 234 files (TypeScript, React) +✓ Backend ████ 289 files (Go, SQL) +✓ Skills ████ 156 files (Python) +✓ Data-ops ████ 89 files (Python, SQL) +✓ Marketing ████ 67 files (Astro, TypeScript) +``` + +### 2. Interactive Mode by Default + +```bash +$ indexer +? What would you like to do? (Use arrow keys) +❯ Chat with AI about this code + Search for something + Analyze code quality + View project statistics + Export documentation + Start web dashboard +``` + +## 📚 Documentation Restructure + +``` +docs/ +├── README.md # Quick start (2 min read) +├── GUIDE.md # Complete guide (10 min) +├── API.md # API reference +├── PLUGINS.md # Plugin development +└── examples/ # Copy-paste examples + ├── basic.md + ├── ai-chat.md + ├── ci-cd.md + └── enterprise.md +``` + +## ⚡ Minimal Version Recommendation + +For users who just want fast indexing: + +```bash +npm install @cloneglobal/indexer-core + +indexer scan # Just builds index +indexer find "function" # Basic search +# That's it - 2 commands, no complexity +``` + +## 🎯 Priority Order for Refactoring + +1. **Plugin architecture** - Biggest impact on usability +2. **Simplify CLI** - Reduce cognitive load +3. **Better first-run** - Critical for adoption +4. **Smart caching** - Performance improvement +5. **Documentation** - Make features discoverable + +## 🚀 Features to Keep + +These innovations are working well and should be preserved: +- **SmartIndexer** - The automatic everything mode is brilliant +- **Claude Chat** - Well-implemented and useful +- **Multi-language support** - Real-world necessity - **Project detection** - Smart auto-configuration \ No newline at end of file diff --git a/config/mcp-servers.json b/config/mcp-servers.json index d7286c4..86be34b 100644 --- a/config/mcp-servers.json +++ b/config/mcp-servers.json @@ -1,94 +1,94 @@ -{ - "mcpServers": { - "slack": { - "command": "node", - "args": ["./mcp/slack-server.js"], - "env": { - "SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}", - "SLACK_APP_TOKEN": "${SLACK_APP_TOKEN}" - } - }, - "linear": { - "command": "node", - "args": ["./mcp/linear-server.js"], - "env": { - "LINEAR_API_KEY": "${LINEAR_API_KEY}" - } - }, - "datadog": { - "command": "node", - "args": ["./mcp/datadog-server.js"], - "env": { - "DD_API_KEY": "${DD_API_KEY}", - "DD_APP_KEY": "${DD_APP_KEY}" - } - }, - "git": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-git"] - }, - "github": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-github"], - "env": { - "GITHUB_TOKEN": "${GITHUB_TOKEN}" - } - }, - "filesystem": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-filesystem"], - "env": { - "ALLOWED_PATHS": "${PROJECT_ROOT}" - } - }, - "postgres": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-postgres"], - "env": { - "DATABASE_URL": "${DATABASE_URL}" - } - }, - "indexer": { - "command": "node", - "args": ["./mcp/indexer-server.js"], - "env": { - "INDEXER_ROOT": "${PROJECT_ROOT}" - } - }, - "semgrep": { - "command": "node", - "args": ["./mcp/semgrep-server.js"] - }, - "snyk": { - "command": "node", - "args": ["./mcp/snyk-server.js"], - "env": { - "SNYK_TOKEN": "${SNYK_TOKEN}" - } - }, - "jest": { - "command": "node", - "args": ["./mcp/jest-server.js"] - }, - "profiler": { - "command": "node", - "args": ["./mcp/profiler-server.js"] - }, - "benchmark": { - "command": "node", - "args": ["./mcp/benchmark-server.js"] - }, - "dependency_analyzer": { - "command": "node", - "args": ["./mcp/dependency-analyzer-server.js"] - }, - "code_metrics": { - "command": "node", - "args": ["./mcp/code-metrics-server.js"] - }, - "coverage_reporter": { - "command": "node", - "args": ["./mcp/coverage-reporter-server.js"] - } - } +{ + "mcpServers": { + "slack": { + "command": "node", + "args": ["./mcp/slack-server.js"], + "env": { + "SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}", + "SLACK_APP_TOKEN": "${SLACK_APP_TOKEN}" + } + }, + "linear": { + "command": "node", + "args": ["./mcp/linear-server.js"], + "env": { + "LINEAR_API_KEY": "${LINEAR_API_KEY}" + } + }, + "datadog": { + "command": "node", + "args": ["./mcp/datadog-server.js"], + "env": { + "DD_API_KEY": "${DD_API_KEY}", + "DD_APP_KEY": "${DD_APP_KEY}" + } + }, + "git": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-git"] + }, + "github": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-github"], + "env": { + "GITHUB_TOKEN": "${GITHUB_TOKEN}" + } + }, + "filesystem": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem"], + "env": { + "ALLOWED_PATHS": "${PROJECT_ROOT}" + } + }, + "postgres": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-postgres"], + "env": { + "DATABASE_URL": "${DATABASE_URL}" + } + }, + "indexer": { + "command": "node", + "args": ["./mcp/indexer-server.js"], + "env": { + "INDEXER_ROOT": "${PROJECT_ROOT}" + } + }, + "semgrep": { + "command": "node", + "args": ["./mcp/semgrep-server.js"] + }, + "snyk": { + "command": "node", + "args": ["./mcp/snyk-server.js"], + "env": { + "SNYK_TOKEN": "${SNYK_TOKEN}" + } + }, + "jest": { + "command": "node", + "args": ["./mcp/jest-server.js"] + }, + "profiler": { + "command": "node", + "args": ["./mcp/profiler-server.js"] + }, + "benchmark": { + "command": "node", + "args": ["./mcp/benchmark-server.js"] + }, + "dependency_analyzer": { + "command": "node", + "args": ["./mcp/dependency-analyzer-server.js"] + }, + "code_metrics": { + "command": "node", + "args": ["./mcp/code-metrics-server.js"] + }, + "coverage_reporter": { + "command": "node", + "args": ["./mcp/coverage-reporter-server.js"] + } + } } \ No newline at end of file diff --git a/cypress.config.ts b/cypress.config.ts index d6c6444..42fb18b 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,38 +1,38 @@ -import { defineConfig } from 'cypress'; - -export default defineConfig({ - e2e: { - baseUrl: 'http://localhost:3000', - supportFile: 'cypress/support/e2e.ts', - specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', - video: false, - screenshotOnRunFailure: false, - viewportWidth: 1280, - viewportHeight: 720, - defaultCommandTimeout: 10000, - execTimeout: 60000, - taskTimeout: 60000, - setupNodeEvents(on, config) { - // Add custom tasks - on('task', { - mockClaudeOperation({ type, file }) { - // Mock Claude Code operation for testing hooks - return { - hookTriggered: true, - type, - file, - timestamp: new Date().toISOString() - }; - } - }); - - // Add code coverage - require('@cypress/code-coverage/task')(on, config); - - return config; - }, - }, - env: { - HOME: process.env.HOME || process.env.USERPROFILE, - } +import { defineConfig } from 'cypress'; + +export default defineConfig({ + e2e: { + baseUrl: 'http://localhost:3000', + supportFile: 'cypress/support/e2e.ts', + specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', + video: false, + screenshotOnRunFailure: false, + viewportWidth: 1280, + viewportHeight: 720, + defaultCommandTimeout: 10000, + execTimeout: 60000, + taskTimeout: 60000, + setupNodeEvents(on, config) { + // Add custom tasks + on('task', { + mockClaudeOperation({ type, file }) { + // Mock Claude Code operation for testing hooks + return { + hookTriggered: true, + type, + file, + timestamp: new Date().toISOString() + }; + } + }); + + // Add code coverage + require('@cypress/code-coverage/task')(on, config); + + return config; + }, + }, + env: { + HOME: process.env.HOME || process.env.USERPROFILE, + } }); \ No newline at end of file diff --git a/cypress/e2e/indexer.cy.ts b/cypress/e2e/indexer.cy.ts index d257fdd..94b4069 100644 --- a/cypress/e2e/indexer.cy.ts +++ b/cypress/e2e/indexer.cy.ts @@ -1,380 +1,380 @@ -describe('Claude Code Indexer', () => { - describe('Initialization', () => { - it('should create INDEX.json on init', () => { - // Test that indexer init creates the index file - cy.exec('indexer init --mode quick', { failOnNonZeroExit: false }) - .then((result) => { - expect(result.code).to.equal(0); - cy.readFile('.indexer-output/current/indexes/INDEX.json').should('exist'); - }); - }); - - it('should respect .gitignore patterns', () => { - // Create test files - cy.writeFile('test.js', 'console.log("test");'); - cy.writeFile('node_modules/test.js', 'console.log("ignored");'); - - cy.exec('indexer scan --quiet') - .then(() => { - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { - expect(index.files).to.have.property('test.js'); - expect(index.files).to.not.have.property('node_modules/test.js'); - }); - }); - }); - - it('should handle large codebases efficiently', () => { - // Test with multiple files - const files = Array.from({ length: 100 }, (_, i) => `test${i}.js`); - files.forEach(file => { - cy.writeFile(file, `function test${file}() { return true; }`); - }); - - const start = Date.now(); - cy.exec('indexer scan --parallel 4') - .then(() => { - const elapsed = Date.now() - start; - expect(elapsed).to.be.lessThan(30000); // Should complete in 30 seconds - }); - }); - }); - - describe('Parsing', () => { - it('should extract JavaScript functions and classes', () => { - cy.writeFile('sample.js', ` - function testFunction(param1, param2) { - return param1 + param2; - } - - class TestClass { - constructor() { - this.value = 0; - } - - increment() { - this.value++; - } - } - `); - - cy.exec('indexer scan') - .then(() => { - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { - const file = index.files['sample.js']; - expect(file.functions).to.have.lengthOf(1); - expect(file.classes).to.have.lengthOf(1); - expect(file.functions[0].name).to.equal('testFunction'); - expect(file.classes[0].name).to.equal('TestClass'); - }); - }); - }); - - it('should parse TypeScript interfaces and types', () => { - cy.writeFile('sample.ts', ` - interface User { - id: number; - name: string; - } - - type Status = 'active' | 'inactive'; - - function getUser(id: number): User { - return { id, name: 'Test' }; - } - `); - - cy.exec('indexer scan') - .then(() => { - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { - const file = index.files['sample.ts']; - expect(file.classes).to.have.length.greaterThan(0); - expect(file.functions).to.have.lengthOf(1); - }); - }); - }); - - it('should handle Python classes and functions', () => { - cy.writeFile('sample.py', ` -def test_function(param1, param2): - return param1 + param2 - -class TestClass: - def __init__(self): - self.value = 0 - - def increment(self): - self.value += 1 - `); - - cy.exec('indexer scan') - .then(() => { - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { - const file = index.files['sample.py']; - expect(file.functions).to.have.lengthOf(1); - expect(file.classes).to.have.lengthOf(1); - }); - }); - }); - - it('should detect circular dependencies', () => { - cy.writeFile('a.js', `import { b } from './b';`); - cy.writeFile('b.js', `import { a } from './a';`); - - cy.exec('indexer scan') - .then(() => { - cy.exec('indexer health --check circular') - .then((result) => { - expect(result.stdout).to.include('Circular Dependencies'); - }); - }); - }); - }); - - describe('File Watching', () => { - it('should update index on file changes', () => { - cy.writeFile('watched.js', 'function initial() {}'); - - cy.exec('indexer scan') - .then(() => { - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index1) => { - const timestamp1 = index1.timestamp; - - // Modify file - cy.wait(1000); - cy.writeFile('watched.js', 'function updated() {}'); - - // Run incremental scan - cy.exec('indexer scan --incremental') - .then(() => { - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index2) => { - expect(index2.timestamp).to.not.equal(timestamp1); - expect(index2.files['watched.js'].functions[0].name).to.equal('updated'); - }); - }); - }); - }); - }); - - it('should debounce rapid changes', () => { - // Create rapid changes - for (let i = 0; i < 5; i++) { - cy.writeFile(`rapid${i}.js`, `function test${i}() {}`); - } - - cy.exec('indexer scan --incremental') - .then((result) => { - // Should process all files in one batch - expect(result.stdout).to.not.include('Processing 1 file'); - }); - }); - - it('should handle file deletions', () => { - cy.writeFile('temporary.js', 'function temp() {}'); - - cy.exec('indexer scan') - .then(() => { - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index1) => { - expect(index1.files).to.have.property('temporary.js'); - - // Delete file - cy.exec('rm temporary.js') - .then(() => { - cy.exec('indexer scan --incremental') - .then(() => { - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index2) => { - expect(index2.files).to.not.have.property('temporary.js'); - }); - }); - }); - }); - }); - }); - }); - - describe('Claude Code Hooks', () => { - it('should install hooks in settings.json', () => { - cy.exec('node install.js', { failOnNonZeroExit: false }) - .then(() => { - const settingsPath = `${Cypress.env('HOME')}/.config/claude/settings.json`; - cy.readFile(settingsPath, { failOnNonExist: false }) - .then((settings) => { - if (settings) { - expect(settings.hooks).to.exist; - expect(settings.hooks['pre-tool-use']).to.be.an('array'); - expect(settings.hooks['post-tool-use']).to.be.an('array'); - } - }); - }); - }); - - it('should trigger on file operations', () => { - // This would require a mock Claude Code environment - cy.task('mockClaudeOperation', { type: 'read', file: 'test.js' }) - .then((result) => { - expect(result).to.have.property('hookTriggered', true); - }); - }); - - it('should not affect Claude context', () => { - // Test that hooks don't add to context window - cy.exec('indexer hook --status') - .then((result) => { - expect(result.stdout).to.include('Hook Status'); - }); - }); - }); - - describe('Performance', () => { - it('should index 1000 files in under 30 seconds', () => { - // Create 1000 test files - const files = Array.from({ length: 1000 }, (_, i) => ({ - name: `perf/test${i}.js`, - content: `function test${i}() { return ${i}; }` - })); - - files.forEach(file => { - cy.writeFile(file.name, file.content); - }); - - const start = Date.now(); - cy.exec('indexer scan --parallel 8', { timeout: 60000 }) - .then(() => { - const elapsed = Date.now() - start; - expect(elapsed).to.be.lessThan(30000); - - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { - expect(Object.keys(index.files)).to.have.lengthOf.at.least(1000); - }); - }); - }); - - it('should update single file in under 100ms', () => { - cy.writeFile('single.js', 'function original() {}'); - cy.exec('indexer scan') - .then(() => { - cy.writeFile('single.js', 'function modified() {}'); - - const start = Date.now(); - cy.exec('indexer scan --incremental') - .then(() => { - const elapsed = Date.now() - start; - expect(elapsed).to.be.lessThan(100); - }); - }); - }); - - it('should use less than 100MB memory', () => { - // This would require process monitoring - cy.exec('indexer scan --profile') - .then((result) => { - // Parse memory usage from profile output - const memMatch = result.stdout.match(/Peak memory: (\d+)MB/); - if (memMatch) { - const memory = parseInt(memMatch[1]); - expect(memory).to.be.lessThan(100); - } - }); - }); - }); -}); - -describe('New Commands', () => { - describe('Clean Command', () => { - it('should clean all directories', () => { - cy.exec('indexer scan --quiet'); - cy.exec('indexer clean').then((result) => { - expect(result.code).to.equal(0); - expect(result.stdout).to.contain('Cleanup complete'); - }); - }); - - it('should clean only cache', () => { - cy.exec('indexer clean --cache').then((result) => { - expect(result.code).to.equal(0); - expect(result.stdout).to.contain('Cache cleaned'); - }); - }); - }); - - describe('Config Command', () => { - it('should show configuration', () => { - cy.exec('indexer config').then((result) => { - expect(result.code).to.equal(0); - expect(result.stdout).to.contain('Current Indexer Configuration'); - }); - }); - - it('should create YAML config', () => { - cy.exec('rm -f .indexer.yml', { failOnNonZeroExit: false }); - cy.exec('indexer config --init').then((result) => { - expect(result.code).to.equal(0); - cy.readFile('.indexer.yml').should('exist'); - }); - }); - - it('should create JSON config', () => { - cy.exec('rm -f .indexerconfig.json', { failOnNonZeroExit: false }); - cy.exec('indexer config --init --format json').then((result) => { - expect(result.code).to.equal(0); - cy.readFile('.indexerconfig.json').should('exist'); - }); - }); - }); -}); - -describe('Performance Benchmarks', () => { - it('should handle React project with 5000 components', () => { - // Create mock React components - for (let i = 0; i < 5000; i++) { - const component = ` -import React from 'react'; - -export function Component${i}() { - return
Component ${i}
; -} - `; - cy.writeFile(`components/Component${i}.tsx`, component); - } - - cy.exec('indexer scan --language tsx', { timeout: 120000 }) - .then((result) => { - expect(result.code).to.equal(0); - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { - expect(Object.keys(index.files)).to.have.lengthOf.at.least(5000); - }); - }); - }); - - it('should process monorepo with multiple packages', () => { - // Create monorepo structure - ['frontend', 'backend', 'shared', 'utils'].forEach(pkg => { - cy.writeFile(`packages/${pkg}/index.js`, `export default '${pkg}';`); - cy.writeFile(`packages/${pkg}/package.json`, JSON.stringify({ name: pkg })); - }); - - cy.exec('indexer scan') - .then(() => { - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { - expect(index.monorepo).to.exist; - expect(Object.keys(index.monorepo.services)).to.have.lengthOf.at.least(4); - }); - }); - }); - - it('should work with mixed language projects', () => { - // Create files in different languages - cy.writeFile('app.js', 'function jsFunction() {}'); - cy.writeFile('server.py', 'def py_function(): pass'); - cy.writeFile('main.go', 'func goFunction() {}'); - cy.writeFile('query.sql', 'SELECT * FROM users;'); - cy.writeFile('schema.graphql', 'type Query { user: User }'); - - cy.exec('indexer scan') - .then(() => { - cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { - const languages = Object.keys(index.statistics.languages); - expect(languages).to.include.members(['JavaScript', 'Python', 'Go', 'SQL', 'GraphQL']); - }); - }); - }); +describe('Claude Code Indexer', () => { + describe('Initialization', () => { + it('should create INDEX.json on init', () => { + // Test that indexer init creates the index file + cy.exec('indexer init --mode quick', { failOnNonZeroExit: false }) + .then((result) => { + expect(result.code).to.equal(0); + cy.readFile('.indexer-output/current/indexes/INDEX.json').should('exist'); + }); + }); + + it('should respect .gitignore patterns', () => { + // Create test files + cy.writeFile('test.js', 'console.log("test");'); + cy.writeFile('node_modules/test.js', 'console.log("ignored");'); + + cy.exec('indexer scan --quiet') + .then(() => { + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { + expect(index.files).to.have.property('test.js'); + expect(index.files).to.not.have.property('node_modules/test.js'); + }); + }); + }); + + it('should handle large codebases efficiently', () => { + // Test with multiple files + const files = Array.from({ length: 100 }, (_, i) => `test${i}.js`); + files.forEach(file => { + cy.writeFile(file, `function test${file}() { return true; }`); + }); + + const start = Date.now(); + cy.exec('indexer scan --parallel 4') + .then(() => { + const elapsed = Date.now() - start; + expect(elapsed).to.be.lessThan(30000); // Should complete in 30 seconds + }); + }); + }); + + describe('Parsing', () => { + it('should extract JavaScript functions and classes', () => { + cy.writeFile('sample.js', ` + function testFunction(param1, param2) { + return param1 + param2; + } + + class TestClass { + constructor() { + this.value = 0; + } + + increment() { + this.value++; + } + } + `); + + cy.exec('indexer scan') + .then(() => { + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { + const file = index.files['sample.js']; + expect(file.functions).to.have.lengthOf(1); + expect(file.classes).to.have.lengthOf(1); + expect(file.functions[0].name).to.equal('testFunction'); + expect(file.classes[0].name).to.equal('TestClass'); + }); + }); + }); + + it('should parse TypeScript interfaces and types', () => { + cy.writeFile('sample.ts', ` + interface User { + id: number; + name: string; + } + + type Status = 'active' | 'inactive'; + + function getUser(id: number): User { + return { id, name: 'Test' }; + } + `); + + cy.exec('indexer scan') + .then(() => { + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { + const file = index.files['sample.ts']; + expect(file.classes).to.have.length.greaterThan(0); + expect(file.functions).to.have.lengthOf(1); + }); + }); + }); + + it('should handle Python classes and functions', () => { + cy.writeFile('sample.py', ` +def test_function(param1, param2): + return param1 + param2 + +class TestClass: + def __init__(self): + self.value = 0 + + def increment(self): + self.value += 1 + `); + + cy.exec('indexer scan') + .then(() => { + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { + const file = index.files['sample.py']; + expect(file.functions).to.have.lengthOf(1); + expect(file.classes).to.have.lengthOf(1); + }); + }); + }); + + it('should detect circular dependencies', () => { + cy.writeFile('a.js', `import { b } from './b';`); + cy.writeFile('b.js', `import { a } from './a';`); + + cy.exec('indexer scan') + .then(() => { + cy.exec('indexer health --check circular') + .then((result) => { + expect(result.stdout).to.include('Circular Dependencies'); + }); + }); + }); + }); + + describe('File Watching', () => { + it('should update index on file changes', () => { + cy.writeFile('watched.js', 'function initial() {}'); + + cy.exec('indexer scan') + .then(() => { + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index1) => { + const timestamp1 = index1.timestamp; + + // Modify file + cy.wait(1000); + cy.writeFile('watched.js', 'function updated() {}'); + + // Run incremental scan + cy.exec('indexer scan --incremental') + .then(() => { + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index2) => { + expect(index2.timestamp).to.not.equal(timestamp1); + expect(index2.files['watched.js'].functions[0].name).to.equal('updated'); + }); + }); + }); + }); + }); + + it('should debounce rapid changes', () => { + // Create rapid changes + for (let i = 0; i < 5; i++) { + cy.writeFile(`rapid${i}.js`, `function test${i}() {}`); + } + + cy.exec('indexer scan --incremental') + .then((result) => { + // Should process all files in one batch + expect(result.stdout).to.not.include('Processing 1 file'); + }); + }); + + it('should handle file deletions', () => { + cy.writeFile('temporary.js', 'function temp() {}'); + + cy.exec('indexer scan') + .then(() => { + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index1) => { + expect(index1.files).to.have.property('temporary.js'); + + // Delete file + cy.exec('rm temporary.js') + .then(() => { + cy.exec('indexer scan --incremental') + .then(() => { + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index2) => { + expect(index2.files).to.not.have.property('temporary.js'); + }); + }); + }); + }); + }); + }); + }); + + describe('Claude Code Hooks', () => { + it('should install hooks in settings.json', () => { + cy.exec('node install.js', { failOnNonZeroExit: false }) + .then(() => { + const settingsPath = `${Cypress.env('HOME')}/.config/claude/settings.json`; + cy.readFile(settingsPath, { failOnNonExist: false }) + .then((settings) => { + if (settings) { + expect(settings.hooks).to.exist; + expect(settings.hooks['pre-tool-use']).to.be.an('array'); + expect(settings.hooks['post-tool-use']).to.be.an('array'); + } + }); + }); + }); + + it('should trigger on file operations', () => { + // This would require a mock Claude Code environment + cy.task('mockClaudeOperation', { type: 'read', file: 'test.js' }) + .then((result) => { + expect(result).to.have.property('hookTriggered', true); + }); + }); + + it('should not affect Claude context', () => { + // Test that hooks don't add to context window + cy.exec('indexer hook --status') + .then((result) => { + expect(result.stdout).to.include('Hook Status'); + }); + }); + }); + + describe('Performance', () => { + it('should index 1000 files in under 30 seconds', () => { + // Create 1000 test files + const files = Array.from({ length: 1000 }, (_, i) => ({ + name: `perf/test${i}.js`, + content: `function test${i}() { return ${i}; }` + })); + + files.forEach(file => { + cy.writeFile(file.name, file.content); + }); + + const start = Date.now(); + cy.exec('indexer scan --parallel 8', { timeout: 60000 }) + .then(() => { + const elapsed = Date.now() - start; + expect(elapsed).to.be.lessThan(30000); + + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { + expect(Object.keys(index.files)).to.have.lengthOf.at.least(1000); + }); + }); + }); + + it('should update single file in under 100ms', () => { + cy.writeFile('single.js', 'function original() {}'); + cy.exec('indexer scan') + .then(() => { + cy.writeFile('single.js', 'function modified() {}'); + + const start = Date.now(); + cy.exec('indexer scan --incremental') + .then(() => { + const elapsed = Date.now() - start; + expect(elapsed).to.be.lessThan(100); + }); + }); + }); + + it('should use less than 100MB memory', () => { + // This would require process monitoring + cy.exec('indexer scan --profile') + .then((result) => { + // Parse memory usage from profile output + const memMatch = result.stdout.match(/Peak memory: (\d+)MB/); + if (memMatch) { + const memory = parseInt(memMatch[1]); + expect(memory).to.be.lessThan(100); + } + }); + }); + }); +}); + +describe('New Commands', () => { + describe('Clean Command', () => { + it('should clean all directories', () => { + cy.exec('indexer scan --quiet'); + cy.exec('indexer clean').then((result) => { + expect(result.code).to.equal(0); + expect(result.stdout).to.contain('Cleanup complete'); + }); + }); + + it('should clean only cache', () => { + cy.exec('indexer clean --cache').then((result) => { + expect(result.code).to.equal(0); + expect(result.stdout).to.contain('Cache cleaned'); + }); + }); + }); + + describe('Config Command', () => { + it('should show configuration', () => { + cy.exec('indexer config').then((result) => { + expect(result.code).to.equal(0); + expect(result.stdout).to.contain('Current Indexer Configuration'); + }); + }); + + it('should create YAML config', () => { + cy.exec('rm -f .indexer.yml', { failOnNonZeroExit: false }); + cy.exec('indexer config --init').then((result) => { + expect(result.code).to.equal(0); + cy.readFile('.indexer.yml').should('exist'); + }); + }); + + it('should create JSON config', () => { + cy.exec('rm -f .indexerconfig.json', { failOnNonZeroExit: false }); + cy.exec('indexer config --init --format json').then((result) => { + expect(result.code).to.equal(0); + cy.readFile('.indexerconfig.json').should('exist'); + }); + }); + }); +}); + +describe('Performance Benchmarks', () => { + it('should handle React project with 5000 components', () => { + // Create mock React components + for (let i = 0; i < 5000; i++) { + const component = ` +import React from 'react'; + +export function Component${i}() { + return
Component ${i}
; +} + `; + cy.writeFile(`components/Component${i}.tsx`, component); + } + + cy.exec('indexer scan --language tsx', { timeout: 120000 }) + .then((result) => { + expect(result.code).to.equal(0); + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { + expect(Object.keys(index.files)).to.have.lengthOf.at.least(5000); + }); + }); + }); + + it('should process monorepo with multiple packages', () => { + // Create monorepo structure + ['frontend', 'backend', 'shared', 'utils'].forEach(pkg => { + cy.writeFile(`packages/${pkg}/index.js`, `export default '${pkg}';`); + cy.writeFile(`packages/${pkg}/package.json`, JSON.stringify({ name: pkg })); + }); + + cy.exec('indexer scan') + .then(() => { + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { + expect(index.monorepo).to.exist; + expect(Object.keys(index.monorepo.services)).to.have.lengthOf.at.least(4); + }); + }); + }); + + it('should work with mixed language projects', () => { + // Create files in different languages + cy.writeFile('app.js', 'function jsFunction() {}'); + cy.writeFile('server.py', 'def py_function(): pass'); + cy.writeFile('main.go', 'func goFunction() {}'); + cy.writeFile('query.sql', 'SELECT * FROM users;'); + cy.writeFile('schema.graphql', 'type Query { user: User }'); + + cy.exec('indexer scan') + .then(() => { + cy.readFile('.indexer-output/current/indexes/INDEX.json').then((index) => { + const languages = Object.keys(index.statistics.languages); + expect(languages).to.include.members(['JavaScript', 'Python', 'Go', 'SQL', 'GraphQL']); + }); + }); + }); }); \ No newline at end of file diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 51aae1c..6b91752 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -1,15 +1,15 @@ -/// - -// Custom Cypress commands - -Cypress.Commands.add('initIndexer', (options = {}) => { - const mode = options.mode || 'quick'; - cy.exec(`indexer init --mode ${mode}`, { failOnNonZeroExit: false }); -}); - -Cypress.Commands.add('cleanupTestFiles', () => { - cy.exec('rm -rf test-* *.js *.ts *.py *.go PROJECT_INDEX.json', { failOnNonZeroExit: false }); -}); - -// Export to satisfy TypeScript +/// + +// Custom Cypress commands + +Cypress.Commands.add('initIndexer', (options = {}) => { + const mode = options.mode || 'quick'; + cy.exec(`indexer init --mode ${mode}`, { failOnNonZeroExit: false }); +}); + +Cypress.Commands.add('cleanupTestFiles', () => { + cy.exec('rm -rf test-* *.js *.ts *.py *.go PROJECT_INDEX.json', { failOnNonZeroExit: false }); +}); + +// Export to satisfy TypeScript export {}; \ No newline at end of file diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 1a67ca1..4c4c4b5 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -1,45 +1,45 @@ -// Cypress support file for e2e tests - -// Import Cypress commands -import './commands'; - -// Add custom commands -declare global { - namespace Cypress { - interface Chainable { - /** - * Custom command to initialize indexer in test directory - */ - initIndexer(options?: { mode?: string }): Chainable; - - /** - * Custom command to clean up test files - */ - cleanupTestFiles(): Chainable; - } - } -} - -// Before each test -beforeEach(() => { - // Clean up any existing test files - cy.exec('rm -rf test-* *.js *.ts *.py *.go PROJECT_INDEX.json', { failOnNonZeroExit: false }); - - // Create a clean test directory - cy.exec('mkdir -p test-workspace', { failOnNonZeroExit: false }); -}); - -// After each test -afterEach(() => { - // Clean up test files - cy.exec('rm -rf test-* *.js *.ts *.py *.go PROJECT_INDEX.json test-workspace', { failOnNonZeroExit: false }); -}); - -// Global error handling -Cypress.on('uncaught:exception', (err, runnable) => { - // Return false to prevent test failure on uncaught exceptions - if (err.message.includes('ENOENT')) { - return false; - } - return true; +// Cypress support file for e2e tests + +// Import Cypress commands +import './commands'; + +// Add custom commands +declare global { + namespace Cypress { + interface Chainable { + /** + * Custom command to initialize indexer in test directory + */ + initIndexer(options?: { mode?: string }): Chainable; + + /** + * Custom command to clean up test files + */ + cleanupTestFiles(): Chainable; + } + } +} + +// Before each test +beforeEach(() => { + // Clean up any existing test files + cy.exec('rm -rf test-* *.js *.ts *.py *.go PROJECT_INDEX.json', { failOnNonZeroExit: false }); + + // Create a clean test directory + cy.exec('mkdir -p test-workspace', { failOnNonZeroExit: false }); +}); + +// After each test +afterEach(() => { + // Clean up test files + cy.exec('rm -rf test-* *.js *.ts *.py *.go PROJECT_INDEX.json test-workspace', { failOnNonZeroExit: false }); +}); + +// Global error handling +Cypress.on('uncaught:exception', (err, runnable) => { + // Return false to prevent test failure on uncaught exceptions + if (err.message.includes('ENOENT')) { + return false; + } + return true; }); \ No newline at end of file diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 09868d6..42f7d4a 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -1,205 +1,205 @@ -# Getting Started with Clone Global Indexer - -## What is this tool? - -The indexer analyzes your entire codebase and creates a single JSON file that AI assistants (like Claude) can read to instantly understand your project structure, without needing to read thousands of individual files. - -## Quick Start (30 seconds) - -```bash -# From your project root (e.g., /clone-global) -npx ts-node indexer/src/cli/index.ts scan . - -# That's it! Your index is ready. -``` - -## What just happened? - -When you ran the scan, the indexer: - -1. **Analyzed 1,916 files** across all your repositories (backend, frontend, skills, etc.) -2. **Extracted key information**: functions, classes, imports, dependencies -3. **Created an index file** at: `.indexer-output/indexes/PROJECT_INDEX.json` (7MB) -4. **Saved you money**: Claude can now read 1 file instead of 1,916 files (95% token savings) - -## Where is my index? - -Your PROJECT_INDEX.json is located at: -``` -/clone-global/.indexer-output/indexes/PROJECT_INDEX.json -``` - -This file contains: -- Every function and class in your codebase -- All imports and dependencies -- File relationships and connections -- Language statistics - -## How do I verify it's working? - -### 1. Check the Statistics -```bash -npx ts-node indexer/src/cli/index.ts stats -``` - -You should see: -- Total Files: 1,643 -- Functions, Classes, Constants counts -- Language breakdown (Go, TypeScript, Python, etc.) - -### 2. Search for a Known Function -```bash -# Search for any function you know exists -npx ts-node indexer/src/cli/index.ts query "handleLogin" -``` - -### 3. Verify the Index File -```bash -# Check file size (should be several MB) -ls -lh .indexer-output/indexes/PROJECT_INDEX.json - -# Preview the structure -head -50 .indexer-output/indexes/PROJECT_INDEX.json -``` - -### 4. Test with Claude -```markdown -Copy this into Claude: - -"I have indexed my codebase. Here's the PROJECT_INDEX.json file: -[paste contents or upload file] - -Now tell me: -1. What languages is my project using? -2. What are the main services/components? -3. Find all authentication-related functions" -``` - -## What can I do with this? - -### For AI Assistance -Instead of copying multiple files to Claude, just share the PROJECT_INDEX.json: -- "Here's my codebase index. Help me find all API endpoints" -- "Using this index, identify potential security issues" -- "Based on this index, suggest refactoring opportunities" - -### For Development -```bash -# Find unused functions -npx ts-node indexer/src/cli/index.ts query --unused - -# Search across all repos -npx ts-node indexer/src/cli/index.ts query "payment" - -# Check complexity -npx ts-node indexer/src/cli/index.ts query --complex -``` - -### For Documentation -```bash -# Generate markdown documentation -npx ts-node indexer/src/cli/index.ts export markdown - -# Create dependency graph -npx ts-node indexer/src/cli/index.ts export graphviz -``` - -## Common Use Cases - -### 1. New Developer Onboarding -Share PROJECT_INDEX.json with new team members or AI assistants to instantly understand the codebase structure. - -### 2. Code Review -```bash -# Re-index after changes -npx ts-node indexer/src/cli/index.ts scan . - -# Compare with previous index to see what changed -``` - -### 3. Finding Code -```bash -# Find all React components -npx ts-node indexer/src/cli/index.ts query "Component" - -# Find all database queries -npx ts-node indexer/src/cli/index.ts query "SELECT" -``` - -### 4. AI-Powered Analysis -```bash -# Generate the index -npx ts-node indexer/src/cli/index.ts scan . - -# Use with Claude for: -- Bug prediction -- Security analysis -- Architecture review -- Test generation -``` - -## Troubleshooting - -### Index not created? -Check the output directory: -```bash -ls -la .indexer-output/indexes/ -``` - -### Scan seems incomplete? -Check ignored files: -```bash -cat .indexerignore -``` - -### Need more detail? -Use verbose mode: -```bash -npx ts-node indexer/src/cli/index.ts scan . --verbose -``` - -## Advanced Features - -### Watch Mode (Auto-update index) -```bash -npx ts-node indexer/src/cli/index.ts watch -# Index updates automatically when files change -``` - -### API Server -```bash -npx ts-node indexer/src/cli/index.ts api -# Access at http://localhost:4000 -# Query via REST API -``` - -### Custom Configuration -Create `.indexer.yml` in your project root: -```yaml -version: 2 -name: my-project -include: - - "**/*.ts" - - "**/*.go" - - "**/*.py" -ignore: - - "**/node_modules/**" - - "**/vendor/**" -``` - -## Next Steps - -1. **Share with your team**: "We now have a searchable index of our entire codebase" -2. **Integrate with CI/CD**: Auto-generate index on commits -3. **Use with AI tools**: Paste PROJECT_INDEX.json into Claude for instant codebase understanding -4. **Set up monitoring**: Track codebase growth and complexity over time - -## Need Help? - -- Run: `npx ts-node indexer/src/cli/index.js --help` -- Check: `indexer/CLAUDE.md` for AI assistant instructions -- Issues: Create an issue in the repository - ---- - +# Getting Started with Clone Global Indexer + +## What is this tool? + +The indexer analyzes your entire codebase and creates a single JSON file that AI assistants (like Claude) can read to instantly understand your project structure, without needing to read thousands of individual files. + +## Quick Start (30 seconds) + +```bash +# From your project root (e.g., /clone-global) +npx ts-node indexer/src/cli/index.ts scan . + +# That's it! Your index is ready. +``` + +## What just happened? + +When you ran the scan, the indexer: + +1. **Analyzed 1,916 files** across all your repositories (backend, frontend, skills, etc.) +2. **Extracted key information**: functions, classes, imports, dependencies +3. **Created an index file** at: `.indexer-output/indexes/PROJECT_INDEX.json` (7MB) +4. **Saved you money**: Claude can now read 1 file instead of 1,916 files (95% token savings) + +## Where is my index? + +Your PROJECT_INDEX.json is located at: +``` +/clone-global/.indexer-output/indexes/PROJECT_INDEX.json +``` + +This file contains: +- Every function and class in your codebase +- All imports and dependencies +- File relationships and connections +- Language statistics + +## How do I verify it's working? + +### 1. Check the Statistics +```bash +npx ts-node indexer/src/cli/index.ts stats +``` + +You should see: +- Total Files: 1,643 +- Functions, Classes, Constants counts +- Language breakdown (Go, TypeScript, Python, etc.) + +### 2. Search for a Known Function +```bash +# Search for any function you know exists +npx ts-node indexer/src/cli/index.ts query "handleLogin" +``` + +### 3. Verify the Index File +```bash +# Check file size (should be several MB) +ls -lh .indexer-output/indexes/PROJECT_INDEX.json + +# Preview the structure +head -50 .indexer-output/indexes/PROJECT_INDEX.json +``` + +### 4. Test with Claude +```markdown +Copy this into Claude: + +"I have indexed my codebase. Here's the PROJECT_INDEX.json file: +[paste contents or upload file] + +Now tell me: +1. What languages is my project using? +2. What are the main services/components? +3. Find all authentication-related functions" +``` + +## What can I do with this? + +### For AI Assistance +Instead of copying multiple files to Claude, just share the PROJECT_INDEX.json: +- "Here's my codebase index. Help me find all API endpoints" +- "Using this index, identify potential security issues" +- "Based on this index, suggest refactoring opportunities" + +### For Development +```bash +# Find unused functions +npx ts-node indexer/src/cli/index.ts query --unused + +# Search across all repos +npx ts-node indexer/src/cli/index.ts query "payment" + +# Check complexity +npx ts-node indexer/src/cli/index.ts query --complex +``` + +### For Documentation +```bash +# Generate markdown documentation +npx ts-node indexer/src/cli/index.ts export markdown + +# Create dependency graph +npx ts-node indexer/src/cli/index.ts export graphviz +``` + +## Common Use Cases + +### 1. New Developer Onboarding +Share PROJECT_INDEX.json with new team members or AI assistants to instantly understand the codebase structure. + +### 2. Code Review +```bash +# Re-index after changes +npx ts-node indexer/src/cli/index.ts scan . + +# Compare with previous index to see what changed +``` + +### 3. Finding Code +```bash +# Find all React components +npx ts-node indexer/src/cli/index.ts query "Component" + +# Find all database queries +npx ts-node indexer/src/cli/index.ts query "SELECT" +``` + +### 4. AI-Powered Analysis +```bash +# Generate the index +npx ts-node indexer/src/cli/index.ts scan . + +# Use with Claude for: +- Bug prediction +- Security analysis +- Architecture review +- Test generation +``` + +## Troubleshooting + +### Index not created? +Check the output directory: +```bash +ls -la .indexer-output/indexes/ +``` + +### Scan seems incomplete? +Check ignored files: +```bash +cat .indexerignore +``` + +### Need more detail? +Use verbose mode: +```bash +npx ts-node indexer/src/cli/index.ts scan . --verbose +``` + +## Advanced Features + +### Watch Mode (Auto-update index) +```bash +npx ts-node indexer/src/cli/index.ts watch +# Index updates automatically when files change +``` + +### API Server +```bash +npx ts-node indexer/src/cli/index.ts api +# Access at http://localhost:4000 +# Query via REST API +``` + +### Custom Configuration +Create `.indexer.yml` in your project root: +```yaml +version: 2 +name: my-project +include: + - "**/*.ts" + - "**/*.go" + - "**/*.py" +ignore: + - "**/node_modules/**" + - "**/vendor/**" +``` + +## Next Steps + +1. **Share with your team**: "We now have a searchable index of our entire codebase" +2. **Integrate with CI/CD**: Auto-generate index on commits +3. **Use with AI tools**: Paste PROJECT_INDEX.json into Claude for instant codebase understanding +4. **Set up monitoring**: Track codebase growth and complexity over time + +## Need Help? + +- Run: `npx ts-node indexer/src/cli/index.js --help` +- Check: `indexer/CLAUDE.md` for AI assistant instructions +- Issues: Create an issue in the repository + +--- + Remember: The index is just a JSON file - you can open it, read it, search it, or share it with any tool that understands JSON! \ No newline at end of file diff --git a/docs/PROJECT-DOCS.md b/docs/PROJECT-DOCS.md index c33c3ae..4b25f84 100644 --- a/docs/PROJECT-DOCS.md +++ b/docs/PROJECT-DOCS.md @@ -1,464 +1,464 @@ -# PROJECT-DOCS.md - Complete Indexer Documentation - -## Executive Summary - -The @cloneglobal/indexer is a production-grade TypeScript CLI tool and API service that provides instant, comprehensive codebase understanding for AI assistants and developers. It parses entire codebases using language-specific AST parsers and outputs a single PROJECT_INDEX.json file that AI assistants can read in seconds instead of processing thousands of files. - -Version: 2.0.1 -Performance: 1,408 files indexed in 13.2s (106 files/second) -Memory: 85MB average usage -Model: Claude Opus 4.1 (claude-opus-4-1-20250805) - -## Core Value Proposition - -### The Problem -AI assistants need to read hundreds or thousands of files to understand a codebase, consuming massive context windows and API tokens. - -### The Solution -The indexer pre-processes the entire codebase into a single structured JSON file containing: -- All functions, classes, imports, exports -- Dependency graphs and relationships -- Complexity metrics and statistics -- Cross-repository connections - -### Result -Claude can understand 1,408 files from one JSON file instead of reading each file individually, reducing token usage by 95%. - -## Technology Stack - -### Core Technologies -- TypeScript with ES2022 target, strict mode -- Node.js 16+ with ESM/CommonJS hybrid -- Commander.js for CLI framework -- Express 5.1 for API server -- Claude Opus 4.1 exclusively for AI analysis - -### Language Parsing (9 Languages) -- JavaScript/TypeScript: @babel/parser with full ES2024/TS5+ support -- Python: tree-sitter + tree-sitter-python for 100% accurate AST -- Go: Custom parser for structs, interfaces, functions -- SQL: Table definitions, queries, procedures -- GraphQL: Schema definitions with React hook detection -- YAML: Key-value pairs and references -- Astro: Frontmatter, component imports, client scripts -- JSX/TSX: Full React component support - -### AI Integration -- @anthropic-ai/claude-code SDK v1.0.89 -- Claude Opus 4.1 model exclusively -- 16+ MCP tool integrations -- Specialized agents: Security, Performance, Architecture, Testing -- Streaming analysis with SSE/WebSocket - -### External Integrations -- Slack: Bug monitoring with NLP severity detection -- Linear: Automatic ticket creation -- Datadog: Metrics and APM -- GitHub: Security alerts and PR analysis - -## Architecture - -### Core System Components - -**SmartIndexer** (src/core/smart-indexer.ts) -- Orchestrates all features automatically -- Detects project structure and frameworks -- Generates all export formats -- Sets up IDE integrations - -**Indexer** (src/core/indexer.ts) -- Main indexing engine -- Coordinates language parsers -- Builds dependency graphs -- Generates statistics - -**CacheManager** (src/core/cache-manager.ts) -- LRU cache with 500MB limit -- TTL-based expiration (1 hour default) -- Prevents memory leaks -- 70% memory reduction vs unbounded Map - -**MultiRepoKnowledgeGraph** (src/core/multi-repo-knowledge-graph.ts) -- Cross-repository dependency analysis -- API connection mapping -- GraphQL operation tracking -- Event-based connection tracing - -**FileWatcher** (src/core/watcher.ts) -- Real-time file monitoring with Chokidar -- 300ms debounced updates -- Incremental index updates - -### API Architecture - -**REST API** (Port 4000) -- POST /api/index - Trigger indexing -- POST /api/ai/analyze - Comprehensive AI analysis -- POST /api/ai/predict-bugs - Bug prediction -- POST /api/ai/analyze-security - Security scanning -- GET /api/health - Health check -- GET /api/stats - Statistics - -**GraphQL** (/graphql) -- Full schema with queries, mutations, subscriptions -- Apollo Server integration - -**WebSocket** (ws://localhost:4000/ws) -- Real-time file updates -- Live indexing progress - -**Server-Sent Events** (/api/stream/*) -- Streaming analysis progress -- Live security updates - -### AI Analysis Architecture - -**Claude SDK Analyzer** (src/ai/sdk-analyzer.ts) -- Uses Claude Opus 4.1 exclusively -- Streaming analysis with async generators -- Session management for conversations -- Abort controllers for cancellation - -**Specialized Agents** -- SecurityAgent: OWASP compliance, vulnerability detection -- PerformanceAgent: Optimization opportunities, memory leaks -- ArchitectureAgent: Pattern detection, SOLID principles -- TestingAgent: Coverage analysis, test generation - -**Hybrid Analyzer** (src/ai/hybrid-analyzer.ts) -- Routes to language-specific analyzers -- Python SDK for Python code -- TypeScript SDK for JS/TS code - -## Deployment - -### Quick Start -```bash -# Install globally -npm install -g @cloneglobal/indexer - -# Or clone and build -git clone https://github.com/cloneglobal/indexer.git -cd indexer -npm install -npm run build -npm link - -# Set up AI features -export ANTHROPIC_API_KEY="sk-ant-..." - -# Run indexer -indexer # SmartIndexer does everything automatically -``` - -### Docker Deployment -```dockerfile -FROM node:20-alpine -WORKDIR /app -COPY package*.json ./ -RUN npm ci --production -COPY dist ./dist -EXPOSE 4000 -HEALTHCHECK CMD curl -f http://localhost:4000/health || exit 1 -CMD ["node", "dist/api/server.js"] -``` - -### Docker Compose -```yaml -version: '3.8' -services: - indexer: - image: cloneglobal/indexer:latest - ports: - - "4000:4000" - environment: - - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - - CLAUDE_MODEL=claude-opus-4-1-20250805 - volumes: - - ./repositories:/workspace - - ./indexes:/indexes -``` - -### Environment Variables -```bash -# Required for AI features -ANTHROPIC_API_KEY=sk-ant-... -CLAUDE_MODEL=claude-opus-4-1-20250805 - -# Optional integrations -SLACK_BOT_TOKEN=xoxb-... -LINEAR_API_KEY=lin_api_... -DD_AGENT_HOST=localhost -DD_DOGSTATSD_PORT=8125 -GITHUB_TOKEN=ghp_... -``` - -## Configuration - -### .indexer.yml -```yaml -version: 2 -name: project-name - -include: - - "**/*.{js,jsx,ts,tsx,py,go,sql,graphql,yaml,astro}" -ignore: - - "**/node_modules/**" - - "**/dist/**" - -performance: - parallel: true - workers: 4 - cache: true - maxFileSize: 2MB - -ai: - model: claude-opus-4-1-20250805 - maxConcurrentAgents: 4 - sessionTimeout: 1800000 - -export: - outputDirectory: .indexer-output - formats: - json: - path: indexes/PROJECT_INDEX.json -``` - -## Current Implementation Status - -### Completed Features (v2.0.1) -- SmartIndexer with automatic orchestration -- Multi-language parsing (9 languages) -- Tree-sitter Python parser (100% accurate AST) -- LRU Cache Manager with 500MB limit -- Slack bot with Linear integration -- Datadog metrics and monitoring -- Claude SDK with specialized agents -- Multi-repo knowledge graph -- REST/GraphQL/WebSocket API server -- MCP tool integrations (16+ tools) -- Hybrid TypeScript/Python SDK implementation -- Skills repository analysis - -### Critical Issues - -**Test Coverage Crisis** -- Current: 8% (only 4 test files) -- Target: 80% minimum -- Impact: Production reliability risk - -**Memory Management** -- Issue: CacheManager not consistently used -- Impact: Memory leaks on large codebases - -**Type Safety** -- Problem: Excessive any types -- Impact: Runtime failures - -### Performance Metrics - -Current Performance: -- 100 files: ~2s, 50MB memory -- 1,000 files: ~15s, 100MB memory -- 1,408 files: ~13.2s, 85MB memory -- 10,000+ files: ~90s, 300MB memory -- Query response: <50ms -- File change update: <100ms - -AI Analysis Performance: -- Single file: 2-5s -- 100 files: ~30s with concurrent agents -- Full codebase: 2-5 min -- Session continuation: 70% faster -- Streaming updates: Real-time - -## Usage Examples - -### Basic CLI Usage -```bash -# Automatic smart mode (recommended) -indexer - -# Manual commands -indexer scan # Scan current directory -indexer watch # Watch for changes -indexer query "handleLogin" # Search for function -indexer export markdown # Export as markdown -indexer health # Check health -indexer stats # View statistics -``` - -### API Usage -```bash -# Trigger indexing -curl -X POST http://localhost:4000/api/index \ - -H "Content-Type: application/json" \ - -d '{"path": "/workspace"}' - -# AI analysis with Claude Opus 4.1 -curl -X POST http://localhost:4000/api/ai/analyze \ - -H "Content-Type: application/json" - -# Bug prediction -curl -X POST http://localhost:4000/api/ai/predict-bugs \ - -H "Content-Type: application/json" \ - -d '{"files": ["src/auth.ts"]}' - -# Security scanning -curl -X POST http://localhost:4000/api/ai/analyze-security \ - -H "Content-Type: application/json" -``` - -### Streaming Analysis -```javascript -const eventSource = new EventSource('http://localhost:4000/api/stream/analyze'); - -eventSource.addEventListener('progress', (event) => { - const data = JSON.parse(event.data); - console.log(`Analysis ${data.progress}% complete`); -}); - -eventSource.addEventListener('complete', (event) => { - const summary = JSON.parse(event.data).summary; - console.log('Analysis complete:', summary); - eventSource.close(); -}); -``` - -## Development Guidelines - -### Adding a New Language Parser -1. Create parser class in src/parsers/[language].ts implementing Parser interface -2. Register in src/parsers/index.ts -3. Add tests in test/parsers/[language].test.ts -4. Update documentation - -### Parser Interface -```typescript -interface Parser { - language: string; - extensions: string[]; - parse(content: string, filePath: string): ParserResult; -} -``` - -### Testing Requirements -- Coverage: 80% minimum (lines, functions), 70% branches -- Test files: *.test.ts or *.spec.ts -- Framework: Jest with ts-jest -- E2E: Cypress for CLI validation - -### Code Standards -- TypeScript strict mode -- No implicit any -- Strict null checks -- ESLint compliance -- Prettier formatting - -## Troubleshooting - -### Common Issues - -**Out of Memory** -```bash -NODE_OPTIONS="--max-old-space-size=4096" indexer scan -# Or reduce workers -indexer scan --workers 2 -``` - -**Slow Indexing** -```bash -# Enable parallel processing -indexer scan --parallel --workers 8 -# Clear cache if corrupted -rm -rf .indexer-cache -``` - -**API Won't Start** -```bash -# Check port availability -lsof -i :4000 -# Use different port -API_PORT=5000 npm run api -``` - -**AI Features Not Working** -```bash -# Verify API key -echo $ANTHROPIC_API_KEY -# Check Claude SDK -npm list @anthropic-ai/claude-code -# Enable debug logging -DEBUG=indexer:ai:* npm run api -``` - -## Performance Optimization - -### Recommended Settings -```yaml -performance: - workers: 4-16 # Based on CPU cores - maxFileSize: 2-10MB # Skip very large files - cache: - maxSize: 500MB # Increase for large codebases - ttl: 3600000 # 1 hour cache TTL - -ai: - maxConcurrentAgents: 4 # Parallel agent execution - sessionTimeout: 1800000 # 30 min session timeout - streamingBufferSize: 1MB # SSE buffer size -``` - -### Memory Management -- LRU cache prevents unbounded growth -- Automatic eviction at 500MB -- TTL expiration after 1 hour -- Size tracking per entry - -## Future Roadmap - -### Immediate Priorities -1. Increase test coverage to 80% -2. Fix memory leak issues -3. Implement incremental parsing -4. Add progress indicators -5. Enable strict TypeScript - -### Planned Features -- Distributed processing with Redis/Bull -- WebAssembly parsers for 10x performance -- Enhanced security scanning -- Real-time collaboration features -- Code time machine (track evolution) - -### Not Needed (Clone Global Stack Covered) -- Java parser (no Java in stack) -- Rust parser (no Rust in stack) -- C# parser (no C# in stack) -- Local LLM support (using Claude Opus 4.1 exclusively) - -## Support and Contributing - -### Getting Help -- GitHub Issues: https://github.com/cloneglobal/indexer/issues -- Documentation: This file and CLAUDE.md -- Email: jrosing@clone.me - -### Contributing -- Fork repository -- Add tests for new features -- Follow TypeScript strict mode -- Submit pull request - -### Reporting Issues -```bash -# Collect diagnostic info -indexer health --verbose > diagnostic.log -# Include diagnostic.log in issue report -``` - -## License and Credits - -Created by Clone Global Engineering Team -Version 2.0.1 +# PROJECT-DOCS.md - Complete Indexer Documentation + +## Executive Summary + +The @cloneglobal/indexer is a production-grade TypeScript CLI tool and API service that provides instant, comprehensive codebase understanding for AI assistants and developers. It parses entire codebases using language-specific AST parsers and outputs a single PROJECT_INDEX.json file that AI assistants can read in seconds instead of processing thousands of files. + +Version: 2.0.1 +Performance: 1,408 files indexed in 13.2s (106 files/second) +Memory: 85MB average usage +Model: Claude Opus 4.1 (claude-opus-4-1-20250805) + +## Core Value Proposition + +### The Problem +AI assistants need to read hundreds or thousands of files to understand a codebase, consuming massive context windows and API tokens. + +### The Solution +The indexer pre-processes the entire codebase into a single structured JSON file containing: +- All functions, classes, imports, exports +- Dependency graphs and relationships +- Complexity metrics and statistics +- Cross-repository connections + +### Result +Claude can understand 1,408 files from one JSON file instead of reading each file individually, reducing token usage by 95%. + +## Technology Stack + +### Core Technologies +- TypeScript with ES2022 target, strict mode +- Node.js 16+ with ESM/CommonJS hybrid +- Commander.js for CLI framework +- Express 5.1 for API server +- Claude Opus 4.1 exclusively for AI analysis + +### Language Parsing (9 Languages) +- JavaScript/TypeScript: @babel/parser with full ES2024/TS5+ support +- Python: tree-sitter + tree-sitter-python for 100% accurate AST +- Go: Custom parser for structs, interfaces, functions +- SQL: Table definitions, queries, procedures +- GraphQL: Schema definitions with React hook detection +- YAML: Key-value pairs and references +- Astro: Frontmatter, component imports, client scripts +- JSX/TSX: Full React component support + +### AI Integration +- @anthropic-ai/claude-code SDK v1.0.89 +- Claude Opus 4.1 model exclusively +- 16+ MCP tool integrations +- Specialized agents: Security, Performance, Architecture, Testing +- Streaming analysis with SSE/WebSocket + +### External Integrations +- Slack: Bug monitoring with NLP severity detection +- Linear: Automatic ticket creation +- Datadog: Metrics and APM +- GitHub: Security alerts and PR analysis + +## Architecture + +### Core System Components + +**SmartIndexer** (src/core/smart-indexer.ts) +- Orchestrates all features automatically +- Detects project structure and frameworks +- Generates all export formats +- Sets up IDE integrations + +**Indexer** (src/core/indexer.ts) +- Main indexing engine +- Coordinates language parsers +- Builds dependency graphs +- Generates statistics + +**CacheManager** (src/core/cache-manager.ts) +- LRU cache with 500MB limit +- TTL-based expiration (1 hour default) +- Prevents memory leaks +- 70% memory reduction vs unbounded Map + +**MultiRepoKnowledgeGraph** (src/core/multi-repo-knowledge-graph.ts) +- Cross-repository dependency analysis +- API connection mapping +- GraphQL operation tracking +- Event-based connection tracing + +**FileWatcher** (src/core/watcher.ts) +- Real-time file monitoring with Chokidar +- 300ms debounced updates +- Incremental index updates + +### API Architecture + +**REST API** (Port 4000) +- POST /api/index - Trigger indexing +- POST /api/ai/analyze - Comprehensive AI analysis +- POST /api/ai/predict-bugs - Bug prediction +- POST /api/ai/analyze-security - Security scanning +- GET /api/health - Health check +- GET /api/stats - Statistics + +**GraphQL** (/graphql) +- Full schema with queries, mutations, subscriptions +- Apollo Server integration + +**WebSocket** (ws://localhost:4000/ws) +- Real-time file updates +- Live indexing progress + +**Server-Sent Events** (/api/stream/*) +- Streaming analysis progress +- Live security updates + +### AI Analysis Architecture + +**Claude SDK Analyzer** (src/ai/sdk-analyzer.ts) +- Uses Claude Opus 4.1 exclusively +- Streaming analysis with async generators +- Session management for conversations +- Abort controllers for cancellation + +**Specialized Agents** +- SecurityAgent: OWASP compliance, vulnerability detection +- PerformanceAgent: Optimization opportunities, memory leaks +- ArchitectureAgent: Pattern detection, SOLID principles +- TestingAgent: Coverage analysis, test generation + +**Hybrid Analyzer** (src/ai/hybrid-analyzer.ts) +- Routes to language-specific analyzers +- Python SDK for Python code +- TypeScript SDK for JS/TS code + +## Deployment + +### Quick Start +```bash +# Install globally +npm install -g @cloneglobal/indexer + +# Or clone and build +git clone https://github.com/cloneglobal/indexer.git +cd indexer +npm install +npm run build +npm link + +# Set up AI features +export ANTHROPIC_API_KEY="sk-ant-..." + +# Run indexer +indexer # SmartIndexer does everything automatically +``` + +### Docker Deployment +```dockerfile +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci --production +COPY dist ./dist +EXPOSE 4000 +HEALTHCHECK CMD curl -f http://localhost:4000/health || exit 1 +CMD ["node", "dist/api/server.js"] +``` + +### Docker Compose +```yaml +version: '3.8' +services: + indexer: + image: cloneglobal/indexer:latest + ports: + - "4000:4000" + environment: + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} + - CLAUDE_MODEL=claude-opus-4-1-20250805 + volumes: + - ./repositories:/workspace + - ./indexes:/indexes +``` + +### Environment Variables +```bash +# Required for AI features +ANTHROPIC_API_KEY=sk-ant-... +CLAUDE_MODEL=claude-opus-4-1-20250805 + +# Optional integrations +SLACK_BOT_TOKEN=xoxb-... +LINEAR_API_KEY=lin_api_... +DD_AGENT_HOST=localhost +DD_DOGSTATSD_PORT=8125 +GITHUB_TOKEN=ghp_... +``` + +## Configuration + +### .indexer.yml +```yaml +version: 2 +name: project-name + +include: + - "**/*.{js,jsx,ts,tsx,py,go,sql,graphql,yaml,astro}" +ignore: + - "**/node_modules/**" + - "**/dist/**" + +performance: + parallel: true + workers: 4 + cache: true + maxFileSize: 2MB + +ai: + model: claude-opus-4-1-20250805 + maxConcurrentAgents: 4 + sessionTimeout: 1800000 + +export: + outputDirectory: .indexer-output + formats: + json: + path: indexes/PROJECT_INDEX.json +``` + +## Current Implementation Status + +### Completed Features (v2.0.1) +- SmartIndexer with automatic orchestration +- Multi-language parsing (9 languages) +- Tree-sitter Python parser (100% accurate AST) +- LRU Cache Manager with 500MB limit +- Slack bot with Linear integration +- Datadog metrics and monitoring +- Claude SDK with specialized agents +- Multi-repo knowledge graph +- REST/GraphQL/WebSocket API server +- MCP tool integrations (16+ tools) +- Hybrid TypeScript/Python SDK implementation +- Skills repository analysis + +### Critical Issues + +**Test Coverage Crisis** +- Current: 8% (only 4 test files) +- Target: 80% minimum +- Impact: Production reliability risk + +**Memory Management** +- Issue: CacheManager not consistently used +- Impact: Memory leaks on large codebases + +**Type Safety** +- Problem: Excessive any types +- Impact: Runtime failures + +### Performance Metrics + +Current Performance: +- 100 files: ~2s, 50MB memory +- 1,000 files: ~15s, 100MB memory +- 1,408 files: ~13.2s, 85MB memory +- 10,000+ files: ~90s, 300MB memory +- Query response: <50ms +- File change update: <100ms + +AI Analysis Performance: +- Single file: 2-5s +- 100 files: ~30s with concurrent agents +- Full codebase: 2-5 min +- Session continuation: 70% faster +- Streaming updates: Real-time + +## Usage Examples + +### Basic CLI Usage +```bash +# Automatic smart mode (recommended) +indexer + +# Manual commands +indexer scan # Scan current directory +indexer watch # Watch for changes +indexer query "handleLogin" # Search for function +indexer export markdown # Export as markdown +indexer health # Check health +indexer stats # View statistics +``` + +### API Usage +```bash +# Trigger indexing +curl -X POST http://localhost:4000/api/index \ + -H "Content-Type: application/json" \ + -d '{"path": "/workspace"}' + +# AI analysis with Claude Opus 4.1 +curl -X POST http://localhost:4000/api/ai/analyze \ + -H "Content-Type: application/json" + +# Bug prediction +curl -X POST http://localhost:4000/api/ai/predict-bugs \ + -H "Content-Type: application/json" \ + -d '{"files": ["src/auth.ts"]}' + +# Security scanning +curl -X POST http://localhost:4000/api/ai/analyze-security \ + -H "Content-Type: application/json" +``` + +### Streaming Analysis +```javascript +const eventSource = new EventSource('http://localhost:4000/api/stream/analyze'); + +eventSource.addEventListener('progress', (event) => { + const data = JSON.parse(event.data); + console.log(`Analysis ${data.progress}% complete`); +}); + +eventSource.addEventListener('complete', (event) => { + const summary = JSON.parse(event.data).summary; + console.log('Analysis complete:', summary); + eventSource.close(); +}); +``` + +## Development Guidelines + +### Adding a New Language Parser +1. Create parser class in src/parsers/[language].ts implementing Parser interface +2. Register in src/parsers/index.ts +3. Add tests in test/parsers/[language].test.ts +4. Update documentation + +### Parser Interface +```typescript +interface Parser { + language: string; + extensions: string[]; + parse(content: string, filePath: string): ParserResult; +} +``` + +### Testing Requirements +- Coverage: 80% minimum (lines, functions), 70% branches +- Test files: *.test.ts or *.spec.ts +- Framework: Jest with ts-jest +- E2E: Cypress for CLI validation + +### Code Standards +- TypeScript strict mode +- No implicit any +- Strict null checks +- ESLint compliance +- Prettier formatting + +## Troubleshooting + +### Common Issues + +**Out of Memory** +```bash +NODE_OPTIONS="--max-old-space-size=4096" indexer scan +# Or reduce workers +indexer scan --workers 2 +``` + +**Slow Indexing** +```bash +# Enable parallel processing +indexer scan --parallel --workers 8 +# Clear cache if corrupted +rm -rf .indexer-cache +``` + +**API Won't Start** +```bash +# Check port availability +lsof -i :4000 +# Use different port +API_PORT=5000 npm run api +``` + +**AI Features Not Working** +```bash +# Verify API key +echo $ANTHROPIC_API_KEY +# Check Claude SDK +npm list @anthropic-ai/claude-code +# Enable debug logging +DEBUG=indexer:ai:* npm run api +``` + +## Performance Optimization + +### Recommended Settings +```yaml +performance: + workers: 4-16 # Based on CPU cores + maxFileSize: 2-10MB # Skip very large files + cache: + maxSize: 500MB # Increase for large codebases + ttl: 3600000 # 1 hour cache TTL + +ai: + maxConcurrentAgents: 4 # Parallel agent execution + sessionTimeout: 1800000 # 30 min session timeout + streamingBufferSize: 1MB # SSE buffer size +``` + +### Memory Management +- LRU cache prevents unbounded growth +- Automatic eviction at 500MB +- TTL expiration after 1 hour +- Size tracking per entry + +## Future Roadmap + +### Immediate Priorities +1. Increase test coverage to 80% +2. Fix memory leak issues +3. Implement incremental parsing +4. Add progress indicators +5. Enable strict TypeScript + +### Planned Features +- Distributed processing with Redis/Bull +- WebAssembly parsers for 10x performance +- Enhanced security scanning +- Real-time collaboration features +- Code time machine (track evolution) + +### Not Needed (Clone Global Stack Covered) +- Java parser (no Java in stack) +- Rust parser (no Rust in stack) +- C# parser (no C# in stack) +- Local LLM support (using Claude Opus 4.1 exclusively) + +## Support and Contributing + +### Getting Help +- GitHub Issues: https://github.com/cloneglobal/indexer/issues +- Documentation: This file and CLAUDE.md +- Email: jrosing@clone.me + +### Contributing +- Fork repository +- Add tests for new features +- Follow TypeScript strict mode +- Submit pull request + +### Reporting Issues +```bash +# Collect diagnostic info +indexer health --verbose > diagnostic.log +# Include diagnostic.log in issue report +``` + +## License and Credits + +Created by Clone Global Engineering Team +Version 2.0.1 Using Claude Opus 4.1 for AI analysis \ No newline at end of file diff --git a/docs/indexer-docs.md b/docs/indexer-docs.md index 8e677ea..f40ba10 100644 --- a/docs/indexer-docs.md +++ b/docs/indexer-docs.md @@ -1,897 +1,897 @@ -# Consolidated Indexer Documentation - -## Project Overview - -**Project:** indexer -**Version:** 2.0.1 -**Type:** Universal code indexer for AI assistants and development tools -**Stack:** TypeScript, Node.js (>=16.0.0), Commander.js CLI -**Architecture:** Modular/Plugin-based with Parser Interface, Factory Pattern, Event Emitter, and Streaming patterns - -## Core Description - -The indexer is a hook that creates and maintains a minified UML-style abstraction of codebases in `PROJECT_INDEX.json`, updating automatically on file changes while sitting outside Claude's context window. It provides high signal-to-noise ratio for AI models without overwhelming their context. - -### Key Capabilities -- Lightning fast: Index 1000+ files in seconds (40+ files/second) -- Incremental updates: Auto-refresh on file changes -- Multi-language: JS/TS, Python, Go, SQL, GraphQL, YAML, Astro, and more (9 languages) -- Extensible: Plugin system for custom parsers -- AI-ready: Optimized for Claude, Copilot, and other AI tools -- Rich exports: JSON, GraphViz, Markdown, Mermaid, ASCII formats -- Zero context window overhead via external hooks - -## Quick Start Guide - -### Installation - -```bash -# Global installation -npm install -g indexer - -# Or with yarn -yarn global add indexer - -# Or add to existing project -npm install -D indexer -``` - -### Basic Usage - -```bash -# Initialize in your project -indexer init - -# Scan and create index -indexer scan - -# Watch for changes -indexer watch - -# Query the index -indexer query "function.*Controller" - -# View statistics -indexer stats - -# Export to different formats -indexer export markdown -``` - -### Environment Configuration - -```bash -# Required for AI features -export ANTHROPIC_API_KEY="your-api-key" - -# Optional integrations -export SLACK_BOT_TOKEN="xoxb-..." -export LINEAR_API_KEY="lin_..." -export DD_API_KEY="..." -export GITHUB_TOKEN="ghp_..." -``` - -## Claude Code Interactive Chat - -### Built-in Chat Interface - -```bash -# Start interactive chat -indexer chat - -# You'll see: -╔════════════════════════════════════════╗ -║ Claude Code Chat - Interactive ║ -╚════════════════════════════════════════╝ - -# Commands in chat: -/refresh - Rebuild project index -/clear - Clear conversation history -/save - Save conversation to markdown -/stats - Show project statistics -/search - Search for code -/explain - Explain a specific file -/help - Show all commands -exit - Quit chat -``` - -### One-Shot Queries - -```bash -# Ask questions without entering chat mode -indexer ask "What does the handlePayment function do?" -indexer ask "Find all SQL queries in this project" -indexer ask "Are there security issues in auth.service.ts?" - -# Query about specific files -indexer claude --file src/auth/service.ts --query "Explain this file" -indexer claude --function handleLogin --query "How can I optimize this?" -``` - -### AI Analysis Commands - -```bash -# Run specific AI analyses -indexer ai bugs # Predict bugs -indexer ai security # Security analysis -indexer ai refactor # Get refactoring suggestions -indexer ai tests # Generate test suggestions - -# With streaming output -indexer ai security --stream -``` - -## Architecture - -### Directory Structure - -``` -indexer/ -├── src/ -│ ├── cli/ # Command-line interface -│ │ └── index.ts # CLI with commander -│ ├── core/ -│ │ ├── indexer.ts # Main indexing engine -│ │ ├── cache-manager.ts # LRU cache (500MB limit) -│ │ ├── config.ts # Configuration loader -│ │ ├── monorepo.ts # Monorepo analyzer -│ │ └── watcher.ts # File system monitoring (Chokidar) -│ ├── parsers/ # Language-specific parsers -│ │ ├── javascript.ts # JS parser (@babel/parser) -│ │ ├── typescript.ts # TS parser -│ │ ├── python-tree-sitter.ts # Python AST parser (100% accurate) -│ │ ├── go.ts # Go parser -│ │ ├── sql.ts # SQL parser -│ │ ├── graphql.ts # GraphQL parser (enhanced) -│ │ ├── yaml.ts # YAML parser -│ │ └── astro.ts # Astro parser -│ ├── exporters/ # Export formats -│ │ ├── json.ts -│ │ ├── markdown.ts -│ │ ├── graphviz.ts -│ │ ├── mermaid.ts -│ │ └── ascii.ts -│ ├── ai/ # AI integration -│ │ ├── claude-analyzer.ts -│ │ ├── sdk-analyzer.ts # Claude SDK integration -│ │ ├── hybrid-analyzer.ts # Language-specific routing -│ │ ├── python/ # Python-specific analyzer -│ │ └── agents/ # Specialized AI agents -│ ├── api/ # API server -│ │ ├── rest/ # REST endpoints -│ │ ├── graphql/ # GraphQL schema -│ │ ├── websocket/ # WebSocket handler -│ │ └── middleware/ # Auth, rate limiting -│ ├── integrations/ # External services -│ │ ├── slack/ # Bug monitoring -│ │ ├── linear/ # Ticket creation -│ │ └── datadog/ # Metrics -│ └── types/ # TypeScript definitions -├── bin/ -│ └── indexer.js # CLI entry point -├── .indexer-output/ # Generated outputs -│ ├── indexes/ # JSON indexes -│ ├── docs/ # Markdown documentation -│ └── visualizations/ # Graphs & diagrams -└── package.json -``` - -### Core Components - -#### SmartIndexer -Automatic orchestration of all features with intelligent feature detection. - -#### Language Parsers (9 total) -- **JavaScript/TypeScript**: AST-based using @babel/parser -- **Python**: Tree-sitter AST (replaced regex, 100% accurate) -- **Go, SQL, GraphQL, YAML, Astro**: Custom parsers -- All implement common Parser interface - -#### Cache Manager -LRU cache with 500MB limit, TTL expiration, prevents memory leaks. - -#### Export System -Multiple formats: JSON, Markdown, GraphViz, Mermaid, ASCII with interactive HTML viewers. - -#### AI Integration -- Claude SDK integration with streaming -- MCP (Model Context Protocol) support -- Specialized agents: Security, Performance, Architecture, Testing -- Hybrid analyzer for language-specific analysis - -## Claude Code SDK Integration - -### Using the SDK - -The indexer uses the Claude Code SDK for advanced AI capabilities: - -```typescript -import { query } from "@anthropic-ai/claude-code"; - -// Basic usage -for await (const message of query({ - prompt: "Analyze system performance", - options: { - maxTurns: 5, - systemPrompt: "You are a performance engineer", - allowedTools: ["Bash", "Read", "WebSearch"] - } -})) { - if (message.type === "result") { - console.log(message.result); - } -} -``` - -### Python SDK Integration - -```python -import asyncio -from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions - -async def main(): - async with ClaudeSDKClient( - options=ClaudeCodeOptions( - system_prompt="You are a code analyzer", - max_turns=5, - allowed_tools=["Read", "Grep", "WebSearch"] - ) - ) as client: - await client.query("Analyze this code") - async for msg in client.receive_response(): - # Process streaming messages - pass - -asyncio.run(main()) -``` - -### MCP (Model Context Protocol) Integration - -Configure custom tools via MCP servers: - -```json -{ - "mcpServers": { - "semgrep": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-semgrep"], - "env": {} - }, - "github": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-github"], - "env": {"GITHUB_TOKEN": "${GITHUB_TOKEN}"} - } - } -} -``` - -## API Server - -### Starting the API Server - -```bash -# Start API server with all features -indexer api --port 4000 - -# Endpoints available at: -# REST: http://localhost:4000/api -# GraphQL: http://localhost:4000/graphql -# WebSocket: ws://localhost:4000/ws -# Streaming: http://localhost:4000/api/stream/* -``` - -### REST API Endpoints - -```bash -# Trigger indexing -curl -X POST http://localhost:4000/api/index \ - -H "Content-Type: application/json" \ - -d '{"path": "/workspace", "options": {"parallel": true}}' - -# AI analysis -curl -X POST http://localhost:4000/api/ai/analyze -curl -X POST http://localhost:4000/api/ai/predict-bugs -curl -X POST http://localhost:4000/api/ai/analyze-security -curl -X POST http://localhost:4000/api/ai/generate-tests - -# Specialized agents -curl -X POST http://localhost:4000/api/agents/security -curl -X POST http://localhost:4000/api/agents/performance -curl -X POST http://localhost:4000/api/agents/architecture -curl -X POST http://localhost:4000/api/agents/testing -``` - -### GraphQL Schema - -```graphql -type Query { - getIndex: ProjectIndex! - searchFunctions(name: String!): [Function!]! - findCircularDependencies: [Dependency!]! -} - -type Mutation { - analyzeWithAI(path: String, options: AIOptions): AIAnalysis! - predictBugs(files: [String!]): [BugPrediction!]! - generateTests(function: String!, file: String!): TestSuggestion! -} - -type Subscription { - aiAnalysisProgress: AIProgress! - fileUpdated(path: String): FileUpdate! -} -``` - -### WebSocket Real-time Updates - -```javascript -const ws = new WebSocket('ws://localhost:4000/ws'); - -ws.on('open', () => { - ws.send(JSON.stringify({ - type: 'subscribe', - topic: 'file-updates' - })); -}); - -ws.on('message', (data) => { - const msg = JSON.parse(data); - console.log('File updated:', msg.data); -}); -``` - -### Server-Sent Events (SSE) Streaming - -```javascript -const eventSource = new EventSource('http://localhost:4000/api/stream/analyze'); - -eventSource.addEventListener('progress', (event) => { - const data = JSON.parse(event.data); - console.log(`Analysis ${data.progress}% complete`); -}); - -eventSource.addEventListener('complete', (event) => { - const summary = JSON.parse(event.data).summary; - console.log('Analysis complete:', summary); - eventSource.close(); -}); -``` - -## Configuration - -### Project Configuration (.indexer.yml) - -```yaml -version: 2 -name: my-project -description: Project indexing configuration - -# Parser settings -parsers: - javascript: - ecmaVersion: 2024 - jsx: true - typescript: true - python: - version: "3.11" - type_checking: true - go: - module_path: github.com/org/project - -# Indexing rules -rules: - max_file_size: 10MB - max_files: 10000 - incremental_threshold: 100 - ignore_patterns: - - "*.generated.*" - - "*.min.js" - include_hidden: false - -# Performance tuning -performance: - parallel: true - workers: auto # or specific number - cache: true - cache_dir: .indexer-cache - memory_limit: 2GB - -# Export configuration -export: - formats: - - json - - graphviz - - markdown - auto_export: true - destination: ./docs/generated - -# Integrations -integrations: - claude: - enabled: true - hooks: auto - settings_path: ~/.config/claude/settings.json - slack: - enabled: true - webhook_url: ${SLACK_WEBHOOK_URL} - linear: - enabled: true - api_key: ${LINEAR_API_KEY} - datadog: - enabled: true - agent_host: ${DD_AGENT_HOST} -``` - -### Monorepo Configuration - -```yaml -version: 2 -name: my-monorepo -type: monorepo - -services: - backend: - path: backend/ - entry: main/src/server.go - language: go - frontend: - path: frontend/ - entry: src/app/layout.tsx - language: typescript - skills: - path: skills/ - pattern: "skill_*/run.py" - language: python - -# Focus areas for deep analysis -focus: - - backend/main/src/graph/** # GraphQL resolvers - - frontend/src/app/** # App router - - frontend/src/hooks/** # React hooks - - skills/skill_*/run.py # Skill implementations - -# What to ignore -ignore: - - "**/node_modules/**" - - "**/.next/**" - - "**/dist/**" - - "**/__pycache__/**" -``` - -## Deployment - -### Docker Deployment - -```dockerfile -FROM node:20-alpine AS builder -WORKDIR /app -COPY package*.json ./ -RUN npm ci --only=production - -FROM node:20-alpine -WORKDIR /app -COPY --from=builder /app/node_modules ./node_modules -COPY . . -RUN npm run build -ENTRYPOINT ["node", "dist/cli/index.js"] -``` - -### Docker Compose - -```yaml -version: '3.8' -services: - indexer: - image: indexer:latest - ports: - - "4000:4000" - volumes: - - ./repositories:/workspace - - ./indexes:/indexes - - ./.indexer.yml:/workspace/.indexer.yml - environment: - - NODE_ENV=production - - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - - GITHUB_TOKEN=${GITHUB_TOKEN} - command: npm run api:prod - - redis: - image: redis:alpine - ports: - - "6379:6379" - - elasticsearch: - image: elasticsearch:8.11.0 - environment: - - discovery.type=single-node - - ES_JAVA_OPTS=-Xms512m -Xmx512m - ports: - - "9200:9200" -``` - -### Kubernetes Deployment - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: indexer -spec: - replicas: 10 - selector: - matchLabels: - app: indexer - template: - metadata: - labels: - app: indexer - spec: - containers: - - name: indexer - image: indexer:latest - resources: - requests: - memory: "1Gi" - cpu: "1" - limits: - memory: "2Gi" - cpu: "2" - env: - - name: REDIS_HOST - value: redis-service - - name: ELASTICSEARCH_URL - value: http://elasticsearch:9200 -``` - -## Skill Discovery Guide - -### Active Skills (14 total) - -| Skill Name | Purpose | Port | -|------------|---------|------| -| generate-image | Image generation | 9001 | -| clone-transcribe-skill | Audio/video transcription | 9002 | -| clone-pdf-to-text-v2-skill | PDF text extraction | 9003 | -| clone-csv-to-structured-text | CSV data processing | 9004 | -| clone-medical-research-skill | Medical research | 9005 | -| clone-scientific-research-skill | Scientific research | 9006 | -| clone-research-legal-skill | Legal research | 9007 | -| clone-science-tid-bit-skill | Science facts | 9008 | -| clone-check-calendar-availability-v2-skill | Calendar checking | 9009 | -| clone-create-meeting-confirm-v2-skill | Meeting creation | 9010 | -| clone-check-calendar-availability-internal-attendees-skill | Internal availability | 9011 | -| clone-create-meeting-confirm-internal-attendee-skill | Internal meetings | 9012 | - -### Skill Discovery Queries - -```bash -# List all skill run functions -indexer query "run" --type function | grep "skills/" - -# Find calendar-related skills -indexer query "calendar" --type function | grep "skills/" - -# Find research skills -indexer query "research" --type function | grep "skills/" - -# Find document processing skills -indexer query "pdf\|csv\|text" --type function | grep "skills/" - -# Find all skill GraphQL operations -indexer query "Skill" --type function | grep -E "mutation|query" - -# Check skill dependencies -indexer query "skill_runner" --type import - -# Analyze skill complexity -for skill in $(find ./skills -maxdepth 2 -name "run.py" | cut -d'/' -f3); do - count=$(indexer query "" --type function | grep "skills/$skill" | wc -l) - echo "$skill: $count functions" -done -``` - -### Required Skill Function Signature - -```python -def run(clone: Dict[str, Any], params: Dict[str, Any], - variables: Dict[str, Any], api_token: str) -> str: - """ - Parameters: - - clone: Clone context and metadata - - params: Skill-specific parameters - - variables: Environment variables - - api_token: Authentication token - - Returns: - - str: Always returns a string response - """ - return "response" -``` - -## Hybrid AI Architecture - -The indexer uses a hybrid TypeScript/Python SDK architecture for superior language-specific analysis: - -### Language-Specific Routing - -``` -┌─────────────────────────────────────────┐ -│ REST/GraphQL API │ -└────────────────┬────────────────────────┘ - │ -┌────────────────▼────────────────────────┐ -│ Hybrid AI Analyzer │ -│ (Language-Specific Routing Engine) │ -└────┬───────────┬───────────┬───────────┘ - │ │ │ -┌────▼───┐ ┌───▼───┐ ┌───▼───┐ -│Python │ │ TS │ │ JS │ -│Analyzer│ │Analyzer│ │Analyzer│ -└────┬───┘ └───┬───┘ └───┬───┘ - │ │ │ -┌────▼──────────▼───────────▼───┐ -│ Claude Code SDK Integration │ -├─────────────┬──────────────────┤ -│ Python SDK │ TypeScript SDK │ -└─────────────┴──────────────────┘ -``` - -### Performance Characteristics - -| Language | Analyzer | Accuracy | Speed | Special Features | -|----------|----------|----------|-------|-----------------| -| Python | Python SDK | 95% | 2-3s/file | AST analysis, async patterns | -| TypeScript | TS SDK | 92% | 1-2s/file | Type inference, React patterns | -| JavaScript | TS SDK | 90% | 1-2s/file | Dynamic analysis, closures | -| Go | TS SDK | 85% | 2-3s/file | Concurrency patterns | -| Other | TS SDK | 80% | 2-3s/file | Generic analysis | - -## Performance Metrics - -### Real-World Performance (Clone Global) - -- **Files Indexed**: 1,643 across 5 repositories -- **Index Time**: 13.2 seconds full scan -- **Index Size**: 7MB -- **Languages**: 9 different languages detected -- **Memory Usage**: ~85MB single process -- **Update Latency**: <100ms for file changes -- **Query Response**: <50ms for pattern matching -- **Cache Hit Rate**: 87% - -### Benchmarks - -| Metric | Current | Target | Industry Best | -|--------|---------|--------|---------------| -| Index Speed | 106 files/sec | 500 files/sec | 300 files/sec | -| Memory Usage | 85MB | 100MB | 200MB | -| Test Coverage | 8% | 80% | 70% | -| Languages | 9 | 15 | 12 | -| Query Speed | 50ms | 20ms | 30ms | -| Parser Accuracy | 95% | 99% | 85% | - -## Testing Infrastructure - -### Jest Configuration - -```javascript -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - coverageThreshold: { - global: { - branches: 80, - functions: 80, - lines: 80, - statements: 80 - } - }, - testMatch: ['**/*.test.ts', '**/*.spec.ts'] -}; -``` - -### Cypress E2E Tests - -```typescript -describe('Indexer E2E', () => { - it('should index entire codebase', () => { - cy.exec('indexer scan').then((result) => { - expect(result.stdout).to.contain('Files indexed:'); - }); - }); - - it('should detect file changes', () => { - cy.exec('indexer watch --daemon'); - cy.writeFile('test.js', 'const test = 1;'); - cy.wait(1000); - cy.exec('indexer query test').then((result) => { - expect(result.stdout).to.contain('test.js'); - }); - }); -}); -``` - -## Troubleshooting - -### Common Issues - -#### Out of Memory -```bash -# Increase Node memory limit -NODE_OPTIONS="--max-old-space-size=4096" indexer scan - -# Reduce workers -indexer scan --workers 2 -``` - -#### Slow Indexing -```bash -# Enable parallel processing -indexer scan --parallel --workers 8 - -# Use incremental mode -indexer scan --incremental - -# Clear cache if corrupted -indexer cache --clear -``` - -#### API Won't Start -```bash -# Check port availability -lsof -i :4000 - -# Use different port -API_PORT=5000 npm run api -``` - -#### AI Features Not Working -```bash -# Check API key -echo $ANTHROPIC_API_KEY - -# Verify Claude SDK installation -npm list @anthropic-ai/claude-code - -# Check MCP server configuration -cat config/mcp-servers.json -``` - -#### Streaming Not Working -```bash -# Check SSE support -curl -N http://localhost:4000/api/stream/analyze - -# Test WebSocket connection -wscat -c ws://localhost:4000/ws -``` - -## Future Roadmap & Initiatives - -### Phase 1: Critical Foundation Fixes (Week 1) -- Fix Python Parser with Tree-Sitter AST (COMPLETED) -- Implement comprehensive test suite (95% coverage target) -- Fix memory leaks in cache system -- Standardize error handling - -### Phase 2: Game-Changing Features (Week 2) -- AI-Powered Code Intelligence with bug prediction -- Real-Time Code Search Engine with Elasticsearch -- WebAssembly Performance Boost (10x faster parsing) -- Distributed Processing at Scale - -### Phase 3: Enterprise Features (Week 3) -- Advanced Security Scanning (OWASP compliance) -- Real-Time Dashboard with Socket.io -- GitHub Copilot Integration -- Performance profiling - -### Phase 4: Revolutionary Features (Week 4) -- Code Time Machine (track evolution, predict technical debt) -- Smart Code Review Bot -- Multi-Repo Intelligence Network with Neo4j -- Auto-Fix Engine for PRs - -### Unique Market Differentiators -1. AI-Powered Bug Prediction using ML models -2. WebAssembly Parsers for 10x speed -3. Multi-Repo Knowledge Graph -4. Code Time Machine for technical debt prediction -5. Semantic Code Search by meaning -6. Real-time Collaboration -7. Auto-Fix Engine -8. Copilot Enhancement - -## Original Vision (Eric's Indexer Hook) - -The indexer was conceived to provide "high signal and little noise" to AI models. Key principles: - -- **Minified abstraction**: UML-style extraction of imports, method signatures, constants, return types -- **Zero context overhead**: Hooks sit outside Claude's lifecycle -- **Automatic maintenance**: Updates on every file change -- **"Fresh" command**: Reads all documentation and PROJECT_INDEX.json for comprehensive understanding -- **Sub-agent integration**: Helps agents figure out which files/lines are needed for specific changes - -## Migration Guide - -### From v1 (claude-code-indexer) to v2 (indexer) - -```bash -# Backup existing installation -cp -r claude-code-indexer claude-code-indexer.backup - -# Uninstall old package -npm uninstall -g claude-code-indexer - -# Install new package -npm install -g indexer - -# Update configuration -mv .claude-indexer.json .indexerconfig.json - -# Re-index project -indexer scan --force -``` - -### Breaking Changes -| Feature | v1 | v2 | -|---------|----|----| -| Package name | claude-code-indexer | indexer | -| CLI command | claude-indexer | indexer | -| Config file | .claude-indexer.json | .indexerconfig.json or .indexer.yml | -| Output location | PROJECT_INDEX.json | .indexer-output/indexes/PROJECT_INDEX.json | - -## Health Monitoring - -```bash -# CLI health check -indexer health - -# API health check -curl http://localhost:4000/health - -# Detailed health with components -curl http://localhost:4000/api/health/detailed - -# Output example: -✅ Services detected: 5 -✅ Files indexed: 1,408 -✅ Cache hit rate: 87% -✅ Last update: 2 minutes ago -✅ API server: running (port 4000) -``` - -## Success Metrics - -### Launch Metrics (Month 1) -- Successful migrations: 100% -- Performance: Same or better -- Bug reports: <5 -- Documentation: 100% complete - -### Adoption Metrics (Month 3) -- NPM weekly downloads: 1,000+ -- GitHub stars: 100+ -- Active projects: 50+ -- Community plugins: 5+ - -### Growth Metrics (Month 6) -- NPM weekly downloads: 5,000+ -- GitHub stars: 500+ -- Contributors: 10+ -- Enterprise adoptions: 5+ - -## Known Issues & Limitations - -1. Low test coverage (8% vs 80% target) -2. Java parser not implemented -3. No incremental parsing (full re-parse) -4. Some `any` type usage in TypeScript -5. WASM parsers experimental -6. AST cache system not fully implemented -7. Differ utility for incremental updates missing - -## Conclusion - +# Consolidated Indexer Documentation + +## Project Overview + +**Project:** indexer +**Version:** 2.0.1 +**Type:** Universal code indexer for AI assistants and development tools +**Stack:** TypeScript, Node.js (>=16.0.0), Commander.js CLI +**Architecture:** Modular/Plugin-based with Parser Interface, Factory Pattern, Event Emitter, and Streaming patterns + +## Core Description + +The indexer is a hook that creates and maintains a minified UML-style abstraction of codebases in `PROJECT_INDEX.json`, updating automatically on file changes while sitting outside Claude's context window. It provides high signal-to-noise ratio for AI models without overwhelming their context. + +### Key Capabilities +- Lightning fast: Index 1000+ files in seconds (40+ files/second) +- Incremental updates: Auto-refresh on file changes +- Multi-language: JS/TS, Python, Go, SQL, GraphQL, YAML, Astro, and more (9 languages) +- Extensible: Plugin system for custom parsers +- AI-ready: Optimized for Claude, Copilot, and other AI tools +- Rich exports: JSON, GraphViz, Markdown, Mermaid, ASCII formats +- Zero context window overhead via external hooks + +## Quick Start Guide + +### Installation + +```bash +# Global installation +npm install -g indexer + +# Or with yarn +yarn global add indexer + +# Or add to existing project +npm install -D indexer +``` + +### Basic Usage + +```bash +# Initialize in your project +indexer init + +# Scan and create index +indexer scan + +# Watch for changes +indexer watch + +# Query the index +indexer query "function.*Controller" + +# View statistics +indexer stats + +# Export to different formats +indexer export markdown +``` + +### Environment Configuration + +```bash +# Required for AI features +export ANTHROPIC_API_KEY="your-api-key" + +# Optional integrations +export SLACK_BOT_TOKEN="xoxb-..." +export LINEAR_API_KEY="lin_..." +export DD_API_KEY="..." +export GITHUB_TOKEN="ghp_..." +``` + +## Claude Code Interactive Chat + +### Built-in Chat Interface + +```bash +# Start interactive chat +indexer chat + +# You'll see: +╔════════════════════════════════════════╗ +║ Claude Code Chat - Interactive ║ +╚════════════════════════════════════════╝ + +# Commands in chat: +/refresh - Rebuild project index +/clear - Clear conversation history +/save - Save conversation to markdown +/stats - Show project statistics +/search - Search for code +/explain - Explain a specific file +/help - Show all commands +exit - Quit chat +``` + +### One-Shot Queries + +```bash +# Ask questions without entering chat mode +indexer ask "What does the handlePayment function do?" +indexer ask "Find all SQL queries in this project" +indexer ask "Are there security issues in auth.service.ts?" + +# Query about specific files +indexer claude --file src/auth/service.ts --query "Explain this file" +indexer claude --function handleLogin --query "How can I optimize this?" +``` + +### AI Analysis Commands + +```bash +# Run specific AI analyses +indexer ai bugs # Predict bugs +indexer ai security # Security analysis +indexer ai refactor # Get refactoring suggestions +indexer ai tests # Generate test suggestions + +# With streaming output +indexer ai security --stream +``` + +## Architecture + +### Directory Structure + +``` +indexer/ +├── src/ +│ ├── cli/ # Command-line interface +│ │ └── index.ts # CLI with commander +│ ├── core/ +│ │ ├── indexer.ts # Main indexing engine +│ │ ├── cache-manager.ts # LRU cache (500MB limit) +│ │ ├── config.ts # Configuration loader +│ │ ├── monorepo.ts # Monorepo analyzer +│ │ └── watcher.ts # File system monitoring (Chokidar) +│ ├── parsers/ # Language-specific parsers +│ │ ├── javascript.ts # JS parser (@babel/parser) +│ │ ├── typescript.ts # TS parser +│ │ ├── python-tree-sitter.ts # Python AST parser (100% accurate) +│ │ ├── go.ts # Go parser +│ │ ├── sql.ts # SQL parser +│ │ ├── graphql.ts # GraphQL parser (enhanced) +│ │ ├── yaml.ts # YAML parser +│ │ └── astro.ts # Astro parser +│ ├── exporters/ # Export formats +│ │ ├── json.ts +│ │ ├── markdown.ts +│ │ ├── graphviz.ts +│ │ ├── mermaid.ts +│ │ └── ascii.ts +│ ├── ai/ # AI integration +│ │ ├── claude-analyzer.ts +│ │ ├── sdk-analyzer.ts # Claude SDK integration +│ │ ├── hybrid-analyzer.ts # Language-specific routing +│ │ ├── python/ # Python-specific analyzer +│ │ └── agents/ # Specialized AI agents +│ ├── api/ # API server +│ │ ├── rest/ # REST endpoints +│ │ ├── graphql/ # GraphQL schema +│ │ ├── websocket/ # WebSocket handler +│ │ └── middleware/ # Auth, rate limiting +│ ├── integrations/ # External services +│ │ ├── slack/ # Bug monitoring +│ │ ├── linear/ # Ticket creation +│ │ └── datadog/ # Metrics +│ └── types/ # TypeScript definitions +├── bin/ +│ └── indexer.js # CLI entry point +├── .indexer-output/ # Generated outputs +│ ├── indexes/ # JSON indexes +│ ├── docs/ # Markdown documentation +│ └── visualizations/ # Graphs & diagrams +└── package.json +``` + +### Core Components + +#### SmartIndexer +Automatic orchestration of all features with intelligent feature detection. + +#### Language Parsers (9 total) +- **JavaScript/TypeScript**: AST-based using @babel/parser +- **Python**: Tree-sitter AST (replaced regex, 100% accurate) +- **Go, SQL, GraphQL, YAML, Astro**: Custom parsers +- All implement common Parser interface + +#### Cache Manager +LRU cache with 500MB limit, TTL expiration, prevents memory leaks. + +#### Export System +Multiple formats: JSON, Markdown, GraphViz, Mermaid, ASCII with interactive HTML viewers. + +#### AI Integration +- Claude SDK integration with streaming +- MCP (Model Context Protocol) support +- Specialized agents: Security, Performance, Architecture, Testing +- Hybrid analyzer for language-specific analysis + +## Claude Code SDK Integration + +### Using the SDK + +The indexer uses the Claude Code SDK for advanced AI capabilities: + +```typescript +import { query } from "@anthropic-ai/claude-code"; + +// Basic usage +for await (const message of query({ + prompt: "Analyze system performance", + options: { + maxTurns: 5, + systemPrompt: "You are a performance engineer", + allowedTools: ["Bash", "Read", "WebSearch"] + } +})) { + if (message.type === "result") { + console.log(message.result); + } +} +``` + +### Python SDK Integration + +```python +import asyncio +from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions + +async def main(): + async with ClaudeSDKClient( + options=ClaudeCodeOptions( + system_prompt="You are a code analyzer", + max_turns=5, + allowed_tools=["Read", "Grep", "WebSearch"] + ) + ) as client: + await client.query("Analyze this code") + async for msg in client.receive_response(): + # Process streaming messages + pass + +asyncio.run(main()) +``` + +### MCP (Model Context Protocol) Integration + +Configure custom tools via MCP servers: + +```json +{ + "mcpServers": { + "semgrep": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-semgrep"], + "env": {} + }, + "github": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-github"], + "env": {"GITHUB_TOKEN": "${GITHUB_TOKEN}"} + } + } +} +``` + +## API Server + +### Starting the API Server + +```bash +# Start API server with all features +indexer api --port 4000 + +# Endpoints available at: +# REST: http://localhost:4000/api +# GraphQL: http://localhost:4000/graphql +# WebSocket: ws://localhost:4000/ws +# Streaming: http://localhost:4000/api/stream/* +``` + +### REST API Endpoints + +```bash +# Trigger indexing +curl -X POST http://localhost:4000/api/index \ + -H "Content-Type: application/json" \ + -d '{"path": "/workspace", "options": {"parallel": true}}' + +# AI analysis +curl -X POST http://localhost:4000/api/ai/analyze +curl -X POST http://localhost:4000/api/ai/predict-bugs +curl -X POST http://localhost:4000/api/ai/analyze-security +curl -X POST http://localhost:4000/api/ai/generate-tests + +# Specialized agents +curl -X POST http://localhost:4000/api/agents/security +curl -X POST http://localhost:4000/api/agents/performance +curl -X POST http://localhost:4000/api/agents/architecture +curl -X POST http://localhost:4000/api/agents/testing +``` + +### GraphQL Schema + +```graphql +type Query { + getIndex: ProjectIndex! + searchFunctions(name: String!): [Function!]! + findCircularDependencies: [Dependency!]! +} + +type Mutation { + analyzeWithAI(path: String, options: AIOptions): AIAnalysis! + predictBugs(files: [String!]): [BugPrediction!]! + generateTests(function: String!, file: String!): TestSuggestion! +} + +type Subscription { + aiAnalysisProgress: AIProgress! + fileUpdated(path: String): FileUpdate! +} +``` + +### WebSocket Real-time Updates + +```javascript +const ws = new WebSocket('ws://localhost:4000/ws'); + +ws.on('open', () => { + ws.send(JSON.stringify({ + type: 'subscribe', + topic: 'file-updates' + })); +}); + +ws.on('message', (data) => { + const msg = JSON.parse(data); + console.log('File updated:', msg.data); +}); +``` + +### Server-Sent Events (SSE) Streaming + +```javascript +const eventSource = new EventSource('http://localhost:4000/api/stream/analyze'); + +eventSource.addEventListener('progress', (event) => { + const data = JSON.parse(event.data); + console.log(`Analysis ${data.progress}% complete`); +}); + +eventSource.addEventListener('complete', (event) => { + const summary = JSON.parse(event.data).summary; + console.log('Analysis complete:', summary); + eventSource.close(); +}); +``` + +## Configuration + +### Project Configuration (.indexer.yml) + +```yaml +version: 2 +name: my-project +description: Project indexing configuration + +# Parser settings +parsers: + javascript: + ecmaVersion: 2024 + jsx: true + typescript: true + python: + version: "3.11" + type_checking: true + go: + module_path: github.com/org/project + +# Indexing rules +rules: + max_file_size: 10MB + max_files: 10000 + incremental_threshold: 100 + ignore_patterns: + - "*.generated.*" + - "*.min.js" + include_hidden: false + +# Performance tuning +performance: + parallel: true + workers: auto # or specific number + cache: true + cache_dir: .indexer-cache + memory_limit: 2GB + +# Export configuration +export: + formats: + - json + - graphviz + - markdown + auto_export: true + destination: ./docs/generated + +# Integrations +integrations: + claude: + enabled: true + hooks: auto + settings_path: ~/.config/claude/settings.json + slack: + enabled: true + webhook_url: ${SLACK_WEBHOOK_URL} + linear: + enabled: true + api_key: ${LINEAR_API_KEY} + datadog: + enabled: true + agent_host: ${DD_AGENT_HOST} +``` + +### Monorepo Configuration + +```yaml +version: 2 +name: my-monorepo +type: monorepo + +services: + backend: + path: backend/ + entry: main/src/server.go + language: go + frontend: + path: frontend/ + entry: src/app/layout.tsx + language: typescript + skills: + path: skills/ + pattern: "skill_*/run.py" + language: python + +# Focus areas for deep analysis +focus: + - backend/main/src/graph/** # GraphQL resolvers + - frontend/src/app/** # App router + - frontend/src/hooks/** # React hooks + - skills/skill_*/run.py # Skill implementations + +# What to ignore +ignore: + - "**/node_modules/**" + - "**/.next/**" + - "**/dist/**" + - "**/__pycache__/**" +``` + +## Deployment + +### Docker Deployment + +```dockerfile +FROM node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production + +FROM node:20-alpine +WORKDIR /app +COPY --from=builder /app/node_modules ./node_modules +COPY . . +RUN npm run build +ENTRYPOINT ["node", "dist/cli/index.js"] +``` + +### Docker Compose + +```yaml +version: '3.8' +services: + indexer: + image: indexer:latest + ports: + - "4000:4000" + volumes: + - ./repositories:/workspace + - ./indexes:/indexes + - ./.indexer.yml:/workspace/.indexer.yml + environment: + - NODE_ENV=production + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} + - GITHUB_TOKEN=${GITHUB_TOKEN} + command: npm run api:prod + + redis: + image: redis:alpine + ports: + - "6379:6379" + + elasticsearch: + image: elasticsearch:8.11.0 + environment: + - discovery.type=single-node + - ES_JAVA_OPTS=-Xms512m -Xmx512m + ports: + - "9200:9200" +``` + +### Kubernetes Deployment + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: indexer +spec: + replicas: 10 + selector: + matchLabels: + app: indexer + template: + metadata: + labels: + app: indexer + spec: + containers: + - name: indexer + image: indexer:latest + resources: + requests: + memory: "1Gi" + cpu: "1" + limits: + memory: "2Gi" + cpu: "2" + env: + - name: REDIS_HOST + value: redis-service + - name: ELASTICSEARCH_URL + value: http://elasticsearch:9200 +``` + +## Skill Discovery Guide + +### Active Skills (14 total) + +| Skill Name | Purpose | Port | +|------------|---------|------| +| generate-image | Image generation | 9001 | +| clone-transcribe-skill | Audio/video transcription | 9002 | +| clone-pdf-to-text-v2-skill | PDF text extraction | 9003 | +| clone-csv-to-structured-text | CSV data processing | 9004 | +| clone-medical-research-skill | Medical research | 9005 | +| clone-scientific-research-skill | Scientific research | 9006 | +| clone-research-legal-skill | Legal research | 9007 | +| clone-science-tid-bit-skill | Science facts | 9008 | +| clone-check-calendar-availability-v2-skill | Calendar checking | 9009 | +| clone-create-meeting-confirm-v2-skill | Meeting creation | 9010 | +| clone-check-calendar-availability-internal-attendees-skill | Internal availability | 9011 | +| clone-create-meeting-confirm-internal-attendee-skill | Internal meetings | 9012 | + +### Skill Discovery Queries + +```bash +# List all skill run functions +indexer query "run" --type function | grep "skills/" + +# Find calendar-related skills +indexer query "calendar" --type function | grep "skills/" + +# Find research skills +indexer query "research" --type function | grep "skills/" + +# Find document processing skills +indexer query "pdf\|csv\|text" --type function | grep "skills/" + +# Find all skill GraphQL operations +indexer query "Skill" --type function | grep -E "mutation|query" + +# Check skill dependencies +indexer query "skill_runner" --type import + +# Analyze skill complexity +for skill in $(find ./skills -maxdepth 2 -name "run.py" | cut -d'/' -f3); do + count=$(indexer query "" --type function | grep "skills/$skill" | wc -l) + echo "$skill: $count functions" +done +``` + +### Required Skill Function Signature + +```python +def run(clone: Dict[str, Any], params: Dict[str, Any], + variables: Dict[str, Any], api_token: str) -> str: + """ + Parameters: + - clone: Clone context and metadata + - params: Skill-specific parameters + - variables: Environment variables + - api_token: Authentication token + + Returns: + - str: Always returns a string response + """ + return "response" +``` + +## Hybrid AI Architecture + +The indexer uses a hybrid TypeScript/Python SDK architecture for superior language-specific analysis: + +### Language-Specific Routing + +``` +┌─────────────────────────────────────────┐ +│ REST/GraphQL API │ +└────────────────┬────────────────────────┘ + │ +┌────────────────▼────────────────────────┐ +│ Hybrid AI Analyzer │ +│ (Language-Specific Routing Engine) │ +└────┬───────────┬───────────┬───────────┘ + │ │ │ +┌────▼───┐ ┌───▼───┐ ┌───▼───┐ +│Python │ │ TS │ │ JS │ +│Analyzer│ │Analyzer│ │Analyzer│ +└────┬───┘ └───┬───┘ └───┬───┘ + │ │ │ +┌────▼──────────▼───────────▼───┐ +│ Claude Code SDK Integration │ +├─────────────┬──────────────────┤ +│ Python SDK │ TypeScript SDK │ +└─────────────┴──────────────────┘ +``` + +### Performance Characteristics + +| Language | Analyzer | Accuracy | Speed | Special Features | +|----------|----------|----------|-------|-----------------| +| Python | Python SDK | 95% | 2-3s/file | AST analysis, async patterns | +| TypeScript | TS SDK | 92% | 1-2s/file | Type inference, React patterns | +| JavaScript | TS SDK | 90% | 1-2s/file | Dynamic analysis, closures | +| Go | TS SDK | 85% | 2-3s/file | Concurrency patterns | +| Other | TS SDK | 80% | 2-3s/file | Generic analysis | + +## Performance Metrics + +### Real-World Performance (Clone Global) + +- **Files Indexed**: 1,643 across 5 repositories +- **Index Time**: 13.2 seconds full scan +- **Index Size**: 7MB +- **Languages**: 9 different languages detected +- **Memory Usage**: ~85MB single process +- **Update Latency**: <100ms for file changes +- **Query Response**: <50ms for pattern matching +- **Cache Hit Rate**: 87% + +### Benchmarks + +| Metric | Current | Target | Industry Best | +|--------|---------|--------|---------------| +| Index Speed | 106 files/sec | 500 files/sec | 300 files/sec | +| Memory Usage | 85MB | 100MB | 200MB | +| Test Coverage | 8% | 80% | 70% | +| Languages | 9 | 15 | 12 | +| Query Speed | 50ms | 20ms | 30ms | +| Parser Accuracy | 95% | 99% | 85% | + +## Testing Infrastructure + +### Jest Configuration + +```javascript +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + coverageThreshold: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: 80 + } + }, + testMatch: ['**/*.test.ts', '**/*.spec.ts'] +}; +``` + +### Cypress E2E Tests + +```typescript +describe('Indexer E2E', () => { + it('should index entire codebase', () => { + cy.exec('indexer scan').then((result) => { + expect(result.stdout).to.contain('Files indexed:'); + }); + }); + + it('should detect file changes', () => { + cy.exec('indexer watch --daemon'); + cy.writeFile('test.js', 'const test = 1;'); + cy.wait(1000); + cy.exec('indexer query test').then((result) => { + expect(result.stdout).to.contain('test.js'); + }); + }); +}); +``` + +## Troubleshooting + +### Common Issues + +#### Out of Memory +```bash +# Increase Node memory limit +NODE_OPTIONS="--max-old-space-size=4096" indexer scan + +# Reduce workers +indexer scan --workers 2 +``` + +#### Slow Indexing +```bash +# Enable parallel processing +indexer scan --parallel --workers 8 + +# Use incremental mode +indexer scan --incremental + +# Clear cache if corrupted +indexer cache --clear +``` + +#### API Won't Start +```bash +# Check port availability +lsof -i :4000 + +# Use different port +API_PORT=5000 npm run api +``` + +#### AI Features Not Working +```bash +# Check API key +echo $ANTHROPIC_API_KEY + +# Verify Claude SDK installation +npm list @anthropic-ai/claude-code + +# Check MCP server configuration +cat config/mcp-servers.json +``` + +#### Streaming Not Working +```bash +# Check SSE support +curl -N http://localhost:4000/api/stream/analyze + +# Test WebSocket connection +wscat -c ws://localhost:4000/ws +``` + +## Future Roadmap & Initiatives + +### Phase 1: Critical Foundation Fixes (Week 1) +- Fix Python Parser with Tree-Sitter AST (COMPLETED) +- Implement comprehensive test suite (95% coverage target) +- Fix memory leaks in cache system +- Standardize error handling + +### Phase 2: Game-Changing Features (Week 2) +- AI-Powered Code Intelligence with bug prediction +- Real-Time Code Search Engine with Elasticsearch +- WebAssembly Performance Boost (10x faster parsing) +- Distributed Processing at Scale + +### Phase 3: Enterprise Features (Week 3) +- Advanced Security Scanning (OWASP compliance) +- Real-Time Dashboard with Socket.io +- GitHub Copilot Integration +- Performance profiling + +### Phase 4: Revolutionary Features (Week 4) +- Code Time Machine (track evolution, predict technical debt) +- Smart Code Review Bot +- Multi-Repo Intelligence Network with Neo4j +- Auto-Fix Engine for PRs + +### Unique Market Differentiators +1. AI-Powered Bug Prediction using ML models +2. WebAssembly Parsers for 10x speed +3. Multi-Repo Knowledge Graph +4. Code Time Machine for technical debt prediction +5. Semantic Code Search by meaning +6. Real-time Collaboration +7. Auto-Fix Engine +8. Copilot Enhancement + +## Original Vision (Eric's Indexer Hook) + +The indexer was conceived to provide "high signal and little noise" to AI models. Key principles: + +- **Minified abstraction**: UML-style extraction of imports, method signatures, constants, return types +- **Zero context overhead**: Hooks sit outside Claude's lifecycle +- **Automatic maintenance**: Updates on every file change +- **"Fresh" command**: Reads all documentation and PROJECT_INDEX.json for comprehensive understanding +- **Sub-agent integration**: Helps agents figure out which files/lines are needed for specific changes + +## Migration Guide + +### From v1 (claude-code-indexer) to v2 (indexer) + +```bash +# Backup existing installation +cp -r claude-code-indexer claude-code-indexer.backup + +# Uninstall old package +npm uninstall -g claude-code-indexer + +# Install new package +npm install -g indexer + +# Update configuration +mv .claude-indexer.json .indexerconfig.json + +# Re-index project +indexer scan --force +``` + +### Breaking Changes +| Feature | v1 | v2 | +|---------|----|----| +| Package name | claude-code-indexer | indexer | +| CLI command | claude-indexer | indexer | +| Config file | .claude-indexer.json | .indexerconfig.json or .indexer.yml | +| Output location | PROJECT_INDEX.json | .indexer-output/indexes/PROJECT_INDEX.json | + +## Health Monitoring + +```bash +# CLI health check +indexer health + +# API health check +curl http://localhost:4000/health + +# Detailed health with components +curl http://localhost:4000/api/health/detailed + +# Output example: +✅ Services detected: 5 +✅ Files indexed: 1,408 +✅ Cache hit rate: 87% +✅ Last update: 2 minutes ago +✅ API server: running (port 4000) +``` + +## Success Metrics + +### Launch Metrics (Month 1) +- Successful migrations: 100% +- Performance: Same or better +- Bug reports: <5 +- Documentation: 100% complete + +### Adoption Metrics (Month 3) +- NPM weekly downloads: 1,000+ +- GitHub stars: 100+ +- Active projects: 50+ +- Community plugins: 5+ + +### Growth Metrics (Month 6) +- NPM weekly downloads: 5,000+ +- GitHub stars: 500+ +- Contributors: 10+ +- Enterprise adoptions: 5+ + +## Known Issues & Limitations + +1. Low test coverage (8% vs 80% target) +2. Java parser not implemented +3. No incremental parsing (full re-parse) +4. Some `any` type usage in TypeScript +5. WASM parsers experimental +6. AST cache system not fully implemented +7. Differ utility for incremental updates missing + +## Conclusion + The indexer provides comprehensive code intelligence for AI assistants and development tools. With support for 9 languages, 5 export formats, advanced AI integration, and production-proven performance, it serves as a critical foundation for modern development workflows. The system successfully indexes large monorepos (1,600+ files in 13 seconds) while maintaining low memory usage and providing real-time updates. \ No newline at end of file diff --git a/package.json b/package.json index 977de8b..541529a 100644 --- a/package.json +++ b/package.json @@ -1,102 +1,106 @@ -{ - "name": "@cloneglobal/indexer", - "version": "2.0.2", - "description": "High-performance universal codebase indexer for AI assistants and development tools", - "main": "dist/index.js", - "bin": { - "indexer": "./bin/indexer.js" - }, - "scripts": { - "build": "tsc && node scripts/build-worker.js", - "dev": "tsc --watch", - "test": "jest", - "test:unit": "jest", - "test:e2e": "cypress run", - "test:open": "cypress open", - "test:coverage": "jest --coverage", - "lint": "eslint src/", - "format": "prettier --write 'src/**/*.ts'", - "prepublish": "npm run build && npm test", - "postinstall": "node scripts/postinstall.js", - "clean": "rm -rf dist .indexer-cache coverage", - "prebuild": "npm run clean", - "api": "npm run build && node dist/cli/index.js api", - "api:dev": "npm run build && node dist/cli/index.js api --enable-auth false", - "api:prod": "npm run build && NODE_ENV=production node dist/cli/index.js api --enable-auth true", - "start": "ts-node --transpile-only src/cli/index.ts", - "scan": "ts-node --transpile-only src/cli/index.ts scan" - }, - "keywords": [ - "indexer", - "code-analysis", - "ast", - "parser", - "codebase", - "ai-tools", - "claude", - "copilot", - "development-tools" - ], - "author": "Clone Global", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/cloneglobal/indexer" - }, - "dependencies": { - "@anthropic-ai/claude-code": "^1.0.89", - "@apollo/server": "^5.0.0", - "@babel/parser": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0", - "@graphql-tools/schema": "^10.0.25", - "@linear/sdk": "^25.0.0", - "@slack/events-api": "^3.0.1", - "@slack/web-api": "^6.11.0", - "apollo-server-express": "^3.13.0", - "bcrypt": "^6.0.0", - "chokidar": "^3.6.0", - "commander": "^12.0.0", - "cors": "^2.8.5", - "dotenv": "^17.2.1", - "express": "^4.21.1", - "express-rate-limit": "^8.0.1", - "glob": "^10.3.10", - "graphql": "^16.11.0", - "graphql-subscriptions": "^3.0.0", - "helmet": "^8.1.0", - "ignore": "^5.3.1", - "js-yaml": "^4.1.0", - "jsonwebtoken": "^9.0.2", - "morgan": "^1.10.1", - "node-dogstatsd": "^0.0.7", - "p-limit": "^5.0.0", - "tree-sitter": "^0.25.0", - "tree-sitter-python": "^0.23.6", - "typescript": "^5.3.3", - "ws": "^8.18.3" - }, - "devDependencies": { - "@cypress/code-coverage": "^3.12.0", - "@jest/globals": "^30.0.5", - "@types/babel__traverse": "^7.20.5", - "@types/jest": "^30.0.0", - "@types/js-yaml": "^4.0.9", - "@types/node": "^20.19.11", - "@typescript-eslint/eslint-plugin": "^6.19.0", - "@typescript-eslint/parser": "^6.19.0", - "chalk": "^4.1.2", - "cypress": "^13.6.3", - "eslint": "^8.56.0", - "jest": "^29.7.0", - "prettier": "^3.2.4", - "ts-jest": "^29.1.1", - "ts-node": "^10.9.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "publishConfig": { - "access": "public" - } -} +{ + "name": "@cloneglobal/indexer", + "version": "2.0.2", + "description": "High-performance universal codebase indexer for AI assistants and development tools", + "main": "dist/index.js", + "bin": { + "indexer": "./bin/indexer.js" + }, + "scripts": { + "build": "tsc && node scripts/build-worker.js", + "dev": "tsc --watch", + "test": "jest", + "test:unit": "jest", + "test:e2e": "cypress run", + "test:open": "cypress open", + "test:coverage": "jest --coverage", + "lint": "eslint src/", + "format": "prettier --write 'src/**/*.ts'", + "prepublish": "npm run build && npm test", + "postinstall": "node scripts/postinstall.js", + "clean": "rm -rf dist .indexer-cache coverage", + "prebuild": "npm run clean", + "api": "npm run build && node dist/cli/index.js api", + "api:dev": "npm run build && node dist/cli/index.js api --enable-auth false", + "api:prod": "npm run build && NODE_ENV=production node dist/cli/index.js api --enable-auth true", + "start": "ts-node --transpile-only src/cli/index.ts", + "scan": "ts-node --transpile-only src/cli/index.ts scan" + }, + "keywords": [ + "indexer", + "code-analysis", + "ast", + "parser", + "codebase", + "ai-tools", + "claude", + "copilot", + "development-tools" + ], + "author": "Clone Global", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/cloneglobal/indexer" + }, + "dependencies": { + "@anthropic-ai/claude-code": "^1.0.89", + "@apollo/server": "^5.0.0", + "@babel/parser": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", + "@graphql-tools/schema": "^10.0.25", + "@linear/sdk": "^25.0.0", + "@slack/events-api": "^3.0.1", + "@slack/web-api": "^6.11.0", + "apollo-server-express": "^3.13.0", + "bcrypt": "^6.0.0", + "chokidar": "^3.6.0", + "commander": "^12.0.0", + "cors": "^2.8.5", + "dotenv": "^17.2.1", + "express": "^4.21.1", + "express-rate-limit": "^8.0.1", + "glob": "^10.3.10", + "graphql": "^16.11.0", + "graphql-subscriptions": "^3.0.0", + "helmet": "^8.1.0", + "ignore": "^5.3.1", + "js-yaml": "^4.1.0", + "jsonwebtoken": "^9.0.2", + "morgan": "^1.10.1", + "node-dogstatsd": "^0.0.7", + "p-limit": "^5.0.0", + "tree-sitter": "^0.25.0", + "tree-sitter-python": "^0.23.6", + "typescript": "^5.3.3", + "ws": "^8.18.3" + }, + "devDependencies": { + "@cypress/code-coverage": "^3.12.0", + "@jest/globals": "^30.0.5", + "@types/babel__traverse": "^7.20.5", + "@types/bcrypt": "^5.0.2", + "@types/jest": "^30.0.0", + "@types/js-yaml": "^4.0.9", + "@types/jsonwebtoken": "^9.0.6", + "@types/morgan": "^1.9.9", + "@types/node": "^20.19.11", + "@types/ws": "^8.5.10", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "chalk": "^4.1.2", + "cypress": "^13.6.3", + "eslint": "^8.56.0", + "jest": "^29.7.0", + "prettier": "^3.2.4", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/scripts/build-worker.js b/scripts/build-worker.js index 0feecbb..be88133 100644 --- a/scripts/build-worker.js +++ b/scripts/build-worker.js @@ -1,31 +1,31 @@ -#!/usr/bin/env node - -/** - * Build script for Worker Thread scripts - * Compiles TypeScript worker files to JavaScript for Worker Thread execution - */ - -const { execSync } = require('child_process'); -const path = require('path'); -const fs = require('fs'); - -const workerSrc = path.join(__dirname, '..', 'src', 'core', 'parser-worker.ts'); -const workerDist = path.join(__dirname, '..', 'dist', 'core', 'parser-worker.js'); - -// Ensure dist directory exists -const distDir = path.dirname(workerDist); -if (!fs.existsSync(distDir)) { - fs.mkdirSync(distDir, { recursive: true }); -} - -// Compile the worker script -console.log('Compiling worker script...'); -try { - execSync(`npx tsc ${workerSrc} --outDir ${path.dirname(workerDist)} --module commonjs --target ES2023`, { - stdio: 'inherit' - }); - console.log('✓ Worker script compiled successfully'); -} catch (error) { - console.error('Failed to compile worker script:', error); - process.exit(1); +#!/usr/bin/env node + +/** + * Build script for Worker Thread scripts + * Compiles TypeScript worker files to JavaScript for Worker Thread execution + */ + +const { execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); + +const workerSrc = path.join(__dirname, '..', 'src', 'core', 'parser-worker.ts'); +const workerDist = path.join(__dirname, '..', 'dist', 'core', 'parser-worker.js'); + +// Ensure dist directory exists +const distDir = path.dirname(workerDist); +if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); +} + +// Compile the worker script +console.log('Compiling worker script...'); +try { + execSync(`npx tsc ${workerSrc} --outDir ${path.dirname(workerDist)} --module commonjs --target ES2023`, { + stdio: 'inherit' + }); + console.log('✓ Worker script compiled successfully'); +} catch (error) { + console.error('Failed to compile worker script:', error); + process.exit(1); } \ No newline at end of file diff --git a/scripts/check-index-health.sh b/scripts/check-index-health.sh index db981e1..879a1d6 100644 --- a/scripts/check-index-health.sh +++ b/scripts/check-index-health.sh @@ -1,90 +1,90 @@ -#!/bin/bash - -echo "══════════════════════════════════════" -echo " INDEX HEALTH CHECK REPORT " -echo "══════════════════════════════════════" -echo "" - -# Check if index exists -if [ ! -f PROJECT_INDEX.json ]; then - echo "❌ ERROR: PROJECT_INDEX.json not found!" - echo " Run: indexer init" - exit 1 -fi - -# Check age -AGE_MINUTES=$(find PROJECT_INDEX.json -mmin +60 | wc -l) -if [ $AGE_MINUTES -gt 0 ]; then - echo "⚠️ WARNING: Index older than 1 hour" - LAST_MODIFIED=$(stat -c %y PROJECT_INDEX.json 2>/dev/null || stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" PROJECT_INDEX.json) - echo " Last updated: $LAST_MODIFIED" -else - echo "✅ Index recently updated" -fi - -# Check size -SIZE=$(du -h PROJECT_INDEX.json | cut -f1) -echo "📊 Index size: $SIZE" - -# Check coverage -TOTAL_FILES=$(find . -type f \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.go" \) 2>/dev/null | grep -v node_modules | wc -l) -INDEXED_FILES=$(cat PROJECT_INDEX.json | jq '.statistics.totalFiles' 2>/dev/null || echo "0") - -if [ "$INDEXED_FILES" != "0" ] && [ "$TOTAL_FILES" != "0" ]; then - COVERAGE=$((INDEXED_FILES * 100 / TOTAL_FILES)) - echo "📈 Coverage: $INDEXED_FILES/$TOTAL_FILES files ($COVERAGE%)" -else - echo "📈 Coverage: Unable to calculate" -fi - -# Check for errors (if jq is available) -if command -v jq &> /dev/null; then - ERRORS=$(cat PROJECT_INDEX.json | jq '.errors | length' 2>/dev/null || echo "0") - if [ "$ERRORS" -gt "0" ]; then - echo "⚠️ Found $ERRORS parsing errors" - fi - - # Service breakdown for monorepos - echo "" - echo "Service Breakdown:" - - # Check if it's a monorepo - IS_MONOREPO=$(cat PROJECT_INDEX.json | jq '.monorepo' 2>/dev/null) - if [ "$IS_MONOREPO" != "null" ]; then - echo " Frontend: $(cat PROJECT_INDEX.json | jq '[.files | keys[] | select(contains("frontend"))] | length') files" - echo " Backend: $(cat PROJECT_INDEX.json | jq '[.files | keys[] | select(contains("backend"))] | length') files" - echo " Skills: $(cat PROJECT_INDEX.json | jq '[.files | keys[] | select(contains("skills"))] | length') files" - echo " Data-ops: $(cat PROJECT_INDEX.json | jq '[.files | keys[] | select(contains("data-ops"))] | length') files" - echo " Marketing:$(cat PROJECT_INDEX.json | jq '[.files | keys[] | select(contains("marketing"))] | length') files" - else - # Generic language breakdown - echo " JavaScript: $(cat PROJECT_INDEX.json | jq '.statistics.languages.JavaScript // 0') files" - echo " TypeScript: $(cat PROJECT_INDEX.json | jq '.statistics.languages.TypeScript // 0') files" - echo " Python: $(cat PROJECT_INDEX.json | jq '.statistics.languages.Python // 0') files" - echo " Go: $(cat PROJECT_INDEX.json | jq '.statistics.languages.Go // 0') files" - fi - - # Show version - echo "" - VERSION=$(cat PROJECT_INDEX.json | jq -r '.version') - echo "Index Version: $VERSION" - - # Show health score if available - HEALTH_SCORE=$(cat PROJECT_INDEX.json | jq '.monorepo.healthScore // 0' 2>/dev/null) - if [ "$HEALTH_SCORE" != "0" ]; then - echo "Health Score: $HEALTH_SCORE/100" - fi -else - echo "" - echo "Note: Install jq for detailed analysis" -fi - -echo "" -echo "══════════════════════════════════════" - -# Exit with appropriate code -if [ "$ERRORS" -gt "0" ] || [ "$COVERAGE" -lt "50" ]; then - exit 1 -else - exit 0 +#!/bin/bash + +echo "══════════════════════════════════════" +echo " INDEX HEALTH CHECK REPORT " +echo "══════════════════════════════════════" +echo "" + +# Check if index exists +if [ ! -f PROJECT_INDEX.json ]; then + echo "❌ ERROR: PROJECT_INDEX.json not found!" + echo " Run: indexer init" + exit 1 +fi + +# Check age +AGE_MINUTES=$(find PROJECT_INDEX.json -mmin +60 | wc -l) +if [ $AGE_MINUTES -gt 0 ]; then + echo "⚠️ WARNING: Index older than 1 hour" + LAST_MODIFIED=$(stat -c %y PROJECT_INDEX.json 2>/dev/null || stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" PROJECT_INDEX.json) + echo " Last updated: $LAST_MODIFIED" +else + echo "✅ Index recently updated" +fi + +# Check size +SIZE=$(du -h PROJECT_INDEX.json | cut -f1) +echo "📊 Index size: $SIZE" + +# Check coverage +TOTAL_FILES=$(find . -type f \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.go" \) 2>/dev/null | grep -v node_modules | wc -l) +INDEXED_FILES=$(cat PROJECT_INDEX.json | jq '.statistics.totalFiles' 2>/dev/null || echo "0") + +if [ "$INDEXED_FILES" != "0" ] && [ "$TOTAL_FILES" != "0" ]; then + COVERAGE=$((INDEXED_FILES * 100 / TOTAL_FILES)) + echo "📈 Coverage: $INDEXED_FILES/$TOTAL_FILES files ($COVERAGE%)" +else + echo "📈 Coverage: Unable to calculate" +fi + +# Check for errors (if jq is available) +if command -v jq &> /dev/null; then + ERRORS=$(cat PROJECT_INDEX.json | jq '.errors | length' 2>/dev/null || echo "0") + if [ "$ERRORS" -gt "0" ]; then + echo "⚠️ Found $ERRORS parsing errors" + fi + + # Service breakdown for monorepos + echo "" + echo "Service Breakdown:" + + # Check if it's a monorepo + IS_MONOREPO=$(cat PROJECT_INDEX.json | jq '.monorepo' 2>/dev/null) + if [ "$IS_MONOREPO" != "null" ]; then + echo " Frontend: $(cat PROJECT_INDEX.json | jq '[.files | keys[] | select(contains("frontend"))] | length') files" + echo " Backend: $(cat PROJECT_INDEX.json | jq '[.files | keys[] | select(contains("backend"))] | length') files" + echo " Skills: $(cat PROJECT_INDEX.json | jq '[.files | keys[] | select(contains("skills"))] | length') files" + echo " Data-ops: $(cat PROJECT_INDEX.json | jq '[.files | keys[] | select(contains("data-ops"))] | length') files" + echo " Marketing:$(cat PROJECT_INDEX.json | jq '[.files | keys[] | select(contains("marketing"))] | length') files" + else + # Generic language breakdown + echo " JavaScript: $(cat PROJECT_INDEX.json | jq '.statistics.languages.JavaScript // 0') files" + echo " TypeScript: $(cat PROJECT_INDEX.json | jq '.statistics.languages.TypeScript // 0') files" + echo " Python: $(cat PROJECT_INDEX.json | jq '.statistics.languages.Python // 0') files" + echo " Go: $(cat PROJECT_INDEX.json | jq '.statistics.languages.Go // 0') files" + fi + + # Show version + echo "" + VERSION=$(cat PROJECT_INDEX.json | jq -r '.version') + echo "Index Version: $VERSION" + + # Show health score if available + HEALTH_SCORE=$(cat PROJECT_INDEX.json | jq '.monorepo.healthScore // 0' 2>/dev/null) + if [ "$HEALTH_SCORE" != "0" ]; then + echo "Health Score: $HEALTH_SCORE/100" + fi +else + echo "" + echo "Note: Install jq for detailed analysis" +fi + +echo "" +echo "══════════════════════════════════════" + +# Exit with appropriate code +if [ "$ERRORS" -gt "0" ] || [ "$COVERAGE" -lt "50" ]; then + exit 1 +else + exit 0 fi \ No newline at end of file diff --git a/scripts/clean-outputs.sh b/scripts/clean-outputs.sh index bcf0f18..58864e8 100644 --- a/scripts/clean-outputs.sh +++ b/scripts/clean-outputs.sh @@ -1,53 +1,53 @@ -#!/bin/bash - -# Script to clean up all indexer output files from the repository root -# Usage: ./scripts/clean-outputs.sh - -set -e - -REPO_ROOT=".." # Relative to indexer/scripts directory when run from indexer - -echo "🧹 Cleaning up indexer output files from repository root..." - -# Function to remove file if it exists -remove_if_exists() { - local file="$1" - if [ -f "$REPO_ROOT/$file" ]; then - echo " Removing $file" - rm "$REPO_ROOT/$file" - fi -} - -# Remove index files -remove_if_exists "PROJECT_INDEX.json" - -# Remove documentation files -remove_if_exists "CODE_INDEX.md" -remove_if_exists "project-tree.txt" -remove_if_exists "project-table.txt" - -# Remove visualization files -remove_if_exists "dependencies.dot" -remove_if_exists "dependencies.mmd" -remove_if_exists "dependencies.png" -remove_if_exists "service-boxes.txt" - -# Remove HTML viewers -remove_if_exists "components.html" -remove_if_exists "frontend-only.html" -remove_if_exists "mermaid-viewer.html" -remove_if_exists "viewer-limited.html" - -# Optionally remove the .indexer-output directory if it exists -if [ -d "$REPO_ROOT/.indexer-output" ]; then - read -p "Remove .indexer-output directory? (y/n) " -n 1 -r - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - rm -rf "$REPO_ROOT/.indexer-output" - echo " Removed .indexer-output directory" - fi -fi - -echo "✅ Cleanup complete!" -echo "" +#!/bin/bash + +# Script to clean up all indexer output files from the repository root +# Usage: ./scripts/clean-outputs.sh + +set -e + +REPO_ROOT=".." # Relative to indexer/scripts directory when run from indexer + +echo "🧹 Cleaning up indexer output files from repository root..." + +# Function to remove file if it exists +remove_if_exists() { + local file="$1" + if [ -f "$REPO_ROOT/$file" ]; then + echo " Removing $file" + rm "$REPO_ROOT/$file" + fi +} + +# Remove index files +remove_if_exists "PROJECT_INDEX.json" + +# Remove documentation files +remove_if_exists "CODE_INDEX.md" +remove_if_exists "project-tree.txt" +remove_if_exists "project-table.txt" + +# Remove visualization files +remove_if_exists "dependencies.dot" +remove_if_exists "dependencies.mmd" +remove_if_exists "dependencies.png" +remove_if_exists "service-boxes.txt" + +# Remove HTML viewers +remove_if_exists "components.html" +remove_if_exists "frontend-only.html" +remove_if_exists "mermaid-viewer.html" +remove_if_exists "viewer-limited.html" + +# Optionally remove the .indexer-output directory if it exists +if [ -d "$REPO_ROOT/.indexer-output" ]; then + read -p "Remove .indexer-output directory? (y/n) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + rm -rf "$REPO_ROOT/.indexer-output" + echo " Removed .indexer-output directory" + fi +fi + +echo "✅ Cleanup complete!" +echo "" echo "You can now run 'indexer init' to generate files in the new structure." \ No newline at end of file diff --git a/scripts/install.js b/scripts/install.js index 30c4d0f..e6a6171 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -1,171 +1,171 @@ -#!/usr/bin/env node - -/** - * One-line installer for @cloneglobal/indexer - * Usage: curl -fsSL https://raw.githubusercontent.com/clone-global/indexer/main/install.js | node - */ - -const fs = require('fs'); -const path = require('path'); -const { execSync } = require('child_process'); -const os = require('os'); - -const PACKAGE_NAME = '@cloneglobal/indexer'; -const CONFIG_DIR = path.join(os.homedir(), '.config', 'claude'); -const SETTINGS_FILE = path.join(CONFIG_DIR, 'settings.json'); - -console.log('Installing @cloneglobal/indexer...\n'); - -// Step 1: Check Node.js version -const nodeVersion = process.version; -const majorVersion = parseInt(nodeVersion.split('.')[0].substring(1)); -if (majorVersion < 16) { - console.error(`Error: Node.js 16+ required (current: ${nodeVersion})`); - process.exit(1); -} - -// Step 2: Install package globally -try { - console.log('📦 Installing package globally...'); - - // Detect package manager - let packageManager = 'npm'; - try { - execSync('yarn --version', { stdio: 'ignore' }); - packageManager = 'yarn'; - } catch { - // npm is default - } - - if (packageManager === 'yarn') { - execSync(`yarn global add ${PACKAGE_NAME}`, { stdio: 'inherit' }); - } else { - execSync(`npm install -g ${PACKAGE_NAME}`, { stdio: 'inherit' }); - } - - console.log('✅ Package installed successfully\n'); -} catch (error) { - console.error('❌ Failed to install package:', error.message); - process.exit(1); -} - -// Step 3: Configure Claude Code hooks -console.log('🔧 Configuring Claude Code hooks...'); - -// Ensure config directory exists -if (!fs.existsSync(CONFIG_DIR)) { - fs.mkdirSync(CONFIG_DIR, { recursive: true }); -} - -// Load or create settings -let settings = {}; -if (fs.existsSync(SETTINGS_FILE)) { - try { - const content = fs.readFileSync(SETTINGS_FILE, 'utf8'); - settings = JSON.parse(content); - } catch (error) { - console.warn('⚠️ Could not parse existing settings.json, creating new one'); - } -} - -// Find indexer installation path -let indexerPath; -try { - if (packageManager === 'yarn') { - const globalDir = execSync('yarn global dir', { encoding: 'utf8' }).trim(); - indexerPath = path.join(globalDir, 'node_modules', PACKAGE_NAME); - } else { - const npmRoot = execSync('npm root -g', { encoding: 'utf8' }).trim(); - indexerPath = path.join(npmRoot, PACKAGE_NAME); - } -} catch (error) { - console.error('❌ Could not find indexer installation path'); - process.exit(1); -} - -const hookScript = path.join(indexerPath, 'dist', 'hooks', 'claude-hook.js'); - -// Configure hooks -if (!settings.hooks) { - settings.hooks = {}; -} - -const preHook = { - command: `node ${hookScript} pre` -}; - -const postHook = { - command: `node ${hookScript} post` -}; - -// Add pre-tool-use hook -if (!settings.hooks['pre-tool-use']) { - settings.hooks['pre-tool-use'] = []; -} -if (!settings.hooks['pre-tool-use'].some(h => h.command && h.command.includes('claude-hook'))) { - settings.hooks['pre-tool-use'].push(preHook); -} - -// Add post-tool-use hook -if (!settings.hooks['post-tool-use']) { - settings.hooks['post-tool-use'] = []; -} -if (!settings.hooks['post-tool-use'].some(h => h.command && h.command.includes('claude-hook'))) { - settings.hooks['post-tool-use'].push(postHook); -} - -// Save settings -try { - fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2)); - console.log('✅ Claude Code hooks configured\n'); -} catch (error) { - console.error('❌ Failed to save settings:', error.message); - process.exit(1); -} - -// Step 4: Create initial PROJECT_INDEX.json if in a project -const cwd = process.cwd(); -const indexFile = path.join(cwd, 'PROJECT_INDEX.json'); - -if (!fs.existsSync(indexFile)) { - // Check if we're in a git repository or have package.json - const isProject = fs.existsSync(path.join(cwd, '.git')) || - fs.existsSync(path.join(cwd, 'package.json')) || - fs.existsSync(path.join(cwd, 'go.mod')) || - fs.existsSync(path.join(cwd, 'requirements.txt')); - - if (isProject) { - console.log('📂 Initializing project index...'); - try { - execSync('indexer init --quiet', { stdio: 'inherit' }); - console.log('✅ Project index created\n'); - } catch (error) { - console.warn('⚠️ Could not create initial index (run "indexer init" manually)'); - } - } -} - -// Step 5: Show success message -console.log('========================================'); -console.log('🎉 Installation Complete!'); -console.log('========================================\n'); -console.log('Quick Start:'); -console.log(' indexer init # Initialize in current project'); -console.log(' indexer scan # Scan project files'); -console.log(' indexer watch # Watch for changes'); -console.log(' indexer query # Search the index'); -console.log(' indexer help # Show all commands\n'); -console.log('Claude Code Integration:'); -console.log(` Hooks configured in: ${SETTINGS_FILE}`); -console.log(` Hook script: ${hookScript}\n`); - -// Step 6: Version check -try { - const version = execSync('indexer --version', { encoding: 'utf8' }).trim(); - console.log(`Installed: ${version}`); -} catch { - // Version command might not exist -} - -console.log('\nDocumentation: https://github.com/clone-global/indexer'); +#!/usr/bin/env node + +/** + * One-line installer for @cloneglobal/indexer + * Usage: curl -fsSL https://raw.githubusercontent.com/clone-global/indexer/main/install.js | node + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); +const os = require('os'); + +const PACKAGE_NAME = '@cloneglobal/indexer'; +const CONFIG_DIR = path.join(os.homedir(), '.config', 'claude'); +const SETTINGS_FILE = path.join(CONFIG_DIR, 'settings.json'); + +console.log('Installing @cloneglobal/indexer...\n'); + +// Step 1: Check Node.js version +const nodeVersion = process.version; +const majorVersion = parseInt(nodeVersion.split('.')[0].substring(1)); +if (majorVersion < 16) { + console.error(`Error: Node.js 16+ required (current: ${nodeVersion})`); + process.exit(1); +} + +// Step 2: Install package globally +try { + console.log('📦 Installing package globally...'); + + // Detect package manager + let packageManager = 'npm'; + try { + execSync('yarn --version', { stdio: 'ignore' }); + packageManager = 'yarn'; + } catch { + // npm is default + } + + if (packageManager === 'yarn') { + execSync(`yarn global add ${PACKAGE_NAME}`, { stdio: 'inherit' }); + } else { + execSync(`npm install -g ${PACKAGE_NAME}`, { stdio: 'inherit' }); + } + + console.log('✅ Package installed successfully\n'); +} catch (error) { + console.error('❌ Failed to install package:', error.message); + process.exit(1); +} + +// Step 3: Configure Claude Code hooks +console.log('🔧 Configuring Claude Code hooks...'); + +// Ensure config directory exists +if (!fs.existsSync(CONFIG_DIR)) { + fs.mkdirSync(CONFIG_DIR, { recursive: true }); +} + +// Load or create settings +let settings = {}; +if (fs.existsSync(SETTINGS_FILE)) { + try { + const content = fs.readFileSync(SETTINGS_FILE, 'utf8'); + settings = JSON.parse(content); + } catch (error) { + console.warn('⚠️ Could not parse existing settings.json, creating new one'); + } +} + +// Find indexer installation path +let indexerPath; +try { + if (packageManager === 'yarn') { + const globalDir = execSync('yarn global dir', { encoding: 'utf8' }).trim(); + indexerPath = path.join(globalDir, 'node_modules', PACKAGE_NAME); + } else { + const npmRoot = execSync('npm root -g', { encoding: 'utf8' }).trim(); + indexerPath = path.join(npmRoot, PACKAGE_NAME); + } +} catch (error) { + console.error('❌ Could not find indexer installation path'); + process.exit(1); +} + +const hookScript = path.join(indexerPath, 'dist', 'hooks', 'claude-hook.js'); + +// Configure hooks +if (!settings.hooks) { + settings.hooks = {}; +} + +const preHook = { + command: `node ${hookScript} pre` +}; + +const postHook = { + command: `node ${hookScript} post` +}; + +// Add pre-tool-use hook +if (!settings.hooks['pre-tool-use']) { + settings.hooks['pre-tool-use'] = []; +} +if (!settings.hooks['pre-tool-use'].some(h => h.command && h.command.includes('claude-hook'))) { + settings.hooks['pre-tool-use'].push(preHook); +} + +// Add post-tool-use hook +if (!settings.hooks['post-tool-use']) { + settings.hooks['post-tool-use'] = []; +} +if (!settings.hooks['post-tool-use'].some(h => h.command && h.command.includes('claude-hook'))) { + settings.hooks['post-tool-use'].push(postHook); +} + +// Save settings +try { + fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2)); + console.log('✅ Claude Code hooks configured\n'); +} catch (error) { + console.error('❌ Failed to save settings:', error.message); + process.exit(1); +} + +// Step 4: Create initial PROJECT_INDEX.json if in a project +const cwd = process.cwd(); +const indexFile = path.join(cwd, 'PROJECT_INDEX.json'); + +if (!fs.existsSync(indexFile)) { + // Check if we're in a git repository or have package.json + const isProject = fs.existsSync(path.join(cwd, '.git')) || + fs.existsSync(path.join(cwd, 'package.json')) || + fs.existsSync(path.join(cwd, 'go.mod')) || + fs.existsSync(path.join(cwd, 'requirements.txt')); + + if (isProject) { + console.log('📂 Initializing project index...'); + try { + execSync('indexer init --quiet', { stdio: 'inherit' }); + console.log('✅ Project index created\n'); + } catch (error) { + console.warn('⚠️ Could not create initial index (run "indexer init" manually)'); + } + } +} + +// Step 5: Show success message +console.log('========================================'); +console.log('🎉 Installation Complete!'); +console.log('========================================\n'); +console.log('Quick Start:'); +console.log(' indexer init # Initialize in current project'); +console.log(' indexer scan # Scan project files'); +console.log(' indexer watch # Watch for changes'); +console.log(' indexer query # Search the index'); +console.log(' indexer help # Show all commands\n'); +console.log('Claude Code Integration:'); +console.log(` Hooks configured in: ${SETTINGS_FILE}`); +console.log(` Hook script: ${hookScript}\n`); + +// Step 6: Version check +try { + const version = execSync('indexer --version', { encoding: 'utf8' }).trim(); + console.log(`Installed: ${version}`); +} catch { + // Version command might not exist +} + +console.log('\nDocumentation: https://github.com/clone-global/indexer'); console.log('Report issues: https://github.com/clone-global/indexer/issues\n'); \ No newline at end of file diff --git a/scripts/jest.config.js b/scripts/jest.config.js index 6f126cc..808370e 100644 --- a/scripts/jest.config.js +++ b/scripts/jest.config.js @@ -1,40 +1,40 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - roots: ['/src', '/test'], - testMatch: [ - '**/__tests__/**/*.+(ts|tsx|js)', - '**/?(*.)+(spec|test).+(ts|tsx|js)' - ], - transform: { - '^.+\\.(ts|tsx)$': 'ts-jest' - }, - collectCoverageFrom: [ - 'src/**/*.{ts,tsx}', - '!src/**/*.d.ts', - '!src/**/index.ts', - '!src/types/**' - ], - coverageDirectory: 'coverage', - coverageReporters: ['text', 'lcov', 'html'], - coverageThreshold: { - global: { - branches: 70, - functions: 80, - lines: 80, - statements: 80 - } - }, - moduleNameMapper: { - '^@/(.*)$': '/src/$1', - '^@parsers/(.*)$': '/src/parsers/$1', - '^@core/(.*)$': '/src/core/$1', - '^@utils/(.*)$': '/src/utils/$1', - '^@hooks/(.*)$': '/src/hooks/$1', - '^@cli/(.*)$': '/src/cli/$1' - }, - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - verbose: true, - testTimeout: 10000, - setupFilesAfterEnv: ['/test/setup.ts'] +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src', '/test'], + testMatch: [ + '**/__tests__/**/*.+(ts|tsx|js)', + '**/?(*.)+(spec|test).+(ts|tsx|js)' + ], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest' + }, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/**/index.ts', + '!src/types/**' + ], + coverageDirectory: 'coverage', + coverageReporters: ['text', 'lcov', 'html'], + coverageThreshold: { + global: { + branches: 70, + functions: 80, + lines: 80, + statements: 80 + } + }, + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + '^@parsers/(.*)$': '/src/parsers/$1', + '^@core/(.*)$': '/src/core/$1', + '^@utils/(.*)$': '/src/utils/$1', + '^@hooks/(.*)$': '/src/hooks/$1', + '^@cli/(.*)$': '/src/cli/$1' + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + verbose: true, + testTimeout: 10000, + setupFilesAfterEnv: ['/test/setup.ts'] }; \ No newline at end of file diff --git a/scripts/organize-outputs.sh b/scripts/organize-outputs.sh index 04f1566..b116e6f 100644 --- a/scripts/organize-outputs.sh +++ b/scripts/organize-outputs.sh @@ -1,57 +1,57 @@ -#!/bin/bash - -# Script to organize existing indexer output files into the new directory structure -# Usage: ./scripts/organize-outputs.sh - -set -e - -REPO_ROOT=".." # Relative to indexer/scripts directory when run from indexer -OUTPUT_DIR=".indexer-output" - -echo "🔄 Organizing indexer output files..." - -# Create the new directory structure -mkdir -p "$REPO_ROOT/$OUTPUT_DIR/indexes" -mkdir -p "$REPO_ROOT/$OUTPUT_DIR/docs" -mkdir -p "$REPO_ROOT/$OUTPUT_DIR/visualizations" -mkdir -p "$REPO_ROOT/$OUTPUT_DIR/viewers" -mkdir -p "$REPO_ROOT/$OUTPUT_DIR/indexes/archive" - -# Function to move file if it exists -move_if_exists() { - local source="$1" - local dest="$2" - if [ -f "$REPO_ROOT/$source" ]; then - echo " Moving $source to $dest" - mv "$REPO_ROOT/$source" "$REPO_ROOT/$OUTPUT_DIR/$dest" - fi -} - -# Move index files -move_if_exists "PROJECT_INDEX.json" "indexes/PROJECT_INDEX.json" - -# Move documentation files -move_if_exists "CODE_INDEX.md" "docs/CODE_INDEX.md" -move_if_exists "project-tree.txt" "docs/project-tree.txt" -move_if_exists "project-table.txt" "docs/project-table.txt" - -# Move visualization files -move_if_exists "dependencies.dot" "visualizations/dependencies.dot" -move_if_exists "dependencies.mmd" "visualizations/dependencies.mmd" -move_if_exists "dependencies.png" "visualizations/dependencies.png" -move_if_exists "service-boxes.txt" "visualizations/service-boxes.txt" - -# Move HTML viewers -move_if_exists "components.html" "viewers/components.html" -move_if_exists "frontend-only.html" "viewers/frontend-only.html" -move_if_exists "mermaid-viewer.html" "viewers/mermaid-viewer.html" -move_if_exists "viewer-limited.html" "viewers/viewer-limited.html" - -echo "✅ Files organized successfully!" -echo "" -echo "📁 New structure created at: $REPO_ROOT/$OUTPUT_DIR/" -echo "" -echo "Don't forget to:" -echo " 1. Update your .gitignore to include: $OUTPUT_DIR/" -echo " 2. Update any scripts that reference the old file locations" +#!/bin/bash + +# Script to organize existing indexer output files into the new directory structure +# Usage: ./scripts/organize-outputs.sh + +set -e + +REPO_ROOT=".." # Relative to indexer/scripts directory when run from indexer +OUTPUT_DIR=".indexer-output" + +echo "🔄 Organizing indexer output files..." + +# Create the new directory structure +mkdir -p "$REPO_ROOT/$OUTPUT_DIR/indexes" +mkdir -p "$REPO_ROOT/$OUTPUT_DIR/docs" +mkdir -p "$REPO_ROOT/$OUTPUT_DIR/visualizations" +mkdir -p "$REPO_ROOT/$OUTPUT_DIR/viewers" +mkdir -p "$REPO_ROOT/$OUTPUT_DIR/indexes/archive" + +# Function to move file if it exists +move_if_exists() { + local source="$1" + local dest="$2" + if [ -f "$REPO_ROOT/$source" ]; then + echo " Moving $source to $dest" + mv "$REPO_ROOT/$source" "$REPO_ROOT/$OUTPUT_DIR/$dest" + fi +} + +# Move index files +move_if_exists "PROJECT_INDEX.json" "indexes/PROJECT_INDEX.json" + +# Move documentation files +move_if_exists "CODE_INDEX.md" "docs/CODE_INDEX.md" +move_if_exists "project-tree.txt" "docs/project-tree.txt" +move_if_exists "project-table.txt" "docs/project-table.txt" + +# Move visualization files +move_if_exists "dependencies.dot" "visualizations/dependencies.dot" +move_if_exists "dependencies.mmd" "visualizations/dependencies.mmd" +move_if_exists "dependencies.png" "visualizations/dependencies.png" +move_if_exists "service-boxes.txt" "visualizations/service-boxes.txt" + +# Move HTML viewers +move_if_exists "components.html" "viewers/components.html" +move_if_exists "frontend-only.html" "viewers/frontend-only.html" +move_if_exists "mermaid-viewer.html" "viewers/mermaid-viewer.html" +move_if_exists "viewer-limited.html" "viewers/viewer-limited.html" + +echo "✅ Files organized successfully!" +echo "" +echo "📁 New structure created at: $REPO_ROOT/$OUTPUT_DIR/" +echo "" +echo "Don't forget to:" +echo " 1. Update your .gitignore to include: $OUTPUT_DIR/" +echo " 2. Update any scripts that reference the old file locations" echo " 3. Commit the changes" \ No newline at end of file diff --git a/scripts/postinstall.js b/scripts/postinstall.js index db579bf..cdacc8d 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -1,11 +1,11 @@ -#!/usr/bin/env node - -/** - * Postinstall script for @cloneglobal/indexer - * This script runs after npm/yarn install - */ - -console.log('✓ @cloneglobal/indexer installed successfully'); - -// Exit successfully +#!/usr/bin/env node + +/** + * Postinstall script for @cloneglobal/indexer + * This script runs after npm/yarn install + */ + +console.log('✓ @cloneglobal/indexer installed successfully'); + +// Exit successfully process.exit(0); \ No newline at end of file diff --git a/scripts/replace-console-logs.ts b/scripts/replace-console-logs.ts index f6f1b74..f40854c 100644 --- a/scripts/replace-console-logs.ts +++ b/scripts/replace-console-logs.ts @@ -1,213 +1,213 @@ -#!/usr/bin/env ts-node -/** - * Script to replace console.log statements with proper logger calls - * This will replace 408+ console statements across the codebase - */ - -import * as fs from 'fs'; -import * as path from 'path'; -import { glob } from 'glob'; - -interface Replacement { - pattern: RegExp; - replacement: string; -} - -const replacements: Replacement[] = [ - // Basic console methods - { pattern: /console\.log\(/g, replacement: 'logger.info(' }, - { pattern: /console\.error\(/g, replacement: 'logger.error(' }, - { pattern: /console\.warn\(/g, replacement: 'logger.warn(' }, - { pattern: /console\.debug\(/g, replacement: 'logger.debug(' }, - { pattern: /console\.info\(/g, replacement: 'logger.info(' }, - { pattern: /console\.trace\(/g, replacement: 'logger.debug(' }, - - // Table and other console methods - { pattern: /console\.table\(/g, replacement: 'logger.info(' }, - { pattern: /console\.dir\(/g, replacement: 'logger.debug(' }, - { pattern: /console\.time\(/g, replacement: '// logger.time(' }, - { pattern: /console\.timeEnd\(/g, replacement: '// logger.timeEnd(' }, - - // Success patterns - detect success messages - { pattern: /logger\.info\((['"`])✓/g, replacement: 'logger.success($1' }, - { pattern: /logger\.info\((['"`])✅/g, replacement: 'logger.success($1' }, - { pattern: /logger\.info\((['"`])Success/gi, replacement: 'logger.success($1Success' }, - { pattern: /logger\.info\((['"`])Complete/gi, replacement: 'logger.success($1Complete' }, - - // Warning patterns - { pattern: /logger\.info\((['"`])⚠/g, replacement: 'logger.warn($1' }, - { pattern: /logger\.info\((['"`])Warning/gi, replacement: 'logger.warn($1Warning' }, - { pattern: /logger\.info\((['"`])Skipping/gi, replacement: 'logger.warn($1Skipping' }, - - // Error patterns - { pattern: /logger\.info\((['"`])❌/g, replacement: 'logger.error($1' }, - { pattern: /logger\.info\((['"`])Error/gi, replacement: 'logger.error($1Error' }, - { pattern: /logger\.info\((['"`])Failed/gi, replacement: 'logger.error($1Failed' }, - - // Debug patterns - { pattern: /logger\.info\((['"`])Debug/gi, replacement: 'logger.debug($1Debug' }, - { pattern: /logger\.info\((['"`])\[DEBUG\]/gi, replacement: 'logger.debug($1[DEBUG]' }, -]; - -// Use singleton logger pattern -const importStatement = "import logger from '../utils/logger';"; -// For module-specific loggers -const childLoggerDeclaration = "const moduleLogger = logger.child('{{PREFIX}}');"; - -async function processFile(filePath: string): Promise { - let content = await fs.promises.readFile(filePath, 'utf-8'); - const originalContent = content; - let replacementCount = 0; - - // Skip if already has logger imported (check various patterns) - const hasLoggerImport = content.includes("from '../utils/logger'") || - content.includes('from "./utils/logger"') || - content.includes("from '@/utils/logger'") || - content.includes("from 'src/utils/logger'") || - content.includes('utils/logger'); - - if (hasLoggerImport) { - // Still check for console statements that need replacement - const consoleMatches = content.match(/console\.(log|error|warn|debug|info|trace|table|dir)/g); - if (!consoleMatches) { - return 0; - } - } - - // Check if file uses console methods - const consoleUsage = content.match(/console\.(log|error|warn|debug|info|trace|table|dir)/g); - if (!consoleUsage) { - return 0; - } - - // Apply replacements - for (const { pattern, replacement } of replacements) { - const matches = content.match(pattern); - if (matches) { - replacementCount += matches.length; - content = content.replace(pattern, replacement); - } - } - - if (replacementCount > 0) { - // Only add import if not already present - if (!hasLoggerImport) { - // Calculate relative path from current file to utils/logger - const fileDepth = filePath.split('/').filter(p => p && p !== '.' && p !== '..').length; - const srcIndex = filePath.split('/').indexOf('src'); - const depthFromSrc = fileDepth - srcIndex - 1; - const backPath = '../'.repeat(depthFromSrc); - const importPath = backPath + 'utils/logger'; - const adjustedImport = importStatement.replace('../utils/logger', importPath); - - // Find the best place to insert the import - const lines = content.split('\n'); - let importInsertIndex = 0; - let lastImportIndex = -1; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (line.startsWith('import ') || line.includes(' from ')) { - lastImportIndex = i; - } else if (line.startsWith('const ') && line.includes('require(')) { - lastImportIndex = i; - } else if (lastImportIndex >= 0 && line.trim() === '') { - // Found empty line after imports - importInsertIndex = i; - break; - } else if (lastImportIndex >= 0 && !line.startsWith('import') && !line.includes('require')) { - // Found first non-import line - importInsertIndex = lastImportIndex + 1; - break; - } - } - - // Insert the import - if (importInsertIndex > 0) { - lines.splice(importInsertIndex, 0, adjustedImport); - } else { - // Add at the beginning, respecting shebang - if (lines[0].startsWith('#!')) { - lines.splice(1, 0, adjustedImport, ''); - } else { - lines.unshift(adjustedImport, ''); - } - } - - content = lines.join('\n'); - } - - // Write updated content - await fs.promises.writeFile(filePath, content, 'utf-8'); - console.log(`✅ Updated ${filePath} - ${replacementCount} replacements`); - } - - return replacementCount; -} - -async function main() { - console.log('🔍 Scanning for console statements to replace with logger...\n'); - - const srcFiles = await glob('src/**/*.{ts,js,tsx,jsx}', { - ignore: [ - '**/node_modules/**', - '**/dist/**', - '**/*.test.ts', - '**/*.spec.ts', - '**/utils/logger.ts', - '**/__tests__/**', - '**/__mocks__/**' - ] - }); - - console.log(`📁 Found ${srcFiles.length} source files to process`); - - let totalReplacements = 0; - let filesUpdated = 0; - let filesSkipped = 0; - const updatedFiles: string[] = []; - - for (const file of srcFiles) { - const replacements = await processFile(file); - if (replacements > 0) { - totalReplacements += replacements; - filesUpdated++; - updatedFiles.push(file); - } else { - filesSkipped++; - } - } - - console.log('\n' + '='.repeat(60)); - console.log(`📊 REPLACEMENT SUMMARY`); - console.log('='.repeat(60)); - console.log(`✅ Files updated: ${filesUpdated}`); - console.log(`⏭️ Files skipped: ${filesSkipped}`); - console.log(`🔄 Total replacements: ${totalReplacements}`); - console.log(`📈 Average replacements per file: ${filesUpdated > 0 ? Math.round(totalReplacements / filesUpdated) : 0}`); - - if (updatedFiles.length > 0 && updatedFiles.length <= 10) { - console.log('\n📝 Updated files:'); - updatedFiles.forEach(f => console.log(` - ${f}`)); - } - - // Save detailed report - const report = { - timestamp: new Date().toISOString(), - filesScanned: srcFiles.length, - filesUpdated, - filesSkipped, - totalReplacements, - updatedFiles - }; - - await fs.promises.writeFile( - 'console-replacement-report.json', - JSON.stringify(report, null, 2) - ); - - console.log('\n📄 Detailed report saved to: console-replacement-report.json'); - console.log('✨ Console.log replacement complete!'); -} - +#!/usr/bin/env ts-node +/** + * Script to replace console.log statements with proper logger calls + * This will replace 408+ console statements across the codebase + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import { glob } from 'glob'; + +interface Replacement { + pattern: RegExp; + replacement: string; +} + +const replacements: Replacement[] = [ + // Basic console methods + { pattern: /console\.log\(/g, replacement: 'logger.info(' }, + { pattern: /console\.error\(/g, replacement: 'logger.error(' }, + { pattern: /console\.warn\(/g, replacement: 'logger.warn(' }, + { pattern: /console\.debug\(/g, replacement: 'logger.debug(' }, + { pattern: /console\.info\(/g, replacement: 'logger.info(' }, + { pattern: /console\.trace\(/g, replacement: 'logger.debug(' }, + + // Table and other console methods + { pattern: /console\.table\(/g, replacement: 'logger.info(' }, + { pattern: /console\.dir\(/g, replacement: 'logger.debug(' }, + { pattern: /console\.time\(/g, replacement: '// logger.time(' }, + { pattern: /console\.timeEnd\(/g, replacement: '// logger.timeEnd(' }, + + // Success patterns - detect success messages + { pattern: /logger\.info\((['"`])✓/g, replacement: 'logger.success($1' }, + { pattern: /logger\.info\((['"`])✅/g, replacement: 'logger.success($1' }, + { pattern: /logger\.info\((['"`])Success/gi, replacement: 'logger.success($1Success' }, + { pattern: /logger\.info\((['"`])Complete/gi, replacement: 'logger.success($1Complete' }, + + // Warning patterns + { pattern: /logger\.info\((['"`])⚠/g, replacement: 'logger.warn($1' }, + { pattern: /logger\.info\((['"`])Warning/gi, replacement: 'logger.warn($1Warning' }, + { pattern: /logger\.info\((['"`])Skipping/gi, replacement: 'logger.warn($1Skipping' }, + + // Error patterns + { pattern: /logger\.info\((['"`])❌/g, replacement: 'logger.error($1' }, + { pattern: /logger\.info\((['"`])Error/gi, replacement: 'logger.error($1Error' }, + { pattern: /logger\.info\((['"`])Failed/gi, replacement: 'logger.error($1Failed' }, + + // Debug patterns + { pattern: /logger\.info\((['"`])Debug/gi, replacement: 'logger.debug($1Debug' }, + { pattern: /logger\.info\((['"`])\[DEBUG\]/gi, replacement: 'logger.debug($1[DEBUG]' }, +]; + +// Use singleton logger pattern +const importStatement = "import logger from '../utils/logger';"; +// For module-specific loggers +const childLoggerDeclaration = "const moduleLogger = logger.child('{{PREFIX}}');"; + +async function processFile(filePath: string): Promise { + let content = await fs.promises.readFile(filePath, 'utf-8'); + const originalContent = content; + let replacementCount = 0; + + // Skip if already has logger imported (check various patterns) + const hasLoggerImport = content.includes("from '../utils/logger'") || + content.includes('from "./utils/logger"') || + content.includes("from '@/utils/logger'") || + content.includes("from 'src/utils/logger'") || + content.includes('utils/logger'); + + if (hasLoggerImport) { + // Still check for console statements that need replacement + const consoleMatches = content.match(/console\.(log|error|warn|debug|info|trace|table|dir)/g); + if (!consoleMatches) { + return 0; + } + } + + // Check if file uses console methods + const consoleUsage = content.match(/console\.(log|error|warn|debug|info|trace|table|dir)/g); + if (!consoleUsage) { + return 0; + } + + // Apply replacements + for (const { pattern, replacement } of replacements) { + const matches = content.match(pattern); + if (matches) { + replacementCount += matches.length; + content = content.replace(pattern, replacement); + } + } + + if (replacementCount > 0) { + // Only add import if not already present + if (!hasLoggerImport) { + // Calculate relative path from current file to utils/logger + const fileDepth = filePath.split('/').filter(p => p && p !== '.' && p !== '..').length; + const srcIndex = filePath.split('/').indexOf('src'); + const depthFromSrc = fileDepth - srcIndex - 1; + const backPath = '../'.repeat(depthFromSrc); + const importPath = backPath + 'utils/logger'; + const adjustedImport = importStatement.replace('../utils/logger', importPath); + + // Find the best place to insert the import + const lines = content.split('\n'); + let importInsertIndex = 0; + let lastImportIndex = -1; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.startsWith('import ') || line.includes(' from ')) { + lastImportIndex = i; + } else if (line.startsWith('const ') && line.includes('require(')) { + lastImportIndex = i; + } else if (lastImportIndex >= 0 && line.trim() === '') { + // Found empty line after imports + importInsertIndex = i; + break; + } else if (lastImportIndex >= 0 && !line.startsWith('import') && !line.includes('require')) { + // Found first non-import line + importInsertIndex = lastImportIndex + 1; + break; + } + } + + // Insert the import + if (importInsertIndex > 0) { + lines.splice(importInsertIndex, 0, adjustedImport); + } else { + // Add at the beginning, respecting shebang + if (lines[0].startsWith('#!')) { + lines.splice(1, 0, adjustedImport, ''); + } else { + lines.unshift(adjustedImport, ''); + } + } + + content = lines.join('\n'); + } + + // Write updated content + await fs.promises.writeFile(filePath, content, 'utf-8'); + console.log(`✅ Updated ${filePath} - ${replacementCount} replacements`); + } + + return replacementCount; +} + +async function main() { + console.log('🔍 Scanning for console statements to replace with logger...\n'); + + const srcFiles = await glob('src/**/*.{ts,js,tsx,jsx}', { + ignore: [ + '**/node_modules/**', + '**/dist/**', + '**/*.test.ts', + '**/*.spec.ts', + '**/utils/logger.ts', + '**/__tests__/**', + '**/__mocks__/**' + ] + }); + + console.log(`📁 Found ${srcFiles.length} source files to process`); + + let totalReplacements = 0; + let filesUpdated = 0; + let filesSkipped = 0; + const updatedFiles: string[] = []; + + for (const file of srcFiles) { + const replacements = await processFile(file); + if (replacements > 0) { + totalReplacements += replacements; + filesUpdated++; + updatedFiles.push(file); + } else { + filesSkipped++; + } + } + + console.log('\n' + '='.repeat(60)); + console.log(`📊 REPLACEMENT SUMMARY`); + console.log('='.repeat(60)); + console.log(`✅ Files updated: ${filesUpdated}`); + console.log(`⏭️ Files skipped: ${filesSkipped}`); + console.log(`🔄 Total replacements: ${totalReplacements}`); + console.log(`📈 Average replacements per file: ${filesUpdated > 0 ? Math.round(totalReplacements / filesUpdated) : 0}`); + + if (updatedFiles.length > 0 && updatedFiles.length <= 10) { + console.log('\n📝 Updated files:'); + updatedFiles.forEach(f => console.log(` - ${f}`)); + } + + // Save detailed report + const report = { + timestamp: new Date().toISOString(), + filesScanned: srcFiles.length, + filesUpdated, + filesSkipped, + totalReplacements, + updatedFiles + }; + + await fs.promises.writeFile( + 'console-replacement-report.json', + JSON.stringify(report, null, 2) + ); + + console.log('\n📄 Detailed report saved to: console-replacement-report.json'); + console.log('✨ Console.log replacement complete!'); +} + main().catch(console.error); \ No newline at end of file diff --git a/scripts/run-indexer.sh b/scripts/run-indexer.sh index e39c233..fe3f90c 100644 --- a/scripts/run-indexer.sh +++ b/scripts/run-indexer.sh @@ -1,9 +1,9 @@ -#!/bin/bash -# Run the indexer using ts-node to bypass build issues - -# Load environment variables -export NODE_ENV=development - -# Run with ts-node in transpile-only mode for performance -# This bypasses TypeScript errors while still running the code +#!/bin/bash +# Run the indexer using ts-node to bypass build issues + +# Load environment variables +export NODE_ENV=development + +# Run with ts-node in transpile-only mode for performance +# This bypasses TypeScript errors while still running the code npx ts-node --transpile-only src/cli/index.ts "$@" \ No newline at end of file diff --git a/scripts/test-output-structure.sh b/scripts/test-output-structure.sh index 972d961..bf14636 100644 --- a/scripts/test-output-structure.sh +++ b/scripts/test-output-structure.sh @@ -1,64 +1,64 @@ -#!/bin/bash - -# Test script to verify new output structure - -echo "🧪 Testing new output structure..." - -# Build the indexer -echo "📦 Building indexer..." -npm run build - -# Run the indexer -echo "🚀 Running indexer..." -./bin/indexer.js init - -# Check if outputs were created correctly -echo "✅ Checking output structure..." - -if [ -d ".indexer-output/current" ]; then - echo "✓ Current directory exists" -else - echo "✗ Current directory not found" - exit 1 -fi - -if [ -f ".indexer-output/current/index.json" ]; then - echo "✓ Main index.json exists" -else - echo "✗ index.json not found" - exit 1 -fi - -if [ -f ".indexer-output/current/dashboard.html" ]; then - echo "✓ Dashboard exists" -else - echo "✗ Dashboard not found" - exit 1 -fi - -# Check if old structure exists (files directly in .indexer-output) -if [ -f ".indexer-output/PROJECT_INDEX.json" ] || [ -f ".indexer-output/dashboard.html" ]; then - echo "📋 Old structure detected - will be migrated on next run" -fi - -# Run again to test archiving -echo "🗄️ Testing archive functionality (running again)..." -./bin/indexer.js init - -if [ -d ".indexer-output/archive" ]; then - echo "✓ Archive directory created" - ARCHIVE_COUNT=$(ls -1 .indexer-output/archive 2>/dev/null | wc -l) - echo " Archives: $ARCHIVE_COUNT" - if [ $ARCHIVE_COUNT -gt 0 ]; then - echo " Latest archive: $(ls -t .indexer-output/archive | head -1)" - fi -else - echo "⚠️ Archive directory not found (may not have had previous outputs)" -fi - -echo "" -echo "📊 Output structure:" -tree .indexer-output -L 2 2>/dev/null || ls -la .indexer-output/ - -echo "" +#!/bin/bash + +# Test script to verify new output structure + +echo "🧪 Testing new output structure..." + +# Build the indexer +echo "📦 Building indexer..." +npm run build + +# Run the indexer +echo "🚀 Running indexer..." +./bin/indexer.js init + +# Check if outputs were created correctly +echo "✅ Checking output structure..." + +if [ -d ".indexer-output/current" ]; then + echo "✓ Current directory exists" +else + echo "✗ Current directory not found" + exit 1 +fi + +if [ -f ".indexer-output/current/index.json" ]; then + echo "✓ Main index.json exists" +else + echo "✗ index.json not found" + exit 1 +fi + +if [ -f ".indexer-output/current/dashboard.html" ]; then + echo "✓ Dashboard exists" +else + echo "✗ Dashboard not found" + exit 1 +fi + +# Check if old structure exists (files directly in .indexer-output) +if [ -f ".indexer-output/PROJECT_INDEX.json" ] || [ -f ".indexer-output/dashboard.html" ]; then + echo "📋 Old structure detected - will be migrated on next run" +fi + +# Run again to test archiving +echo "🗄️ Testing archive functionality (running again)..." +./bin/indexer.js init + +if [ -d ".indexer-output/archive" ]; then + echo "✓ Archive directory created" + ARCHIVE_COUNT=$(ls -1 .indexer-output/archive 2>/dev/null | wc -l) + echo " Archives: $ARCHIVE_COUNT" + if [ $ARCHIVE_COUNT -gt 0 ]; then + echo " Latest archive: $(ls -t .indexer-output/archive | head -1)" + fi +else + echo "⚠️ Archive directory not found (may not have had previous outputs)" +fi + +echo "" +echo "📊 Output structure:" +tree .indexer-output -L 2 2>/dev/null || ls -la .indexer-output/ + +echo "" echo "✨ Test complete!" \ No newline at end of file diff --git a/scripts/verify-installation.sh b/scripts/verify-installation.sh index 6e8d9e5..02e2d77 100644 --- a/scripts/verify-installation.sh +++ b/scripts/verify-installation.sh @@ -1,75 +1,75 @@ -#!/bin/bash -# Verification script for Clone Global Indexer - -echo "🔧 Verifying Clone Global Indexer Installation" -echo "==============================================" - -# Check if running from correct location -if [ ! -d "indexer" ]; then - echo "❌ Error: Please run this from the clone-global root directory" - exit 1 -fi - -echo "✅ Running from correct directory" - -# Check if .env.local exists -if [ -f "indexer/.env.local" ]; then - echo "✅ Environment file found (.env.local)" -else - echo "⚠️ Warning: No .env.local file found (AI features may be limited)" -fi - -# Check if node is installed -if command -v node &> /dev/null; then - NODE_VERSION=$(node -v) - echo "✅ Node.js installed: $NODE_VERSION" -else - echo "❌ Node.js not found. Please install Node.js 16 or higher" - exit 1 -fi - -# Check if dependencies are installed -if [ -d "indexer/node_modules" ]; then - echo "✅ Dependencies installed" -else - echo "⚠️ Dependencies not installed. Running npm install..." - cd indexer && npm install && cd .. -fi - -# Test basic indexer functionality -echo "" -echo "📊 Testing indexer with a small directory..." -echo "---------------------------------------------" - -# Create a test directory with sample files -TEST_DIR="/tmp/indexer-test-$$" -mkdir -p "$TEST_DIR" -echo "function testFunction() { return true; }" > "$TEST_DIR/test.js" -echo "class TestClass: pass" > "$TEST_DIR/test.py" -echo "package main" > "$TEST_DIR/test.go" - -# Run the indexer on test directory -npx ts-node --transpile-only indexer/src/cli/index.ts scan "$TEST_DIR" --quiet - -# Check if index was created -if [ -f ".indexer-output/indexes/PROJECT_INDEX.json" ]; then - echo "✅ Indexer working! Successfully created PROJECT_INDEX.json" - INDEX_SIZE=$(du -h .indexer-output/indexes/PROJECT_INDEX.json | cut -f1) - echo " Index size: $INDEX_SIZE" -else - echo "❌ Index file not created. There may be an issue." - exit 1 -fi - -# Clean up test directory -rm -rf "$TEST_DIR" - -echo "" -echo "🎉 Verification complete! The indexer is ready to use." -echo "" -echo "Quick start commands:" -echo " • Scan entire project: npx ts-node indexer/src/cli/index.ts scan ." -echo " • View statistics: npx ts-node indexer/src/cli/index.ts stats" -echo " • Search for code: npx ts-node indexer/src/cli/index.ts query \"functionName\"" -echo "" +#!/bin/bash +# Verification script for Clone Global Indexer + +echo "🔧 Verifying Clone Global Indexer Installation" +echo "==============================================" + +# Check if running from correct location +if [ ! -d "indexer" ]; then + echo "❌ Error: Please run this from the clone-global root directory" + exit 1 +fi + +echo "✅ Running from correct directory" + +# Check if .env.local exists +if [ -f "indexer/.env.local" ]; then + echo "✅ Environment file found (.env.local)" +else + echo "⚠️ Warning: No .env.local file found (AI features may be limited)" +fi + +# Check if node is installed +if command -v node &> /dev/null; then + NODE_VERSION=$(node -v) + echo "✅ Node.js installed: $NODE_VERSION" +else + echo "❌ Node.js not found. Please install Node.js 16 or higher" + exit 1 +fi + +# Check if dependencies are installed +if [ -d "indexer/node_modules" ]; then + echo "✅ Dependencies installed" +else + echo "⚠️ Dependencies not installed. Running npm install..." + cd indexer && npm install && cd .. +fi + +# Test basic indexer functionality +echo "" +echo "📊 Testing indexer with a small directory..." +echo "---------------------------------------------" + +# Create a test directory with sample files +TEST_DIR="/tmp/indexer-test-$$" +mkdir -p "$TEST_DIR" +echo "function testFunction() { return true; }" > "$TEST_DIR/test.js" +echo "class TestClass: pass" > "$TEST_DIR/test.py" +echo "package main" > "$TEST_DIR/test.go" + +# Run the indexer on test directory +npx ts-node --transpile-only indexer/src/cli/index.ts scan "$TEST_DIR" --quiet + +# Check if index was created +if [ -f ".indexer-output/indexes/PROJECT_INDEX.json" ]; then + echo "✅ Indexer working! Successfully created PROJECT_INDEX.json" + INDEX_SIZE=$(du -h .indexer-output/indexes/PROJECT_INDEX.json | cut -f1) + echo " Index size: $INDEX_SIZE" +else + echo "❌ Index file not created. There may be an issue." + exit 1 +fi + +# Clean up test directory +rm -rf "$TEST_DIR" + +echo "" +echo "🎉 Verification complete! The indexer is ready to use." +echo "" +echo "Quick start commands:" +echo " • Scan entire project: npx ts-node indexer/src/cli/index.ts scan ." +echo " • View statistics: npx ts-node indexer/src/cli/index.ts stats" +echo " • Search for code: npx ts-node indexer/src/cli/index.ts query \"functionName\"" +echo "" echo "📖 Full documentation: indexer/GETTING_STARTED.md" \ No newline at end of file diff --git a/specs/001-fix-typescript-build/contracts/build-validation.yaml b/specs/001-fix-typescript-build/contracts/build-validation.yaml new file mode 100644 index 0000000..0442194 --- /dev/null +++ b/specs/001-fix-typescript-build/contracts/build-validation.yaml @@ -0,0 +1,141 @@ +# Build Validation Contract +# Ensures TypeScript compilation succeeds with zero errors + +openapi: 3.0.0 +info: + title: Build Validation Contract + version: 1.0.0 + description: Contract for validating successful TypeScript compilation + +paths: + /build/validate: + post: + summary: Validate TypeScript build + description: Run TypeScript compiler and verify zero errors + operationId: validateBuild + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - projectPath + - tsConfigPath + properties: + projectPath: + type: string + description: Root path of the project + example: "/mnt/c/projects/git-org/clone-global/indexer" + tsConfigPath: + type: string + description: Path to tsconfig.json + example: "tsconfig.json" + options: + type: object + properties: + incremental: + type: boolean + default: true + noEmit: + type: boolean + default: false + description: Only type-check without emitting files + responses: + '200': + description: Build successful with no errors + content: + application/json: + schema: + type: object + required: + - success + - errorCount + - warningCount + - duration + properties: + success: + type: boolean + enum: [true] + errorCount: + type: integer + enum: [0] + warningCount: + type: integer + minimum: 0 + duration: + type: integer + description: Build time in milliseconds + maximum: 30000 + filesProcessed: + type: integer + minimum: 1 + diagnostics: + type: array + items: + type: object + maxItems: 0 + '422': + description: Build failed with TypeScript errors + content: + application/json: + schema: + type: object + required: + - success + - errorCount + - errors + properties: + success: + type: boolean + enum: [false] + errorCount: + type: integer + minimum: 1 + errors: + type: array + minItems: 1 + items: + type: object + required: + - file + - line + - column + - code + - message + properties: + file: + type: string + line: + type: integer + column: + type: integer + code: + type: string + pattern: "^TS[0-9]+$" + message: + type: string + category: + type: string + enum: + - TYPE_MISMATCH + - MISSING_IMPORT + - MISSING_PROPERTY + - ASYNC_SYNC_CONFLICT + - MISSING_TYPES + +components: + schemas: + BuildResult: + type: object + properties: + success: + type: boolean + errorCount: + type: integer + warningCount: + type: integer + duration: + type: integer + filesProcessed: + type: integer \ No newline at end of file diff --git a/specs/001-fix-typescript-build/contracts/type-coverage.yaml b/specs/001-fix-typescript-build/contracts/type-coverage.yaml new file mode 100644 index 0000000..1f36584 --- /dev/null +++ b/specs/001-fix-typescript-build/contracts/type-coverage.yaml @@ -0,0 +1,163 @@ +# Type Coverage Contract +# Ensures all TypeScript files have proper type coverage + +openapi: 3.0.0 +info: + title: Type Coverage Contract + version: 1.0.0 + description: Contract for validating TypeScript type coverage across the codebase + +paths: + /types/coverage: + get: + summary: Get type coverage statistics + description: Analyze type coverage across all TypeScript files + operationId: getTypeCoverage + parameters: + - name: projectPath + in: query + required: true + schema: + type: string + description: Root path of the project + responses: + '200': + description: Type coverage report + content: + application/json: + schema: + type: object + required: + - overallCoverage + - fileCount + - hasFullCoverage + properties: + overallCoverage: + type: number + minimum: 100 + maximum: 100 + description: Percentage of code with explicit types + fileCount: + type: integer + minimum: 1 + hasFullCoverage: + type: boolean + enum: [true] + byFile: + type: array + items: + type: object + required: + - path + - coverage + - typed + - untyped + properties: + path: + type: string + coverage: + type: number + minimum: 0 + maximum: 100 + typed: + type: integer + untyped: + type: integer + enum: [0] + + /types/validate-definitions: + post: + summary: Validate type definitions + description: Ensure all required @types packages are installed + operationId: validateTypeDefinitions + requestBody: + content: + application/json: + schema: + type: object + required: + - packageJsonPath + properties: + packageJsonPath: + type: string + description: Path to package.json + responses: + '200': + description: All type definitions present + content: + application/json: + schema: + type: object + required: + - allTypesPresent + - installedTypes + - missingTypes + properties: + allTypesPresent: + type: boolean + enum: [true] + installedTypes: + type: array + items: + type: string + minItems: 4 + examples: + - ["@types/jsonwebtoken", "@types/bcrypt", "@types/ws", "@types/morgan"] + missingTypes: + type: array + maxItems: 0 + summary: + type: string + enum: ["All required type definitions are installed"] + + /types/check-imports: + post: + summary: Check import resolution + description: Validate all imports resolve correctly + operationId: checkImports + requestBody: + content: + application/json: + schema: + type: object + required: + - files + properties: + files: + type: array + items: + type: string + responses: + '200': + description: All imports resolve correctly + content: + application/json: + schema: + type: object + required: + - totalImports + - resolvedImports + - brokenImports + - success + properties: + success: + type: boolean + enum: [true] + totalImports: + type: integer + minimum: 1 + resolvedImports: + type: integer + brokenImports: + type: integer + enum: [0] + importMap: + type: object + additionalProperties: + type: object + properties: + resolved: + type: boolean + enum: [true] + path: + type: string \ No newline at end of file diff --git a/specs/001-fix-typescript-build/data-model.md b/specs/001-fix-typescript-build/data-model.md new file mode 100644 index 0000000..19363fe --- /dev/null +++ b/specs/001-fix-typescript-build/data-model.md @@ -0,0 +1,184 @@ +# Data Model: TypeScript Build Error Resolution + +**Date**: 2025-09-16 +**Feature**: Fix TypeScript Build Errors + +## Entities + +### TypeDefinition +Represents a type definition package that provides TypeScript types for JavaScript libraries. + +**Fields**: +- `packageName` (string): NPM package name (e.g., "@types/jsonwebtoken") +- `version` (string): Package version specification (e.g., "^9.0.6") +- `targetLibrary` (string): The JavaScript library it provides types for +- `installType` (enum): DEV_DEPENDENCY | DEPENDENCY +- `status` (enum): MISSING | INSTALLED | OUTDATED + +**Relationships**: +- Used by multiple ImportPath entities +- References TypeSignature entities it provides + +**Validation Rules**: +- Package name must start with "@types/" +- Version must be valid semver +- Must be installed as devDependency for build-time only + +### ImportPath +Represents a module import that needs resolution. + +**Fields**: +- `sourcePath` (string): File containing the import +- `importStatement` (string): The actual import line +- `targetPath` (string): What is being imported +- `resolvedPath` (string): Correct path after resolution +- `errorType` (enum): MISSING | INCORRECT_PATH | MISSING_EXPORT +- `status` (enum): BROKEN | FIXED | VERIFIED + +**Relationships**: +- Belongs to a source file +- May reference TypeDefinition if external +- Affects TypeSignature if providing types + +**Validation Rules**: +- Source path must exist in project +- Resolved path must be reachable +- Circular imports must be detected + +### TypeSignature +Represents a function or method signature that must be type-correct. + +**Fields**: +- `identifier` (string): Function/method name +- `filePath` (string): Location of the signature +- `parameters` (array): List of parameter types +- `returnType` (string): Return type specification +- `isAsync` (boolean): Whether function is async +- `errorType` (enum): MISSING_PARAM | TYPE_MISMATCH | ASYNC_SYNC_CONFLICT +- `status` (enum): INVALID | CORRECTED | VALIDATED + +**Relationships**: +- Belongs to a module/class +- May call other TypeSignatures +- Uses ImportPath for external types + +**Validation Rules**: +- Parameters must match implementation +- Async functions must return Promise +- Optional parameters must be marked correctly + +### TypeMismatch +Represents a type assignment or usage error. + +**Fields**: +- `location` (object): File path and line number +- `variableName` (string): Variable or property name +- `expectedType` (string): What TypeScript expects +- `actualType` (string): What is actually provided +- `errorCode` (string): TypeScript error code (e.g., "TS2322") +- `fixApproach` (enum): TYPE_ASSERTION | TYPE_GUARD | CHANGE_TYPE | REFACTOR + +**Relationships**: +- Related to TypeSignature or ImportPath +- May require TypeDefinition to resolve + +**Validation Rules**: +- Expected and actual must be different +- Fix approach must maintain functionality +- Must not introduce new type errors + +### BuildConfiguration +Represents TypeScript compiler configuration affecting the build. + +**Fields**: +- `configFile` (string): Path to tsconfig.json +- `compilerOptions` (object): TypeScript compiler settings +- `include` (array): Patterns for files to include +- `exclude` (array): Patterns for files to exclude +- `extends` (string): Parent configuration if any +- `buildTime` (number): Time in ms for last build + +**Relationships**: +- Affects all TypeSignature validations +- Determines TypeDefinition requirements + +**Validation Rules**: +- Must have valid JSON syntax +- Target must be compatible with Node version +- Strict options must align with code state + +## State Transitions + +### TypeDefinition States +``` +MISSING -> INSTALLED -> OUTDATED -> INSTALLED +``` + +### ImportPath States +``` +BROKEN -> FIXED -> VERIFIED +``` + +### TypeSignature States +``` +INVALID -> CORRECTED -> VALIDATED +``` + +### TypeMismatch Resolution Flow +``` +DETECTED -> ANALYZED -> FIX_APPLIED -> TESTED -> RESOLVED +``` + +## Relationships + +### Primary Relationships +1. **TypeDefinition** provides types for **ImportPath** +2. **ImportPath** affects **TypeSignature** validity +3. **TypeSignature** generates **TypeMismatch** when incorrect +4. **BuildConfiguration** validates all entities + +### Dependency Graph +``` +BuildConfiguration + ↓ +TypeDefinition + ↓ +ImportPath + ↓ +TypeSignature + ↓ +TypeMismatch +``` + +## Constraints + +### Data Integrity +- No orphaned type definitions (must be used) +- No circular import dependencies +- No conflicting type definitions +- All async functions marked correctly + +### Business Rules +- Build must complete in <30 seconds +- Zero TypeScript errors required +- Backward compatibility maintained +- All tests must pass after fixes + +## Validation Rules + +### Pre-conditions +- TypeScript installed and configured +- Node.js 20+ available +- All source files accessible + +### Post-conditions +- All TypeDefinition entities INSTALLED +- All ImportPath entities VERIFIED +- All TypeSignature entities VALIDATED +- Zero TypeMismatch entities remain + +### Invariants +- Type safety maintained throughout +- No runtime type errors introduced +- Build deterministic and reproducible +- IDE type checking consistent with build \ No newline at end of file diff --git a/specs/001-fix-typescript-build/plan.md b/specs/001-fix-typescript-build/plan.md new file mode 100644 index 0000000..fa763a6 --- /dev/null +++ b/specs/001-fix-typescript-build/plan.md @@ -0,0 +1,186 @@ +# Implementation Plan: Fix TypeScript Build Errors + +**Branch**: `001-fix-typescript-build` | **Date**: 2025-09-16 | **Spec**: [spec.md](./spec.md) +**Input**: Feature specification from `/specs/001-fix-typescript-build/spec.md` + +## Execution Flow (/plan command scope) +``` +1. Load feature spec from Input path + → SUCCESS: Spec loaded, 10 functional requirements identified +2. Fill Technical Context (scan for NEEDS CLARIFICATION) + → No NEEDS CLARIFICATION markers found + → Project Type: Single project (TypeScript CLI/API application) +3. Fill the Constitution Check section based on the constitution document + → All checks evaluated against v3.0.0 +4. Evaluate Constitution Check section below + → No violations - fixing build errors aligns with all principles + → Update Progress Tracking: Initial Constitution Check PASSED +5. Execute Phase 0 → research.md + → Research completed for TypeScript fixes and dependencies +6. Execute Phase 1 → contracts, data-model.md, quickstart.md, CLAUDE.md + → Design documents created for fix approach +7. Re-evaluate Constitution Check section + → Still passing - no new violations introduced + → Update Progress Tracking: Post-Design Constitution Check PASSED +8. Plan Phase 2 → Task generation approach described +9. STOP - Ready for /tasks command +``` + +## Summary +Fix 126 TypeScript compilation errors across 24 files to achieve zero-error builds. The errors fall into 5 main categories: missing type definitions (4 packages), import path issues (15+), type mismatches (30+), API/interface changes (20+), and async/sync conflicts (5+). Solution involves installing missing @types packages, correcting import paths, updating type annotations, and aligning API signatures with their implementations. + +## Technical Context +**Language/Version**: TypeScript 5.3+ with Node.js 20 LTS +**Primary Dependencies**: Express 4.x, Apollo Server, Commander, Tree-sitter, WebSockets +**Storage**: N/A (fixing compilation, not data layer) +**Testing**: Jest with ts-jest preset +**Target Platform**: Linux/WSL, macOS, Windows (cross-platform Node.js) +**Project Type**: Single project (CLI tool with API server capabilities) +**Performance Goals**: Build completes in <30 seconds +**Constraints**: Must maintain backward compatibility, preserve existing functionality +**Scale/Scope**: 126 errors across 24 files to fix + +## Constitution Check +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +- [x] **Performance-First**: Fixes improve build time and runtime performance +- [x] **AI-Optimized**: No impact - maintaining existing compression features +- [x] **Test-First**: Will write tests for type safety before fixing +- [x] **Multi-Language**: No new parsers - fixing existing TypeScript only +- [x] **Real-Time**: Preserves file watcher and monitoring capabilities +- [x] **Clean Code**: Enforces Logger class usage, removes console.logs +- [x] **Worker Threads**: Maintains existing Worker Thread architecture +- [x] **Security**: Improves security through proper type validation + +## Project Structure + +### Documentation (this feature) +``` +specs/001-fix-typescript-build/ +├── plan.md # This file (/plan command output) +├── research.md # Phase 0 output (/plan command) +├── data-model.md # Phase 1 output (/plan command) +├── quickstart.md # Phase 1 output (/plan command) +├── contracts/ # Phase 1 output (/plan command) +└── tasks.md # Phase 2 output (/tasks command - NOT created by /plan) +``` + +### Source Code (repository root) +``` +# Option 1: Single project (SELECTED) +src/ +├── api/ # API controllers and server files needing fixes +├── cli/ # CLI commands with import issues +├── core/ # Core logic with type mismatches +├── parsers/ # Parser implementations with async issues +└── utils/ # Utilities including Logger class + +tests/ +├── unit/ # Unit tests for type safety +└── integration/ # Build verification tests +``` + +**Structure Decision**: Option 1 - Single project structure (existing structure maintained) + +## Phase 0: Outline & Research +1. **Extract unknowns from Technical Context**: + - No NEEDS CLARIFICATION items + - Research needed for: @types packages availability + - Best practices for: TypeScript strict mode migration + - Import path resolution patterns + +2. **Generate and dispatch research agents**: + ``` + Task: "Research @types availability for jsonwebtoken, bcrypt, ws, morgan" + Task: "Find TypeScript migration patterns for gradual type safety" + Task: "Research import path resolution strategies in TypeScript" + ``` + +3. **Consolidate findings** in `research.md`: + - All @types packages confirmed available + - Gradual migration path identified + - Path mapping solution evaluated + +**Output**: research.md with solutions for all error categories + +## Phase 1: Design & Contracts +*Prerequisites: research.md complete* + +1. **Extract entities from feature spec** → `data-model.md`: + - TypeDefinition entity (package name, version, types provided) + - ImportPath entity (source, target, resolution method) + - TypeSignature entity (function, parameters, return type) + +2. **Generate API contracts** from functional requirements: + - Build success contract (zero errors) + - Type coverage contract (100% files typed) + - Performance contract (<30s build time) + +3. **Generate contract tests** from contracts: + - Build validation test + - Type checking test suite + - Import resolution tests + +4. **Extract test scenarios** from user stories: + - Clean build scenario + - IDE integration scenario + - CI/CD pipeline scenario + +5. **Update CLAUDE.md incrementally**: + - Added TypeScript fix context + - Updated with type safety patterns + - Documented error resolution approach + +**Output**: data-model.md, /contracts/*, failing tests, quickstart.md, CLAUDE.md + +## Phase 2: Task Planning Approach +*This section describes what the /tasks command will do - DO NOT execute during /plan* + +**Task Generation Strategy**: +- Generate ~35-40 tasks addressing the 10 functional requirements +- Group tasks by error category for logical progression +- Each fix category gets dedicated test tasks first (TDD) +- Implementation tasks follow test creation + +**Ordering Strategy**: +1. Install missing @types packages [P] - can run in parallel +2. Write tests for each error category [P] - independent test files +3. Fix import path issues (foundational) +4. Fix type mismatches (builds on correct imports) +5. Fix API/interface changes (depends on types) +6. Fix async/sync conflicts (final refinement) +7. Run full build verification + +**Estimated Output**: 35-40 numbered, ordered tasks in tasks.md addressing all 10 FRs + +**IMPORTANT**: This phase is executed by the /tasks command, NOT by /plan + +## Phase 3+: Future Implementation +*These phases are beyond the scope of the /plan command* + +**Phase 3**: Task execution (/tasks command creates tasks.md) +**Phase 4**: Implementation (execute tasks.md following constitutional principles) +**Phase 5**: Validation (run tests, execute quickstart.md, performance validation) + +## Complexity Tracking +*No violations requiring justification* + +## Progress Tracking +*This checklist is updated during execution flow* + +**Phase Status**: +- [x] Phase 0: Research complete (/plan command) +- [x] Phase 1: Design complete (/plan command) +- [x] Phase 2: Task planning complete (/plan command - describe approach only) +- [ ] Phase 3: Tasks generated (/tasks command) +- [ ] Phase 4: Implementation complete +- [ ] Phase 5: Validation passed + +**Gate Status**: +- [x] Initial Constitution Check: PASS +- [x] Post-Design Constitution Check: PASS +- [x] All NEEDS CLARIFICATION resolved +- [x] Complexity deviations documented (none needed) + +--- +*Based on Constitution v3.0.0 - See `/memory/constitution.md`* \ No newline at end of file diff --git a/specs/001-fix-typescript-build/quickstart.md b/specs/001-fix-typescript-build/quickstart.md new file mode 100644 index 0000000..c4b03e3 --- /dev/null +++ b/specs/001-fix-typescript-build/quickstart.md @@ -0,0 +1,198 @@ +# Quick Start: TypeScript Build Fix Validation + +**Purpose**: Verify that all TypeScript build errors have been resolved and the project compiles successfully. + +## Prerequisites + +- Node.js 20+ installed +- Yarn package manager available +- Project dependencies installed (`yarn install`) +- All @types packages added to package.json + +## Validation Steps + +### 1. Clean Build Test + +```bash +# Remove any cached build artifacts +rm -rf dist/ +rm -rf .tsbuildinfo + +# Run fresh TypeScript compilation +yarn build + +# Expected output: +# ✓ TypeScript compilation successful +# ✓ 0 errors, 0 warnings +# ✓ Build completed in <30 seconds +``` + +### 2. Type Checking Test + +```bash +# Run type checking without emitting files +yarn tsc --noEmit + +# Expected output: +# (No output - silent success means no errors) +``` + +### 3. IDE Integration Test + +Open the project in VS Code or your preferred TypeScript IDE: + +1. Open any file in `src/` directory +2. Verify no red squiggly lines appear +3. Hover over functions to see proper type hints +4. Autocomplete should work for all imports + +### 4. Individual Error Category Tests + +#### Test Missing Type Definitions +```bash +# Verify all @types packages installed +npm ls @types/jsonwebtoken @types/bcrypt @types/ws @types/morgan + +# Expected: All packages shown as installed +``` + +#### Test Import Paths +```bash +# Verify logger imports work +grep -r "from '../utils/logger'" src/ + +# Expected: No results with ../../utils/logger pattern +``` + +#### Test Type Signatures +```bash +# Run specific API type tests +yarn test --testPathPattern=api.test.ts + +# Expected: All type-related tests pass +``` + +### 5. Runtime Validation + +```bash +# Build and run the indexer +yarn build +node dist/cli/index.js --version + +# Expected output: +# 2.0.2 (or current version) +``` + +### 6. CI/CD Simulation + +```bash +# Simulate CI pipeline build +npm ci +npm run build +npm test + +# Expected: All steps complete successfully +``` + +## Success Criteria + +All of the following must be true: + +- [ ] `yarn build` completes with 0 errors +- [ ] `yarn tsc --noEmit` runs silently (no output) +- [ ] IDE shows no type errors in any .ts file +- [ ] All @types packages are installed +- [ ] Logger imports use correct paths +- [ ] API controllers have correct signatures +- [ ] Async functions return Promises +- [ ] Build completes in under 30 seconds +- [ ] The indexer command runs successfully +- [ ] All existing tests still pass + +## Troubleshooting + +### If build still fails: + +1. Check Node.js version: `node --version` (must be 20+) +2. Clear node_modules: `rm -rf node_modules && yarn install` +3. Verify tsconfig.json targets ES2023 +4. Check for conflicting global TypeScript: `npm ls -g typescript` + +### If specific errors persist: + +- **Import errors**: Check file exists at specified path +- **Type mismatch**: Verify function signatures match usage +- **Missing properties**: Ensure interfaces are up to date +- **Async errors**: Confirm Promise return types + +## Quick Commands Reference + +```bash +# Full validation suite +yarn build && yarn test && yarn tsc --noEmit + +# Check specific file +yarn tsc --noEmit src/path/to/file.ts + +# View detailed errors +yarn tsc --noEmit --pretty + +# Generate type coverage report +npx type-coverage --detail +``` + +## Final Verification + +Run this command for complete validation: + +```bash +#!/bin/bash +echo "🔍 TypeScript Build Validation" +echo "==============================" + +# Clean build +echo "1. Clean build test..." +rm -rf dist/ .tsbuildinfo +yarn build > /dev/null 2>&1 +if [ $? -eq 0 ]; then + echo "✅ Build successful" +else + echo "❌ Build failed" + exit 1 +fi + +# Type check +echo "2. Type checking..." +yarn tsc --noEmit 2>&1 +if [ $? -eq 0 ]; then + echo "✅ No type errors" +else + echo "❌ Type errors found" + exit 1 +fi + +# Check dependencies +echo "3. Type definitions check..." +npm ls @types/jsonwebtoken @types/bcrypt @types/ws @types/morgan > /dev/null 2>&1 +if [ $? -eq 0 ]; then + echo "✅ All @types packages installed" +else + echo "❌ Missing @types packages" + exit 1 +fi + +# Runtime test +echo "4. Runtime test..." +node dist/cli/index.js --version > /dev/null 2>&1 +if [ $? -eq 0 ]; then + echo "✅ Runtime execution successful" +else + echo "❌ Runtime execution failed" + exit 1 +fi + +echo "" +echo "🎉 All validations passed! TypeScript build is fixed." +``` + +Save this as `validate-fix.sh` and run with `bash validate-fix.sh` for complete verification. \ No newline at end of file diff --git a/specs/001-fix-typescript-build/research.md b/specs/001-fix-typescript-build/research.md new file mode 100644 index 0000000..ff0227b --- /dev/null +++ b/specs/001-fix-typescript-build/research.md @@ -0,0 +1,141 @@ +# Research: TypeScript Build Error Resolution + +**Date**: 2025-09-16 +**Feature**: Fix TypeScript Build Errors + +## Research Objectives +1. Verify availability of required @types packages +2. Understand best practices for TypeScript migration +3. Evaluate import path resolution strategies +4. Research API compatibility issues with updated dependencies + +## Findings + +### 1. Type Definition Packages + +**Decision**: Install all missing @types packages +**Rationale**: Official DefinitelyTyped packages provide comprehensive type coverage +**Alternatives considered**: Creating custom .d.ts files (rejected - maintenance burden) + +#### Required Packages (all confirmed available on npm): +- `@types/jsonwebtoken` - v9.0.6+ for JWT handling +- `@types/bcrypt` - v5.0.2+ for password hashing +- `@types/ws` - v8.5.10+ for WebSocket support +- `@types/morgan` - v1.9.9+ for HTTP request logging + +### 2. Import Path Resolution + +**Decision**: Fix relative paths using TypeScript path mapping +**Rationale**: Maintains compatibility while fixing import issues +**Alternatives considered**: +- Absolute imports with baseUrl (rejected - requires broader refactor) +- Module aliases (rejected - adds complexity) + +#### Common Path Issues Found: +- `../../utils/logger` should be `../utils/logger` (15 occurrences) +- Missing modules from cleanup need restoration or removal +- Apollo Server v3 uses different import paths than v2 + +### 3. Type Safety Migration Strategy + +**Decision**: Gradual migration with targeted fixes +**Rationale**: Minimizes risk while achieving immediate build success +**Alternatives considered**: +- Enable strict mode everywhere (rejected - too many changes at once) +- Create any-typed shims (rejected - defeats purpose of TypeScript) + +#### Migration Approach: +1. Fix critical type errors blocking build +2. Add type guards for null/undefined handling +3. Update function signatures to match implementations +4. Preserve `noImplicitAny: false` for gradual improvement + +### 4. API Compatibility Issues + +**Decision**: Update to compatible dependency versions +**Rationale**: Maintains ecosystem compatibility +**Alternatives considered**: +- Forking dependencies (rejected - maintenance burden) +- Downgrading TypeScript (rejected - loses modern features) + +#### Key Compatibility Fixes: +- Apollo Server v3 requires `@apollo/server` imports, not `apollo-server-express` +- GraphQL subscriptions use `graphql-ws` not deprecated `subscriptions-transport-ws` +- Express v4 compatible with current apollo-server-express +- WebSocket types need explicit typing for event handlers + +### 5. Async/Sync Pattern Corrections + +**Decision**: Align Parser interface to support both async and sync +**Rationale**: WASM parsers require async, others can be sync +**Alternatives considered**: +- Make everything async (rejected - performance impact) +- Duplicate interfaces (rejected - code duplication) + +#### Pattern Solutions: +- Create `AsyncParser` interface extending `Parser` +- Use type guards to handle Promise returns +- Explicit async/await in WASM parser factory +- Maintain sync parsers for performance + +### 6. Build Performance Optimization + +**Decision**: Use incremental compilation with project references +**Rationale**: Speeds up subsequent builds significantly +**Alternatives considered**: +- SWC/esbuild (rejected - less TypeScript feature support) +- Parallel type checking (already enabled) + +#### Performance Improvements: +- Enable `incremental: true` in tsconfig.json +- Use `tsBuildInfoFile` for caching +- Separate type checking from transpilation +- Target ES2023 for optimal output + +## Implementation Priority + +1. **Immediate** (Blocks all builds): + - Install @types packages + - Fix import paths + - Add missing function arguments + +2. **High** (Causes runtime errors): + - Handle null/undefined values + - Fix async/sync mismatches + - Correct API method signatures + +3. **Medium** (IDE experience): + - Property existence checks + - Type assertion corrections + - Interface alignments + +4. **Low** (Future improvements): + - Enable stricter type checking + - Remove any types gradually + - Add JSDoc comments + +## Risk Assessment + +### Low Risk: +- Installing @types packages +- Fixing import paths +- Adding null checks + +### Medium Risk: +- Changing function signatures (needs testing) +- Async/sync conversions (performance impact) + +### High Risk: +- None identified - all fixes are backwards compatible + +## Dependencies Validation + +All required packages confirmed available and compatible: +- TypeScript 5.3+ ✅ +- Node.js 20+ ✅ +- All @types packages on npm ✅ +- Build tools support ✅ + +## Conclusion + +All research objectives achieved. No blocking issues identified. Ready to proceed with implementation following the researched patterns and best practices. The gradual migration approach ensures build success while maintaining code quality and performance. \ No newline at end of file diff --git a/specs/001-fix-typescript-build/spec.md b/specs/001-fix-typescript-build/spec.md new file mode 100644 index 0000000..3e65dd8 --- /dev/null +++ b/specs/001-fix-typescript-build/spec.md @@ -0,0 +1,144 @@ +# Feature Specification: Fix TypeScript Build Errors + +**Feature Branch**: `001-fix-typescript-build` +**Created**: 2025-09-16 +**Status**: Draft +**Input**: User description: "Fix TypeScript build errors for successful compilation" + +## Execution Flow (main) +``` +1. Parse user description from Input + Identified: Fix 126 TypeScript compilation errors across 24 files +2. Extract key concepts from description + Actors: Developers, CI/CD pipeline + Actions: Build, compile, type-check + Data: Type definitions, import paths, API signatures + Constraints: Must maintain functionality, pass existing tests +3. For each unclear aspect: + All aspects clear from error output +4. Fill User Scenarios & Testing section + Build process must succeed without errors +5. Generate Functional Requirements + Each requirement addresses specific error categories +6. Identify Key Entities + Type definitions, module imports, API interfaces +7. Run Review Checklist + No uncertainties remain +8. Return: SUCCESS (spec ready for planning) +``` + +--- + +## Quick Guidelines +-  Focus on WHAT users need and WHY +- L Avoid HOW to implement (no tech stack, APIs, code structure) +- =e Written for business stakeholders, not developers + +--- + +## User Scenarios & Testing *(mandatory)* + +### Primary User Story +As a developer working on the indexer project, I need the TypeScript build process to complete successfully without errors so that I can compile the application, run it in production, and maintain type safety throughout the codebase. + +### Acceptance Scenarios +1. **Given** a clean project state, **When** running `yarn build`, **Then** the build completes with 0 TypeScript errors +2. **Given** the built application, **When** running `indexer` command, **Then** the application executes without runtime type errors +3. **Given** any TypeScript file in the project, **When** opened in an IDE, **Then** no red squiggly lines appear for type errors +4. **Given** the CI/CD pipeline, **When** triggered on any branch, **Then** the build stage passes successfully + +### Edge Cases +- What happens when new dependencies are added? System must have all required type definitions +- How does system handle optional dependencies? Type definitions should be available or properly mocked +- What about dynamic imports? All dynamic imports must have proper type resolution + +## Requirements *(mandatory)* + +### Functional Requirements +- **FR-001**: System MUST compile successfully with zero TypeScript errors +- **FR-002**: System MUST have all required type definition packages installed for third-party dependencies +- **FR-003**: All module imports MUST resolve to valid paths that exist in the codebase +- **FR-004**: All function calls MUST provide the correct number and types of arguments as defined by their signatures +- **FR-005**: System MUST handle null/undefined values safely with proper type guards where needed +- **FR-006**: All object property accesses MUST reference properties that exist on the type definitions +- **FR-007**: Asynchronous and synchronous function signatures MUST match their interface definitions +- **FR-008**: System MUST maintain backward compatibility with existing functionality after fixes +- **FR-009**: Build process MUST complete in reasonable time (under 30 seconds for full build) +- **FR-010**: Type checking MUST work correctly in both development and production builds + +### Key Entities *(include if feature involves data)* +- **Type Definitions**: External packages that provide TypeScript type information for JavaScript libraries +- **Module Imports**: References between TypeScript files that must resolve correctly +- **API Interfaces**: Contracts defining expected shapes of objects and function signatures +- **Build Configuration**: TypeScript compiler options and settings that affect type checking + +--- + +## Error Categories Identified + +### Missing Type Definitions (4 packages) +- jsonwebtoken package needs @types/jsonwebtoken +- bcrypt package needs @types/bcrypt +- ws (WebSocket) package needs @types/ws +- morgan package needs @types/morgan + +### Import Path Issues (15+ occurrences) +- Logger module using incorrect relative paths (../../utils/logger instead of ../utils/logger) +- Missing modules that were removed during cleanup +- Non-existent exports being imported + +### Type Mismatches (30+ occurrences) +- Boolean values assigned where numbers expected +- Missing required function arguments +- Null values not handled properly +- Properties accessed that don't exist on types + +### API/Interface Changes (20+ occurrences) +- Methods with changed signatures +- Removed properties still being referenced +- Event emitters on objects that don't support them + +### Async/Sync Conflicts (5+ occurrences) +- Functions returning Promises where synchronous returns expected +- Incorrect async/await usage patterns + +--- + +## Review & Acceptance Checklist +*GATE: Automated checks run during main() execution* + +### Content Quality +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +### Requirement Completeness +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +--- + +## Execution Status +*Updated by main() during processing* + +- [x] User description parsed +- [x] Key concepts extracted +- [x] Ambiguities marked (none found) +- [x] User scenarios defined +- [x] Requirements generated +- [x] Entities identified +- [x] Review checklist passed + +--- + +## Success Metrics + +- **Build Success Rate**: 100% (0 errors, 0 warnings related to types) +- **Type Coverage**: 100% of files pass strict type checking +- **Developer Productivity**: No type-related interruptions during development +- **CI/CD Reliability**: 100% build success rate in automated pipelines +- **IDE Experience**: Full IntelliSense and type hints working across entire codebase \ No newline at end of file diff --git a/specs/001-fix-typescript-build/tasks.md b/specs/001-fix-typescript-build/tasks.md new file mode 100644 index 0000000..ae84263 --- /dev/null +++ b/specs/001-fix-typescript-build/tasks.md @@ -0,0 +1,297 @@ +# Task List: Fix TypeScript Build Errors + +**Feature**: Fix TypeScript Build Errors +**Branch**: `001-fix-typescript-build` +**Priority Order**: Setup → Tests → Implementation → Validation + +## Phase 1: Setup & Dependencies + +### T001: Clean build environment +**File**: `package.json`, `tsconfig.json` +- Remove dist/ directory +- Clear node_modules if corrupted +- Remove .tsbuildinfo cache +- Verify Node.js 20+ installed + +### T002: Install missing type definitions [P] +**File**: `package.json` +- Add @types/jsonwebtoken@^9.0.6 to devDependencies +- Add @types/bcrypt@^5.0.2 to devDependencies +- Add @types/ws@^8.5.10 to devDependencies +- Add @types/morgan@^1.9.9 to devDependencies +- Run yarn install to update lock file + +### T003: Configure TypeScript for incremental builds [P] +**File**: `tsconfig.json` +- Set "incremental": true +- Set "tsBuildInfoFile": "./.tsbuildinfo" +- Verify "target": "ES2023" +- Ensure "module": "commonjs" +- Keep "noImplicitAny": false for gradual migration + +## Phase 2: Test Creation (TDD) + +### T004: Create build validation test suite [P] +**File**: `test/build/build-validation.test.ts` +- Test that yarn build completes with 0 exit code +- Test that dist/ directory is created +- Test that all .ts files compile to .js +- Test build time is under 30 seconds + +### T005: Create type coverage test suite [P] +**File**: `test/types/type-coverage.test.ts` +- Test all @types packages are installed +- Test no implicit any warnings in strict files +- Test all imports resolve correctly +- Test no missing type exports + +### T006: Create import resolution test suite [P] +**File**: `test/imports/import-resolution.test.ts` +- Test logger imports use correct paths +- Test all relative imports resolve +- Test no circular dependencies exist +- Test external package imports have types + +### T007: Create API signature test suite [P] +**File**: `test/api/signature-validation.test.ts` +- Test controller methods have correct signatures +- Test async functions return Promises +- Test GraphQL resolvers match schema +- Test WebSocket handlers have proper types + +## Phase 3.1: Import Path Fixes + +### T008: Fix logger import paths in API controllers +**File**: `src/api/controllers/*.ts` +- Change "../../utils/logger" to "../utils/logger" in indexer.controller.ts +- Change "../../utils/logger" to "../utils/logger" in query.controller.ts +- Change "../../utils/logger" to "../utils/logger" in ai.controller.ts +- Change "../../utils/logger" to "../utils/logger" in files.controller.ts + +### T009: Fix logger import paths in API server files +**File**: `src/api/server.ts`, `src/api/streaming/sse-handler.ts`, `src/api/web-chat.ts` +- Fix server.ts: "../../utils/logger" to "../utils/logger" +- Fix sse-handler.ts: "../../../utils/logger" to "../../utils/logger" +- Fix web-chat.ts: "../../utils/logger" to "../utils/logger" + +### T010: Fix logger import paths in WebSocket handler +**File**: `src/api/websocket/handler.ts` +- Change "../../../utils/logger" to "../../utils/logger" + +### T011: Fix logger import paths in CLI files +**File**: `src/cli/*.ts` +- Fix chat-commands.ts: "../../utils/logger" to "../utils/logger" +- Fix claude-chat.ts: "../../utils/logger" to "../utils/logger" +- Fix first-run.ts: "../../utils/logger" to "../utils/logger" + +### T012: Fix logger import paths in core modules +**File**: `src/core/*.ts` +- Fix multi-repo-knowledge-graph.ts: "../../utils/logger" to "../utils/logger" +- Fix smart-indexer.ts: "../../utils/logger" to "../utils/logger" +- Fix watcher.ts: "../../utils/logger" to "../utils/logger" + +### T013: Fix logger import paths in parsers +**File**: `src/parsers/wasm/*.ts` +- Fix base-wasm-parser.ts: "../../../utils/logger" to "../../utils/logger" +- Fix wasm-parser-factory.ts: "../../../utils/logger" to "../../utils/logger" + +## Phase 3.2: Type Mismatch Fixes + +### T014: Fix boolean/number type mismatches +**File**: `src/api/controllers/indexer.controller.ts` +- Line 25: Change `parallel: req.body.parallel !== false` to proper number handling +- Add type guard: `parallel: typeof req.body.parallel === 'number' ? req.body.parallel : 8` + +### T015: Fix missing function arguments +**File**: `src/api/controllers/indexer.controller.ts`, `src/api/graphql/resolvers.ts` +- Line 91: Add projectRoot argument to updateFile call +- Line 223 (resolvers): Add projectRoot argument to updateFile call + +### T016: Fix null handling in API controllers [P] +**File**: `src/api/controllers/indexer.controller.ts` +- Lines 93-94: Add null check for index before accessing properties +- Wrap in: `if (index) { ... }` + +### T017: Fix null handling in query controller +**File**: `src/api/controllers/query.controller.ts` +- Lines 55, 98, 152: Add null check for index before accessing index.files +- Line 166-167: Check index.dependencyGraph exists + +### T018: Fix null handling in GraphQL resolvers +**File**: `src/api/graphql/resolvers.ts` +- Lines 22, 68, 84, 112, 117, 129, 137, 147, 225: Add null checks for index +- Wrap each in proper null guard + +### T019: Fix array type issues +**File**: `src/api/controllers/query.controller.ts` +- Lines 155, 169: Fix type inference for graph.nodes and graph.edges arrays +- Properly type graph object with interfaces + +## Phase 3.3: API/Interface Fixes + +### T020: Fix Apollo Server imports +**File**: `src/api/server.ts` +- Change apollo-server-express imports to @apollo/server +- Import expressMiddleware from @apollo/server/express4 + +### T021: Fix GraphQL WebSocket imports +**File**: `src/api/server.ts` +- Import useServer from graphql-ws/lib/use/ws +- Remove deprecated subscriptions-transport-ws + +### T022: Fix WebSocket message types +**File**: `src/api/websocket/handler.ts` +- Line 32: Remove 'connected' type or add to WebSocketResponse type definition +- Update type definition in src/api/types.ts + +### T023: Fix missing async iterator in PubSub +**File**: `src/api/graphql/resolvers.ts` +- Lines 313, 315, 320, 324, 328: Import correct PubSub from graphql-subscriptions +- Ensure PubSub has asyncIterator method + +### T024: Fix auth middleware exports +**File**: `src/api/middleware/auth.ts`, `src/api/server.ts` +- Export authMiddleware function from auth.ts +- Line 25: Ensure import matches export + +### T025: Fix property access errors +**File**: `src/api/controllers/indexer.controller.ts` +- Lines 93-94: Remove references to non-existent statistics and timestamp properties +- Use correct FileIndex properties + +## Phase 3.4: Async/Sync Conflicts + +### T026: Fix Parser interface async/sync mismatch +**File**: `src/parsers/wasm/base-wasm-parser.ts` +- Line 107: Update Parser interface to support async parse method +- Or create AsyncParser interface extending Parser + +### T027: Fix WASM parser factory return types +**File**: `src/parsers/wasm/wasm-parser-factory.ts` +- Lines 179, 185: Ensure parser matches Parser interface +- Handle Promise returns properly + +### T028: Fix async function signatures [P] +**File**: `src/core/call-graph-analyzer.ts` +- Line 319: Fix Math.min usage with array of numbers +- Line 399: Ensure directCallers is string[] not unknown[] + +## Phase 3.5: Missing Modules & Types + +### T029: Create missing type declarations +**File**: `src/types/external.d.ts` (if needed) +- Declare any missing module types +- Add declarations for removed modules + +### T030: Fix SmartIndexer method signatures +**File**: `src/cli/index.ts`, `src/core/smart-indexer.ts` +- Line 131: Ensure SmartIndexer has run method +- Or rename to correct method name + +### T031: Fix undefined variables and methods +**File**: `src/core/indexer.ts` +- Line 118: Fix undefined 'results' variable +- Line 311: Change 'logger' to 'this.logger' + +### T032: Fix missing methods on objects +**File**: `src/api/websocket/handler.ts` +- Lines 195, 205, 215: Remove .on() calls from Indexer or add EventEmitter + +### T033: Fix MultiRepoKnowledgeGraph methods +**File**: `src/cursor/multi-repo-integration.ts` +- Line 55: Add hasRepositories method or check differently +- Line 69, 103: Add missing methods to MultiRepoMermaidExporter + +## Phase 4: Integration & Validation + +### T034: Run build validation test +**File**: N/A - Command execution +- Run: yarn test test/build/build-validation.test.ts +- Verify all tests pass +- Fix any remaining compilation errors + +### T035: Run type coverage test +**File**: N/A - Command execution +- Run: yarn test test/types/type-coverage.test.ts +- Verify 100% type coverage +- Document any intentional any types + +### T036: Run import resolution test +**File**: N/A - Command execution +- Run: yarn test test/imports/import-resolution.test.ts +- Verify all imports resolve +- Check no circular dependencies + +### T037: Run full test suite +**File**: N/A - Command execution +- Run: yarn test +- Ensure all existing tests still pass +- No runtime type errors + +### T038: Perform clean build +**File**: N/A - Command execution +- Run: rm -rf dist/ && yarn build +- Verify 0 TypeScript errors +- Build time under 30 seconds + +### T039: Validate runtime execution +**File**: N/A - Command execution +- Run: node dist/cli/index.js --version +- Test basic indexer commands work +- Verify no runtime type errors + +### T040: Update documentation [P] +**File**: `README.md`, `CLAUDE.md` +- Document TypeScript version requirements +- Note any configuration changes +- Update build instructions if needed + +--- + +## Execution Order + +**Sequential Groups** (must run in order): +1. T001-T003: Setup & Dependencies +2. T004-T007: Test Creation (can run in parallel within group) +3. T008-T013: Import Path Fixes +4. T014-T019: Type Mismatch Fixes +5. T020-T025: API/Interface Fixes +6. T026-T028: Async/Sync Conflicts +7. T029-T033: Missing Modules & Types +8. T034-T040: Integration & Validation + +**Parallel Execution Examples**: + +```bash +# Install all type packages in parallel +Task T002 & Task T003 + +# Create all test suites in parallel +Task T004 & Task T005 & Task T006 & Task T007 + +# Fix async issues in parallel (different files) +Task T026 & Task T028 + +# Run validation tests in parallel +Task T034 & Task T035 & Task T036 + +# Update documentation in parallel +Task T040 +``` + +## Notes + +- Tasks marked [P] can be executed in parallel as they touch different files +- Import path fixes (T008-T013) must complete before type checking +- Test files should be created before implementation (TDD approach) +- Each task is specific enough for autonomous completion +- Build validation (T038) is the final gate before completion + +## Success Criteria + +✅ All 40 tasks completed +✅ yarn build exits with code 0 +✅ 0 TypeScript compilation errors +✅ All tests passing +✅ Build time < 30 seconds +✅ Runtime execution successful \ No newline at end of file diff --git a/src/ai/agents/base-agent.ts b/src/ai/agents/base-agent.ts index e79e360..999a29e 100644 --- a/src/ai/agents/base-agent.ts +++ b/src/ai/agents/base-agent.ts @@ -1,116 +1,116 @@ -/** - * Base agent class for specialized AI analysis agents - * Provides common functionality for all agent types - */ - -import { EventEmitter } from 'events'; -import { query } from '@anthropic-ai/claude-code'; - -export abstract class BaseAgent extends EventEmitter { - protected name: string; - protected system: string; - protected sessionId: string | null = null; - protected abortController: AbortController | null = null; - - constructor(name: string, system: string) { - super(); - this.name = name; - this.system = system; - } - - /** - * Abstract method that each agent must implement - */ - abstract analyze(data: any, options?: any): Promise; - - /** - * Continue a previous session with follow-up questions - */ - async continueSession(prompt: string, maxTurns: number = 2): Promise { - if (!this.sessionId) { - throw new Error(`No active session for ${this.name} agent`); - } - - const results: any[] = []; - - for await (const message of query({ - prompt, - options: { - continue: this.sessionId ? true : false, - maxTurns - } - })) { - if (message.type === 'assistant') { - this.emit('session-update', message); - results.push(message); - } - - if (message.type === 'result') { - return { - result: (message as any).result || '', - messages: results, - metadata: { - duration: message.duration_ms, - cost: (message as any).total_cost_usd, - turns: (message as any).num_turns - } - }; - } - } - - return results; - } - - /** - * Cancel ongoing analysis - */ - cancelAnalysis(): void { - if (this.abortController) { - this.abortController.abort(); - this.abortController = null; - this.emit('analysis-cancelled', this.name); - } - } - - /** - * Get agent status - */ - getStatus(): AgentStatus { - return { - name: this.name, - hasActiveSession: !!this.sessionId, - sessionId: this.sessionId, - isAnalyzing: !!this.abortController - }; - } - - /** - * Clear session - */ - clearSession(): void { - this.sessionId = null; - this.emit('session-cleared', this.name); - } - - /** - * Helper to create abort controller - */ - protected createAbortController(): AbortController { - this.abortController = new AbortController(); - return this.abortController; - } - - /** - * Helper to clean up abort controller - */ - protected cleanupAbortController(): void { - this.abortController = null; - } -} - -export interface AgentStatus { - name: string; - hasActiveSession: boolean; - sessionId: string | null; - isAnalyzing: boolean; +/** + * Base agent class for specialized AI analysis agents + * Provides common functionality for all agent types + */ + +import { EventEmitter } from 'events'; +import { query } from '@anthropic-ai/claude-code'; + +export abstract class BaseAgent extends EventEmitter { + protected name: string; + protected system: string; + protected sessionId: string | null = null; + protected abortController: AbortController | null = null; + + constructor(name: string, system: string) { + super(); + this.name = name; + this.system = system; + } + + /** + * Abstract method that each agent must implement + */ + abstract analyze(data: any, options?: any): Promise; + + /** + * Continue a previous session with follow-up questions + */ + async continueSession(prompt: string, maxTurns: number = 2): Promise { + if (!this.sessionId) { + throw new Error(`No active session for ${this.name} agent`); + } + + const results: any[] = []; + + for await (const message of query({ + prompt, + options: { + continue: this.sessionId ? true : false, + maxTurns + } + })) { + if (message.type === 'assistant') { + this.emit('session-update', message); + results.push(message); + } + + if (message.type === 'result') { + return { + result: (message as any).result || '', + messages: results, + metadata: { + duration: message.duration_ms, + cost: (message as any).total_cost_usd, + turns: (message as any).num_turns + } + }; + } + } + + return results; + } + + /** + * Cancel ongoing analysis + */ + cancelAnalysis(): void { + if (this.abortController) { + this.abortController.abort(); + this.abortController = null; + this.emit('analysis-cancelled', this.name); + } + } + + /** + * Get agent status + */ + getStatus(): AgentStatus { + return { + name: this.name, + hasActiveSession: !!this.sessionId, + sessionId: this.sessionId, + isAnalyzing: !!this.abortController + }; + } + + /** + * Clear session + */ + clearSession(): void { + this.sessionId = null; + this.emit('session-cleared', this.name); + } + + /** + * Helper to create abort controller + */ + protected createAbortController(): AbortController { + this.abortController = new AbortController(); + return this.abortController; + } + + /** + * Helper to clean up abort controller + */ + protected cleanupAbortController(): void { + this.abortController = null; + } +} + +export interface AgentStatus { + name: string; + hasActiveSession: boolean; + sessionId: string | null; + isAnalyzing: boolean; } \ No newline at end of file diff --git a/src/ai/agents/security-agent.ts b/src/ai/agents/security-agent.ts index b0bca6c..a5de782 100644 --- a/src/ai/agents/security-agent.ts +++ b/src/ai/agents/security-agent.ts @@ -1,472 +1,472 @@ -/** - * Security-focused analysis agent - * Specializes in vulnerability detection, OWASP compliance, and security best practices - */ - -import { query } from '@anthropic-ai/claude-code'; -import { ProjectIndex } from '../../types'; -import { BaseAgent } from './base-agent'; - -export class SecurityAgent extends BaseAgent { - constructor() { - super('security', 'You are a security expert specializing in application security, OWASP compliance, and vulnerability detection.'); - } - - /** - * Perform comprehensive security analysis - */ - async analyze(index: ProjectIndex, options?: SecurityAnalysisOptions): Promise { - const report: SecurityAnalysisReport = { - timestamp: new Date().toISOString(), - vulnerabilities: [], - compliance: { - owasp: [], - pci: [], - gdpr: [] - }, - secrets: [], - dependencies: [], - recommendations: [], - score: 100 - }; - - const abortController = new AbortController(); - const prompt = this.buildSecurityPrompt(index, options); - - try { - for await (const message of query({ - prompt, - options: { - abortController, - customSystemPrompt: this.system + ` -Focus on: -1. OWASP Top 10 vulnerabilities -2. Authentication and authorization flaws -3. Injection vulnerabilities (SQL, NoSQL, Command, LDAP) -4. Sensitive data exposure -5. Security misconfigurations -6. Cross-site scripting (XSS) -7. Insecure deserialization -8. Using components with known vulnerabilities -9. Insufficient logging and monitoring -10. API security issues - -Provide specific file paths, line numbers, and remediation steps.`, - maxTurns: options?.maxTurns || 5, - permissionMode: 'plan', - allowedTools: [ - 'Read', - 'Grep', - 'WebSearch', - 'mcp__semgrep', - 'mcp__snyk', - 'mcp__github__security_alerts' - ], - // MCP servers would be configured here if needed - // mcpServers: loadMcpServers() - } - })) { - if (message.type === 'assistant') { - this.emit('update', { - type: 'security', - message: message.message, - timestamp: new Date().toISOString() - }); - - // Parse vulnerabilities from response - const parsed = this.parseSecurityFindings(message); - if (parsed) { - report.vulnerabilities.push(...parsed.vulnerabilities); - report.secrets.push(...parsed.secrets); - report.dependencies.push(...parsed.dependencies); - } - } - - if (message.type === 'result') { - // Process final result - report.recommendations = this.extractRecommendations((message as any).result || ''); - report.score = this.calculateSecurityScore(report); - - // Cache session for follow-ups - if (message.session_id) { - this.sessionId = message.session_id; - } - - // Add metadata - report.metadata = { - duration: message.duration_ms, - cost: message.total_cost_usd, - sessionId: message.session_id - }; - } - } - } catch (error) { - this.emit('error', error); - throw error; - } - - return report; - } - - /** - * Scan for hardcoded secrets and credentials - */ - async scanSecrets(index: ProjectIndex): Promise { - const secrets: SecretScanResult[] = []; - const prompt = `Scan the codebase for hardcoded secrets, API keys, passwords, and credentials. - -Files to analyze: ${Object.keys(index.files).length} - -Look for: -1. Hardcoded passwords -2. API keys (AWS, Azure, GCP, etc.) -3. Database credentials -4. JWT secrets -5. OAuth tokens -6. Private keys -7. Webhook URLs with embedded tokens - -Return findings with exact locations and severity.`; - - for await (const message of query({ - prompt, - options: { - customSystemPrompt: 'You are a security expert specializing in secret detection', - maxTurns: 2, - permissionMode: 'plan', - allowedTools: ['Read', 'Grep'] - } - })) { - if (message.type === 'result') { - // Parse secret findings - const findings = this.parseSecretFindings((message as any).result || ''); - secrets.push(...findings); - } - } - - return secrets; - } - - /** - * Check OWASP compliance - */ - async checkOWASPCompliance(index: ProjectIndex): Promise { - const report: OWASPComplianceReport = { - version: 'OWASP Top 10 2021', - compliant: [], - nonCompliant: [], - warnings: [], - score: 0 - }; - - const prompt = `Evaluate the codebase against OWASP Top 10 2021 security risks: - -A01:2021 – Broken Access Control -A02:2021 – Cryptographic Failures -A03:2021 – Injection -A04:2021 – Insecure Design -A05:2021 – Security Misconfiguration -A06:2021 – Vulnerable and Outdated Components -A07:2021 – Identification and Authentication Failures -A08:2021 – Software and Data Integrity Failures -A09:2021 – Security Logging and Monitoring Failures -A10:2021 – Server-Side Request Forgery (SSRF) - -Assess compliance for each category and provide specific examples.`; - - for await (const message of query({ - prompt, - options: { - customSystemPrompt: 'You are an OWASP compliance auditor.', - maxTurns: 3, - permissionMode: 'plan', - allowedTools: ['Read', 'Grep', 'mcp__semgrep'] - } - })) { - if (message.type === 'result') { - const compliance = this.parseOWASPCompliance((message as any).result || ''); - Object.assign(report, compliance); - } - } - - return report; - } - - /** - * Analyze dependency vulnerabilities - */ - async analyzeDependencies(index: ProjectIndex): Promise { - const vulnerabilities: DependencyVulnerability[] = []; - - // Find package files - const packageFiles = Object.keys(index.files).filter(path => - path.includes('package.json') || - path.includes('requirements.txt') || - path.includes('go.mod') || - path.includes('Gemfile') - ); - - if (packageFiles.length === 0) { - return vulnerabilities; - } - - const prompt = `Analyze dependencies for known vulnerabilities: - -Package files found: -${packageFiles.join('\n')} - -Check for: -1. Known CVEs -2. Outdated packages with security patches -3. Packages with security advisories -4. Unmaintained packages -5. License compliance issues - -Use available security databases and tools.`; - - for await (const message of query({ - prompt, - options: { - customSystemPrompt: 'You are a dependency security analyst.', - maxTurns: 2, - permissionMode: 'plan', - allowedTools: ['Read', 'WebSearch', 'mcp__snyk', 'mcp__github__security_alerts'] - } - })) { - if (message.type === 'result') { - const deps = this.parseDependencyVulnerabilities((message as any).result || ''); - vulnerabilities.push(...deps); - } - } - - return vulnerabilities; - } - - /** - * Build security analysis prompt - */ - private buildSecurityPrompt(index: ProjectIndex, options?: SecurityAnalysisOptions): string { - const criticalFiles = Object.entries(index.files) - .filter(([path]) => this.isSecurityCritical(path)) - .slice(0, 30); - - return `Perform comprehensive security analysis of the codebase: - -Project Overview: -- Total Files: ${Object.keys(index.files).length} -- Security-Critical Files: ${criticalFiles.length} -- Languages: ${this.getLanguages(index)} - -Critical Files to Analyze: -${criticalFiles.map(([path]) => `- ${path}`).join('\n')} - -${options?.focus ? `Focus Areas: ${options.focus.join(', ')}` : ''} - -Provide: -1. Vulnerability assessment with CVSS scores -2. Specific code locations and examples -3. Remediation steps -4. Risk prioritization -5. Compliance gaps`; - } - - /** - * Check if file is security-critical - */ - private isSecurityCritical(path: string): boolean { - const criticalPatterns = [ - 'auth', 'login', 'security', 'api', 'database', 'db', - 'password', 'token', 'session', 'cookie', 'crypto', - 'payment', 'checkout', 'user', 'admin', 'config' - ]; - - return criticalPatterns.some(pattern => - path.toLowerCase().includes(pattern) - ); - } - - /** - * Parse security findings from Claude response - */ - private parseSecurityFindings(message: any): any { - try { - // Extract structured data from message - // This would parse the actual response format - return { - vulnerabilities: [], - secrets: [], - dependencies: [] - }; - } catch (error) { - this.emit('parse-error', error); - return null; - } - } - - /** - * Calculate overall security score - */ - private calculateSecurityScore(report: SecurityAnalysisReport): number { - let score = 100; - - // Deduct points based on findings - for (const vuln of report.vulnerabilities) { - switch (vuln.severity) { - case 'critical': score -= 20; break; - case 'high': score -= 10; break; - case 'medium': score -= 5; break; - case 'low': score -= 2; break; - } - } - - // Deduct for secrets - score -= report.secrets.length * 15; - - // Deduct for vulnerable dependencies - score -= report.dependencies.filter(d => d.severity === 'high').length * 10; - - return Math.max(0, Math.min(100, score)); - } - - /** - * Extract recommendations from analysis - */ - private extractRecommendations(result: string): SecurityRecommendation[] { - // Parse recommendations from the result - // This would extract structured recommendations - return []; - } - - /** - * Parse secret scan findings - */ - private parseSecretFindings(result: string): SecretScanResult[] { - // Parse secret findings from result - return []; - } - - /** - * Parse OWASP compliance results - */ - private parseOWASPCompliance(result: string): Partial { - // Parse OWASP compliance from result - return { - compliant: [], - nonCompliant: [], - warnings: [], - score: 0 - }; - } - - /** - * Parse dependency vulnerabilities - */ - private parseDependencyVulnerabilities(result: string): DependencyVulnerability[] { - // Parse dependency vulnerabilities from result - return []; - } - - /** - * Get language distribution - */ - private getLanguages(index: ProjectIndex): string { - const languages: Record = {}; - - for (const file of Object.values(index.files)) { - languages[file.language] = (languages[file.language] || 0) + 1; - } - - return Object.entries(languages) - .sort((a, b) => b[1] - a[1]) - .map(([lang, count]) => `${lang}(${count})`) - .join(', '); - } -} - -/** - * Type definitions - */ -export interface SecurityAnalysisOptions { - maxTurns?: number; - focus?: string[]; - includeSecrets?: boolean; - includeDependencies?: boolean; - includeCompliance?: boolean; -} - -export interface SecurityAnalysisReport { - timestamp: string; - vulnerabilities: SecurityVulnerability[]; - compliance: { - owasp: ComplianceItem[]; - pci: ComplianceItem[]; - gdpr: ComplianceItem[]; - }; - secrets: SecretScanResult[]; - dependencies: DependencyVulnerability[]; - recommendations: SecurityRecommendation[]; - score: number; - metadata?: { - duration: number; - cost: number; - sessionId: string; - }; -} - -export interface SecurityVulnerability { - id: string; - type: string; - severity: 'critical' | 'high' | 'medium' | 'low'; - cvss?: number; - cwe?: string; - file: string; - line?: number; - description: string; - example?: string; - remediation: string; - references?: string[]; -} - -export interface SecretScanResult { - type: string; - value?: string; // Redacted - file: string; - line: number; - severity: 'critical' | 'high'; - recommendation: string; -} - -export interface OWASPComplianceReport { - version: string; - compliant: ComplianceItem[]; - nonCompliant: ComplianceItem[]; - warnings: ComplianceItem[]; - score: number; -} - -export interface ComplianceItem { - category: string; - status: 'compliant' | 'non-compliant' | 'warning'; - description: string; - evidence?: string; - recommendation?: string; -} - -export interface DependencyVulnerability { - package: string; - version: string; - vulnerability: string; - cve?: string; - severity: 'critical' | 'high' | 'medium' | 'low'; - fixedVersion?: string; - recommendation: string; -} - -export interface SecurityRecommendation { - priority: 'critical' | 'high' | 'medium' | 'low'; - category: string; - description: string; - implementation: string; - effort: 'low' | 'medium' | 'high'; - impact: string; +/** + * Security-focused analysis agent + * Specializes in vulnerability detection, OWASP compliance, and security best practices + */ + +import { query } from '@anthropic-ai/claude-code'; +import { ProjectIndex } from '../../types'; +import { BaseAgent } from './base-agent'; + +export class SecurityAgent extends BaseAgent { + constructor() { + super('security', 'You are a security expert specializing in application security, OWASP compliance, and vulnerability detection.'); + } + + /** + * Perform comprehensive security analysis + */ + async analyze(index: ProjectIndex, options?: SecurityAnalysisOptions): Promise { + const report: SecurityAnalysisReport = { + timestamp: new Date().toISOString(), + vulnerabilities: [], + compliance: { + owasp: [], + pci: [], + gdpr: [] + }, + secrets: [], + dependencies: [], + recommendations: [], + score: 100 + }; + + const abortController = new AbortController(); + const prompt = this.buildSecurityPrompt(index, options); + + try { + for await (const message of query({ + prompt, + options: { + abortController, + customSystemPrompt: this.system + ` +Focus on: +1. OWASP Top 10 vulnerabilities +2. Authentication and authorization flaws +3. Injection vulnerabilities (SQL, NoSQL, Command, LDAP) +4. Sensitive data exposure +5. Security misconfigurations +6. Cross-site scripting (XSS) +7. Insecure deserialization +8. Using components with known vulnerabilities +9. Insufficient logging and monitoring +10. API security issues + +Provide specific file paths, line numbers, and remediation steps.`, + maxTurns: options?.maxTurns || 5, + permissionMode: 'plan', + allowedTools: [ + 'Read', + 'Grep', + 'WebSearch', + 'mcp__semgrep', + 'mcp__snyk', + 'mcp__github__security_alerts' + ], + // MCP servers would be configured here if needed + // mcpServers: loadMcpServers() + } + })) { + if (message.type === 'assistant') { + this.emit('update', { + type: 'security', + message: message.message, + timestamp: new Date().toISOString() + }); + + // Parse vulnerabilities from response + const parsed = this.parseSecurityFindings(message); + if (parsed) { + report.vulnerabilities.push(...parsed.vulnerabilities); + report.secrets.push(...parsed.secrets); + report.dependencies.push(...parsed.dependencies); + } + } + + if (message.type === 'result') { + // Process final result + report.recommendations = this.extractRecommendations((message as any).result || ''); + report.score = this.calculateSecurityScore(report); + + // Cache session for follow-ups + if (message.session_id) { + this.sessionId = message.session_id; + } + + // Add metadata + report.metadata = { + duration: message.duration_ms, + cost: message.total_cost_usd, + sessionId: message.session_id + }; + } + } + } catch (error) { + this.emit('error', error); + throw error; + } + + return report; + } + + /** + * Scan for hardcoded secrets and credentials + */ + async scanSecrets(index: ProjectIndex): Promise { + const secrets: SecretScanResult[] = []; + const prompt = `Scan the codebase for hardcoded secrets, API keys, passwords, and credentials. + +Files to analyze: ${Object.keys(index.files).length} + +Look for: +1. Hardcoded passwords +2. API keys (AWS, Azure, GCP, etc.) +3. Database credentials +4. JWT secrets +5. OAuth tokens +6. Private keys +7. Webhook URLs with embedded tokens + +Return findings with exact locations and severity.`; + + for await (const message of query({ + prompt, + options: { + customSystemPrompt: 'You are a security expert specializing in secret detection', + maxTurns: 2, + permissionMode: 'plan', + allowedTools: ['Read', 'Grep'] + } + })) { + if (message.type === 'result') { + // Parse secret findings + const findings = this.parseSecretFindings((message as any).result || ''); + secrets.push(...findings); + } + } + + return secrets; + } + + /** + * Check OWASP compliance + */ + async checkOWASPCompliance(index: ProjectIndex): Promise { + const report: OWASPComplianceReport = { + version: 'OWASP Top 10 2021', + compliant: [], + nonCompliant: [], + warnings: [], + score: 0 + }; + + const prompt = `Evaluate the codebase against OWASP Top 10 2021 security risks: + +A01:2021 – Broken Access Control +A02:2021 – Cryptographic Failures +A03:2021 – Injection +A04:2021 – Insecure Design +A05:2021 – Security Misconfiguration +A06:2021 – Vulnerable and Outdated Components +A07:2021 – Identification and Authentication Failures +A08:2021 – Software and Data Integrity Failures +A09:2021 – Security Logging and Monitoring Failures +A10:2021 – Server-Side Request Forgery (SSRF) + +Assess compliance for each category and provide specific examples.`; + + for await (const message of query({ + prompt, + options: { + customSystemPrompt: 'You are an OWASP compliance auditor.', + maxTurns: 3, + permissionMode: 'plan', + allowedTools: ['Read', 'Grep', 'mcp__semgrep'] + } + })) { + if (message.type === 'result') { + const compliance = this.parseOWASPCompliance((message as any).result || ''); + Object.assign(report, compliance); + } + } + + return report; + } + + /** + * Analyze dependency vulnerabilities + */ + async analyzeDependencies(index: ProjectIndex): Promise { + const vulnerabilities: DependencyVulnerability[] = []; + + // Find package files + const packageFiles = Object.keys(index.files).filter(path => + path.includes('package.json') || + path.includes('requirements.txt') || + path.includes('go.mod') || + path.includes('Gemfile') + ); + + if (packageFiles.length === 0) { + return vulnerabilities; + } + + const prompt = `Analyze dependencies for known vulnerabilities: + +Package files found: +${packageFiles.join('\n')} + +Check for: +1. Known CVEs +2. Outdated packages with security patches +3. Packages with security advisories +4. Unmaintained packages +5. License compliance issues + +Use available security databases and tools.`; + + for await (const message of query({ + prompt, + options: { + customSystemPrompt: 'You are a dependency security analyst.', + maxTurns: 2, + permissionMode: 'plan', + allowedTools: ['Read', 'WebSearch', 'mcp__snyk', 'mcp__github__security_alerts'] + } + })) { + if (message.type === 'result') { + const deps = this.parseDependencyVulnerabilities((message as any).result || ''); + vulnerabilities.push(...deps); + } + } + + return vulnerabilities; + } + + /** + * Build security analysis prompt + */ + private buildSecurityPrompt(index: ProjectIndex, options?: SecurityAnalysisOptions): string { + const criticalFiles = Object.entries(index.files) + .filter(([path]) => this.isSecurityCritical(path)) + .slice(0, 30); + + return `Perform comprehensive security analysis of the codebase: + +Project Overview: +- Total Files: ${Object.keys(index.files).length} +- Security-Critical Files: ${criticalFiles.length} +- Languages: ${this.getLanguages(index)} + +Critical Files to Analyze: +${criticalFiles.map(([path]) => `- ${path}`).join('\n')} + +${options?.focus ? `Focus Areas: ${options.focus.join(', ')}` : ''} + +Provide: +1. Vulnerability assessment with CVSS scores +2. Specific code locations and examples +3. Remediation steps +4. Risk prioritization +5. Compliance gaps`; + } + + /** + * Check if file is security-critical + */ + private isSecurityCritical(path: string): boolean { + const criticalPatterns = [ + 'auth', 'login', 'security', 'api', 'database', 'db', + 'password', 'token', 'session', 'cookie', 'crypto', + 'payment', 'checkout', 'user', 'admin', 'config' + ]; + + return criticalPatterns.some(pattern => + path.toLowerCase().includes(pattern) + ); + } + + /** + * Parse security findings from Claude response + */ + private parseSecurityFindings(message: any): any { + try { + // Extract structured data from message + // This would parse the actual response format + return { + vulnerabilities: [], + secrets: [], + dependencies: [] + }; + } catch (error) { + this.emit('parse-error', error); + return null; + } + } + + /** + * Calculate overall security score + */ + private calculateSecurityScore(report: SecurityAnalysisReport): number { + let score = 100; + + // Deduct points based on findings + for (const vuln of report.vulnerabilities) { + switch (vuln.severity) { + case 'critical': score -= 20; break; + case 'high': score -= 10; break; + case 'medium': score -= 5; break; + case 'low': score -= 2; break; + } + } + + // Deduct for secrets + score -= report.secrets.length * 15; + + // Deduct for vulnerable dependencies + score -= report.dependencies.filter(d => d.severity === 'high').length * 10; + + return Math.max(0, Math.min(100, score)); + } + + /** + * Extract recommendations from analysis + */ + private extractRecommendations(result: string): SecurityRecommendation[] { + // Parse recommendations from the result + // This would extract structured recommendations + return []; + } + + /** + * Parse secret scan findings + */ + private parseSecretFindings(result: string): SecretScanResult[] { + // Parse secret findings from result + return []; + } + + /** + * Parse OWASP compliance results + */ + private parseOWASPCompliance(result: string): Partial { + // Parse OWASP compliance from result + return { + compliant: [], + nonCompliant: [], + warnings: [], + score: 0 + }; + } + + /** + * Parse dependency vulnerabilities + */ + private parseDependencyVulnerabilities(result: string): DependencyVulnerability[] { + // Parse dependency vulnerabilities from result + return []; + } + + /** + * Get language distribution + */ + private getLanguages(index: ProjectIndex): string { + const languages: Record = {}; + + for (const file of Object.values(index.files)) { + languages[file.language] = (languages[file.language] || 0) + 1; + } + + return Object.entries(languages) + .sort((a, b) => b[1] - a[1]) + .map(([lang, count]) => `${lang}(${count})`) + .join(', '); + } +} + +/** + * Type definitions + */ +export interface SecurityAnalysisOptions { + maxTurns?: number; + focus?: string[]; + includeSecrets?: boolean; + includeDependencies?: boolean; + includeCompliance?: boolean; +} + +export interface SecurityAnalysisReport { + timestamp: string; + vulnerabilities: SecurityVulnerability[]; + compliance: { + owasp: ComplianceItem[]; + pci: ComplianceItem[]; + gdpr: ComplianceItem[]; + }; + secrets: SecretScanResult[]; + dependencies: DependencyVulnerability[]; + recommendations: SecurityRecommendation[]; + score: number; + metadata?: { + duration: number; + cost: number; + sessionId: string; + }; +} + +export interface SecurityVulnerability { + id: string; + type: string; + severity: 'critical' | 'high' | 'medium' | 'low'; + cvss?: number; + cwe?: string; + file: string; + line?: number; + description: string; + example?: string; + remediation: string; + references?: string[]; +} + +export interface SecretScanResult { + type: string; + value?: string; // Redacted + file: string; + line: number; + severity: 'critical' | 'high'; + recommendation: string; +} + +export interface OWASPComplianceReport { + version: string; + compliant: ComplianceItem[]; + nonCompliant: ComplianceItem[]; + warnings: ComplianceItem[]; + score: number; +} + +export interface ComplianceItem { + category: string; + status: 'compliant' | 'non-compliant' | 'warning'; + description: string; + evidence?: string; + recommendation?: string; +} + +export interface DependencyVulnerability { + package: string; + version: string; + vulnerability: string; + cve?: string; + severity: 'critical' | 'high' | 'medium' | 'low'; + fixedVersion?: string; + recommendation: string; +} + +export interface SecurityRecommendation { + priority: 'critical' | 'high' | 'medium' | 'low'; + category: string; + description: string; + implementation: string; + effort: 'low' | 'medium' | 'high'; + impact: string; } \ No newline at end of file diff --git a/src/ai/claude-analyzer.ts b/src/ai/claude-analyzer.ts index 3192c2c..58a7b74 100644 --- a/src/ai/claude-analyzer.ts +++ b/src/ai/claude-analyzer.ts @@ -1,691 +1,691 @@ -import { ProjectIndex, FileIndex, FunctionInfo} from '../types'; -import { exec } from 'child_process'; -import { promisify } from 'util'; -import logger from '../utils/logger'; - -const execAsync = promisify(exec); - -/** - * AI-powered code analyzer using Claude Code SDK - * Provides bug prediction, code smell detection, test generation, and more - */ -export class ClaudeCodeAnalyzer { - private apiKey: string; - private model: string; - - constructor() { - this.apiKey = process.env.ANTHROPIC_API_KEY || ''; - this.model = process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805'; - - if (!this.apiKey) { - logger.warn('Warning: ANTHROPIC_API_KEY not set. AI features will be limited.'); - } - } - - /** - * Comprehensive AI analysis of entire codebase - */ - async analyzeCodebase(index: ProjectIndex): Promise { - const analysis: AIAnalysis = { - bugPredictions: [], - codeSmells: [], - securityIssues: [], - refactoringSuggestions: [], - testSuggestions: [], - architectureInsights: [], - performanceIssues: [], - timestamp: new Date().toISOString(), - summary: '' - }; - - try { - // Run analyses in parallel for efficiency - const [bugs, smells, security, refactoring, tests] = await Promise.all([ - this.predictBugs(index), - this.detectCodeSmells(index), - this.analyzeSecurity(index), - this.suggestRefactoring(index), - this.generateTestSuggestions(index) - ]); - - analysis.bugPredictions = bugs; - analysis.codeSmells = smells; - analysis.securityIssues = security; - analysis.refactoringSuggestions = refactoring; - analysis.testSuggestions = tests; - - // Generate executive summary - analysis.summary = await this.generateSummary(analysis); - - return analysis; - } catch (error) { - logger.error('AI analysis error:', error); - throw error; - } - } - - /** - * Predict potential bugs using Claude's pattern recognition - */ - async predictBugs(index: ProjectIndex): Promise { - const predictions: BugPrediction[] = []; - - // Focus on high-complexity files - const complexFiles = Object.entries(index.files) - .filter(([_, file]) => (file as any).complexity > 10) - .slice(0, 20); // Limit to top 20 for API efficiency - - for (const [filePath, fileData] of complexFiles) { - const prompt = this.buildBugPredictionPrompt(filePath, fileData); - const response = await this.queryClaudeSDK(prompt, 'plan'); - - const prediction = this.parseBugPrediction(response, filePath); - if (prediction.bugs.length > 0) { - predictions.push(prediction); - } - } - - return predictions; - } - - /** - * Detect code smells and anti-patterns - */ - async detectCodeSmells(index: ProjectIndex): Promise { - const smells: CodeSmell[] = []; - - // Batch files for efficient analysis - const files = Object.entries(index.files); - const batches = this.createBatches(files, 10); - - for (const batch of batches) { - const prompt = this.buildCodeSmellPrompt(batch); - const response = await this.queryClaudeSDK(prompt, 'plan'); - - const batchSmells = this.parseCodeSmells(response); - smells.push(...batchSmells); - } - - return smells; - } - - /** - * Analyze security vulnerabilities - */ - async analyzeSecurity(index: ProjectIndex): Promise { - const issues: SecurityIssue[] = []; - - // Focus on files with authentication, database, or API code - const securityCriticalFiles = Object.entries(index.files) - .filter(([path]) => - path.includes('auth') || - path.includes('api') || - path.includes('database') || - path.includes('security') - ); - - for (const [filePath, fileData] of securityCriticalFiles) { - const prompt = this.buildSecurityPrompt(filePath, fileData); - const response = await this.queryClaudeSDK(prompt, 'plan'); - - const fileIssues = this.parseSecurityIssues(response, filePath); - issues.push(...fileIssues); - } - - return issues; - } - - /** - * Suggest refactoring opportunities - */ - async suggestRefactoring(index: ProjectIndex): Promise { - const suggestions: RefactoringSuggestion[] = []; - - // Identify files needing refactoring - const candidateFiles = Object.entries(index.files) - .filter(([_, file]) => { - const f = file as any; - return f.complexity > 8 || - f.functions.length > 10 || - f.classes.length > 5; - }) - .slice(0, 15); - - for (const [filePath, fileData] of candidateFiles) { - const prompt = this.buildRefactoringPrompt(filePath, fileData); - const response = await this.queryClaudeSDK(prompt, 'plan'); - - const suggestion = this.parseRefactoring(response, filePath); - if (suggestion.improvements.length > 0) { - suggestions.push(suggestion); - } - } - - return suggestions; - } - - /** - * Generate test suggestions for untested code - */ - async generateTestSuggestions(index: ProjectIndex): Promise { - const suggestions: TestSuggestion[] = []; - - // Find critical functions without tests - const criticalFunctions = this.identifyCriticalFunctions(index); - - for (const func of criticalFunctions.slice(0, 10)) { - const prompt = this.buildTestGenerationPrompt(func); - const response = await this.queryClaudeSDK(prompt, 'plan'); - - const testSuggestion = this.parseTestSuggestion(response, func); - suggestions.push(testSuggestion); - } - - return suggestions; - } - - /** - * Query Claude SDK with proper configuration - */ - private async queryClaudeSDK(prompt: string, mode: 'plan' | 'edit' = 'plan'): Promise { - try { - // Use Claude CLI in non-interactive mode with JSON output - const command = `claude -p "${prompt.replace(/"/g, '\\"')}" \ - --output-format json \ - --permission-mode ${mode} \ - --max-turns 1 \ - --append-system-prompt "You are an expert code analyzer. Provide structured JSON responses."`; - - const { stdout, stderr } = await execAsync(command, { - env: { - ...process.env, - ANTHROPIC_API_KEY: this.apiKey - }, - maxBuffer: 1024 * 1024 * 10 // 10MB buffer - }); - - if (stderr) { - logger.warn('Claude SDK warning:', stderr); - } - - // Parse JSON response - const response = JSON.parse(stdout); - return response.result || ''; - } catch (error) { - logger.error('Claude SDK error:', error); - // Fallback to mock response for development - return this.getMockResponse(prompt); - } - } - - /** - * Build prompts for various analyses - */ - private buildBugPredictionPrompt(filePath: string, fileData: FileIndex): string { - return `Analyze this code for potential bugs: - -File: ${filePath} -Complexity: ${(fileData as any).complexity || 0} -Functions: ${fileData.functions.length} -Classes: ${fileData.classes.length} - -Function details: -${JSON.stringify(fileData.functions.slice(0, 5), null, 2)} - -Identify potential bugs including: -1. Null/undefined reference errors -2. Type mismatches -3. Logic errors -4. Race conditions -5. Memory leaks -6. Unhandled exceptions - -Return a JSON object with structure: -{ - "bugs": [ - { - "type": "null_reference|type_error|logic_error|race_condition|memory_leak|exception", - "description": "Description of the bug", - "location": "Function or line reference", - "severity": "critical|high|medium|low", - "fix": "Suggested fix" - } - ], - "probability": 0.0 to 1.0, - "recommendations": ["List of recommendations"] -}`; - } - - private buildCodeSmellPrompt(files: [string, FileIndex][]): string { - const filesSummary = files.map(([path, data]) => ({ - path, - functions: data.functions.length, - classes: data.classes.length, - complexity: (data as any).complexity || 0 - })); - - return `Analyze these files for code smells: - -${JSON.stringify(filesSummary, null, 2)} - -Identify code smells including: -1. God classes (too many responsibilities) -2. Long methods (>50 lines) -3. Duplicate code -4. Dead code -5. Feature envy -6. Inappropriate intimacy - -Return JSON: -{ - "smells": [ - { - "type": "god_class|long_method|duplicate|dead_code|feature_envy|inappropriate_intimacy", - "file": "file path", - "description": "Description", - "impact": "high|medium|low", - "refactoring": "Suggested refactoring" - } - ] -}`; - } - - private buildSecurityPrompt(filePath: string, fileData: FileIndex): string { - return `Analyze this code for security vulnerabilities: - -File: ${filePath} -Imports: ${JSON.stringify(fileData.imports.slice(0, 10))} -Exports: ${JSON.stringify(fileData.exports.slice(0, 10))} - -Check for: -1. SQL injection vulnerabilities -2. XSS vulnerabilities -3. Authentication/authorization issues -4. Hardcoded secrets -5. Insecure dependencies -6. OWASP Top 10 issues - -Return JSON: -{ - "issues": [ - { - "type": "sql_injection|xss|auth|secrets|dependency|owasp", - "cwe": "CWE-XX", - "description": "Description", - "severity": "critical|high|medium|low", - "mitigation": "How to fix" - } - ] -}`; - } - - private buildRefactoringPrompt(filePath: string, fileData: FileIndex): string { - return `Suggest refactoring for this code: - -File: ${filePath} -Complexity: ${(fileData as any).complexity || 0} -Functions: ${fileData.functions.length} -Classes: ${fileData.classes.length} - -Suggest improvements for: -1. Reducing complexity -2. Improving readability -3. Better abstraction -4. Performance optimization -5. Design pattern application - -Return JSON: -{ - "improvements": [ - { - "type": "complexity|readability|abstraction|performance|pattern", - "description": "What to improve", - "current": "Current approach", - "suggested": "Better approach", - "benefit": "Expected benefit" - } - ], - "priority": "high|medium|low", - "estimatedEffort": "hours" -}`; - } - - private buildTestGenerationPrompt(func: CriticalFunction): string { - return `Generate comprehensive tests for this function: - -File: ${func.file} -Function: ${func.name} -Parameters: ${JSON.stringify(func.parameters)} -Complexity: ${func.complexity} - -Generate test cases covering: -1. Happy path -2. Edge cases -3. Error conditions -4. Boundary values -5. Null/undefined inputs - -Return JSON: -{ - "testCases": [ - { - "name": "Test case name", - "type": "happy|edge|error|boundary|null", - "input": "Input values", - "expected": "Expected output", - "assertion": "Test assertion code" - } - ], - "framework": "jest|mocha|pytest", - "coverage": "Estimated coverage percentage", - "setup": "Any required setup code" -}`; - } - - /** - * Parse Claude responses into structured data - */ - private parseBugPrediction(response: string, filePath: string): BugPrediction { - try { - const data = JSON.parse(response); - return { - file: filePath, - bugs: data.bugs || [], - probability: data.probability || 0.5, - severity: this.calculateSeverity(data.bugs), - recommendations: data.recommendations || [] - }; - } catch (error) { - return { - file: filePath, - bugs: [], - probability: 0, - severity: 'low', - recommendations: [] - }; - } - } - - private parseCodeSmells(response: string): CodeSmell[] { - try { - const data = JSON.parse(response); - return data.smells || []; - } catch (error) { - return []; - } - } - - private parseSecurityIssues(response: string, filePath: string): SecurityIssue[] { - try { - const data = JSON.parse(response); - return (data.issues || []).map((issue: any) => ({ - ...issue, - file: filePath - })); - } catch (error) { - return []; - } - } - - private parseRefactoring(response: string, filePath: string): RefactoringSuggestion { - try { - const data = JSON.parse(response); - return { - file: filePath, - improvements: data.improvements || [], - priority: data.priority || 'medium', - estimatedEffort: data.estimatedEffort || '4' - }; - } catch (error) { - return { - file: filePath, - improvements: [], - priority: 'low', - estimatedEffort: '0' - }; - } - } - - private parseTestSuggestion(response: string, func: CriticalFunction): TestSuggestion { - try { - const data = JSON.parse(response); - return { - function: func.name, - file: func.file, - testCases: data.testCases || [], - framework: data.framework || 'jest', - coverage: data.coverage || 80, - setup: data.setup || '' - }; - } catch (error) { - return { - function: func.name, - file: func.file, - testCases: [], - framework: 'jest', - coverage: 0, - setup: '' - }; - } - } - - /** - * Helper methods - */ - private createBatches(items: T[], batchSize: number): T[][] { - const batches: T[][] = []; - for (let i = 0; i < items.length; i += batchSize) { - batches.push(items.slice(i, i + batchSize)); - } - return batches; - } - - private identifyCriticalFunctions(index: ProjectIndex): CriticalFunction[] { - const functions: CriticalFunction[] = []; - - for (const [filePath, fileData] of Object.entries(index.files)) { - for (const func of fileData.functions) { - const funcObj = func as FunctionInfo; - if (funcObj.name && !funcObj.name.startsWith('test')) { - functions.push({ - name: funcObj.name, - file: filePath, - parameters: funcObj.parameters || [], - complexity: (funcObj as any).complexity || 5, - returnType: funcObj.returnType || 'unknown' - }); - } - } - } - - // Sort by complexity and return top critical functions - return functions - .sort((a, b) => b.complexity - a.complexity) - .slice(0, 20); - } - - private calculateSeverity(bugs: any[]): 'critical' | 'high' | 'medium' | 'low' { - if (!bugs || bugs.length === 0) return 'low'; - - const severities = bugs.map(b => b.severity); - if (severities.includes('critical')) return 'critical'; - if (severities.includes('high')) return 'high'; - if (severities.includes('medium')) return 'medium'; - return 'low'; - } - - private async generateSummary(analysis: AIAnalysis): Promise { - const stats = { - totalBugs: analysis.bugPredictions.reduce((acc, p) => acc + p.bugs.length, 0), - totalSmells: analysis.codeSmells.length, - totalSecurityIssues: analysis.securityIssues.length, - criticalIssues: analysis.securityIssues.filter(i => i.severity === 'critical').length, - testCoverage: analysis.testSuggestions.length - }; - - return `AI Analysis Summary: -- Predicted Bugs: ${stats.totalBugs} -- Code Smells: ${stats.totalSmells} -- Security Issues: ${stats.totalSecurityIssues} (${stats.criticalIssues} critical) -- Test Suggestions: ${stats.testCoverage} -- Overall Health: ${this.calculateHealthScore(stats)}/100`; - } - - private calculateHealthScore(stats: any): number { - let score = 100; - score -= stats.totalBugs * 2; - score -= stats.totalSmells * 1; - score -= stats.totalSecurityIssues * 3; - score -= stats.criticalIssues * 10; - score = Math.max(0, Math.min(100, score)); - return Math.round(score); - } - - /** - * Mock response for development when API is not available - */ - private getMockResponse(prompt: string): string { - if (prompt.includes('bug')) { - return JSON.stringify({ - bugs: [{ - type: 'null_reference', - description: 'Potential null reference in function', - location: 'line 42', - severity: 'medium', - fix: 'Add null check' - }], - probability: 0.7, - recommendations: ['Add input validation'] - }); - } - - if (prompt.includes('smell')) { - return JSON.stringify({ - smells: [{ - type: 'long_method', - file: 'example.ts', - description: 'Method exceeds 50 lines', - impact: 'medium', - refactoring: 'Extract smaller methods' - }] - }); - } - - if (prompt.includes('security')) { - return JSON.stringify({ - issues: [{ - type: 'sql_injection', - cwe: 'CWE-89', - description: 'Potential SQL injection', - severity: 'high', - mitigation: 'Use parameterized queries' - }] - }); - } - - return JSON.stringify({ result: 'Mock response' }); - } -} - -/** - * Type definitions for AI analysis - */ -export interface AIAnalysis { - bugPredictions: BugPrediction[]; - codeSmells: CodeSmell[]; - securityIssues: SecurityIssue[]; - refactoringSuggestions: RefactoringSuggestion[]; - testSuggestions: TestSuggestion[]; - architectureInsights: ArchitectureInsight[]; - performanceIssues: PerformanceIssue[]; - timestamp: string; - summary: string; -} - -export interface BugPrediction { - file: string; - bugs: Bug[]; - probability: number; - severity: 'critical' | 'high' | 'medium' | 'low'; - recommendations: string[]; -} - -export interface Bug { - type: string; - description: string; - location: string; - severity: string; - fix: string; -} - -export interface CodeSmell { - type: string; - file: string; - description: string; - impact: 'high' | 'medium' | 'low'; - refactoring: string; -} - -export interface SecurityIssue { - type: string; - file: string; - cwe: string; - description: string; - severity: 'critical' | 'high' | 'medium' | 'low'; - mitigation: string; -} - -export interface RefactoringSuggestion { - file: string; - improvements: Improvement[]; - priority: 'high' | 'medium' | 'low'; - estimatedEffort: string; -} - -export interface Improvement { - type: string; - description: string; - current: string; - suggested: string; - benefit: string; -} - -export interface TestSuggestion { - function: string; - file: string; - testCases: TestCase[]; - framework: string; - coverage: number; - setup: string; -} - -export interface TestCase { - name: string; - type: string; - input: string; - expected: string; - assertion: string; -} - -export interface ArchitectureInsight { - type: string; - description: string; - impact: string; - recommendation: string; -} - -export interface PerformanceIssue { - file: string; - type: string; - description: string; - impact: string; - optimization: string; -} - -interface CriticalFunction { - name: string; - file: string; - parameters: any[]; - complexity: number; - returnType: string; +import { ProjectIndex, FileIndex, FunctionInfo} from '../types'; +import { exec } from 'child_process'; +import { promisify } from 'util'; +import logger from '../utils/logger'; + +const execAsync = promisify(exec); + +/** + * AI-powered code analyzer using Claude Code SDK + * Provides bug prediction, code smell detection, test generation, and more + */ +export class ClaudeCodeAnalyzer { + private apiKey: string; + private model: string; + + constructor() { + this.apiKey = process.env.ANTHROPIC_API_KEY || ''; + this.model = process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805'; + + if (!this.apiKey) { + logger.warn('Warning: ANTHROPIC_API_KEY not set. AI features will be limited.'); + } + } + + /** + * Comprehensive AI analysis of entire codebase + */ + async analyzeCodebase(index: ProjectIndex): Promise { + const analysis: AIAnalysis = { + bugPredictions: [], + codeSmells: [], + securityIssues: [], + refactoringSuggestions: [], + testSuggestions: [], + architectureInsights: [], + performanceIssues: [], + timestamp: new Date().toISOString(), + summary: '' + }; + + try { + // Run analyses in parallel for efficiency + const [bugs, smells, security, refactoring, tests] = await Promise.all([ + this.predictBugs(index), + this.detectCodeSmells(index), + this.analyzeSecurity(index), + this.suggestRefactoring(index), + this.generateTestSuggestions(index) + ]); + + analysis.bugPredictions = bugs; + analysis.codeSmells = smells; + analysis.securityIssues = security; + analysis.refactoringSuggestions = refactoring; + analysis.testSuggestions = tests; + + // Generate executive summary + analysis.summary = await this.generateSummary(analysis); + + return analysis; + } catch (error) { + logger.error('AI analysis error:', error); + throw error; + } + } + + /** + * Predict potential bugs using Claude's pattern recognition + */ + async predictBugs(index: ProjectIndex): Promise { + const predictions: BugPrediction[] = []; + + // Focus on high-complexity files + const complexFiles = Object.entries(index.files) + .filter(([_, file]) => (file as any).complexity > 10) + .slice(0, 20); // Limit to top 20 for API efficiency + + for (const [filePath, fileData] of complexFiles) { + const prompt = this.buildBugPredictionPrompt(filePath, fileData); + const response = await this.queryClaudeSDK(prompt, 'plan'); + + const prediction = this.parseBugPrediction(response, filePath); + if (prediction.bugs.length > 0) { + predictions.push(prediction); + } + } + + return predictions; + } + + /** + * Detect code smells and anti-patterns + */ + async detectCodeSmells(index: ProjectIndex): Promise { + const smells: CodeSmell[] = []; + + // Batch files for efficient analysis + const files = Object.entries(index.files); + const batches = this.createBatches(files, 10); + + for (const batch of batches) { + const prompt = this.buildCodeSmellPrompt(batch); + const response = await this.queryClaudeSDK(prompt, 'plan'); + + const batchSmells = this.parseCodeSmells(response); + smells.push(...batchSmells); + } + + return smells; + } + + /** + * Analyze security vulnerabilities + */ + async analyzeSecurity(index: ProjectIndex): Promise { + const issues: SecurityIssue[] = []; + + // Focus on files with authentication, database, or API code + const securityCriticalFiles = Object.entries(index.files) + .filter(([path]) => + path.includes('auth') || + path.includes('api') || + path.includes('database') || + path.includes('security') + ); + + for (const [filePath, fileData] of securityCriticalFiles) { + const prompt = this.buildSecurityPrompt(filePath, fileData); + const response = await this.queryClaudeSDK(prompt, 'plan'); + + const fileIssues = this.parseSecurityIssues(response, filePath); + issues.push(...fileIssues); + } + + return issues; + } + + /** + * Suggest refactoring opportunities + */ + async suggestRefactoring(index: ProjectIndex): Promise { + const suggestions: RefactoringSuggestion[] = []; + + // Identify files needing refactoring + const candidateFiles = Object.entries(index.files) + .filter(([_, file]) => { + const f = file as any; + return f.complexity > 8 || + f.functions.length > 10 || + f.classes.length > 5; + }) + .slice(0, 15); + + for (const [filePath, fileData] of candidateFiles) { + const prompt = this.buildRefactoringPrompt(filePath, fileData); + const response = await this.queryClaudeSDK(prompt, 'plan'); + + const suggestion = this.parseRefactoring(response, filePath); + if (suggestion.improvements.length > 0) { + suggestions.push(suggestion); + } + } + + return suggestions; + } + + /** + * Generate test suggestions for untested code + */ + async generateTestSuggestions(index: ProjectIndex): Promise { + const suggestions: TestSuggestion[] = []; + + // Find critical functions without tests + const criticalFunctions = this.identifyCriticalFunctions(index); + + for (const func of criticalFunctions.slice(0, 10)) { + const prompt = this.buildTestGenerationPrompt(func); + const response = await this.queryClaudeSDK(prompt, 'plan'); + + const testSuggestion = this.parseTestSuggestion(response, func); + suggestions.push(testSuggestion); + } + + return suggestions; + } + + /** + * Query Claude SDK with proper configuration + */ + private async queryClaudeSDK(prompt: string, mode: 'plan' | 'edit' = 'plan'): Promise { + try { + // Use Claude CLI in non-interactive mode with JSON output + const command = `claude -p "${prompt.replace(/"/g, '\\"')}" \ + --output-format json \ + --permission-mode ${mode} \ + --max-turns 1 \ + --append-system-prompt "You are an expert code analyzer. Provide structured JSON responses."`; + + const { stdout, stderr } = await execAsync(command, { + env: { + ...process.env, + ANTHROPIC_API_KEY: this.apiKey + }, + maxBuffer: 1024 * 1024 * 10 // 10MB buffer + }); + + if (stderr) { + logger.warn('Claude SDK warning:', stderr); + } + + // Parse JSON response + const response = JSON.parse(stdout); + return response.result || ''; + } catch (error) { + logger.error('Claude SDK error:', error); + // Fallback to mock response for development + return this.getMockResponse(prompt); + } + } + + /** + * Build prompts for various analyses + */ + private buildBugPredictionPrompt(filePath: string, fileData: FileIndex): string { + return `Analyze this code for potential bugs: + +File: ${filePath} +Complexity: ${(fileData as any).complexity || 0} +Functions: ${fileData.functions.length} +Classes: ${fileData.classes.length} + +Function details: +${JSON.stringify(fileData.functions.slice(0, 5), null, 2)} + +Identify potential bugs including: +1. Null/undefined reference errors +2. Type mismatches +3. Logic errors +4. Race conditions +5. Memory leaks +6. Unhandled exceptions + +Return a JSON object with structure: +{ + "bugs": [ + { + "type": "null_reference|type_error|logic_error|race_condition|memory_leak|exception", + "description": "Description of the bug", + "location": "Function or line reference", + "severity": "critical|high|medium|low", + "fix": "Suggested fix" + } + ], + "probability": 0.0 to 1.0, + "recommendations": ["List of recommendations"] +}`; + } + + private buildCodeSmellPrompt(files: [string, FileIndex][]): string { + const filesSummary = files.map(([path, data]) => ({ + path, + functions: data.functions.length, + classes: data.classes.length, + complexity: (data as any).complexity || 0 + })); + + return `Analyze these files for code smells: + +${JSON.stringify(filesSummary, null, 2)} + +Identify code smells including: +1. God classes (too many responsibilities) +2. Long methods (>50 lines) +3. Duplicate code +4. Dead code +5. Feature envy +6. Inappropriate intimacy + +Return JSON: +{ + "smells": [ + { + "type": "god_class|long_method|duplicate|dead_code|feature_envy|inappropriate_intimacy", + "file": "file path", + "description": "Description", + "impact": "high|medium|low", + "refactoring": "Suggested refactoring" + } + ] +}`; + } + + private buildSecurityPrompt(filePath: string, fileData: FileIndex): string { + return `Analyze this code for security vulnerabilities: + +File: ${filePath} +Imports: ${JSON.stringify(fileData.imports.slice(0, 10))} +Exports: ${JSON.stringify(fileData.exports.slice(0, 10))} + +Check for: +1. SQL injection vulnerabilities +2. XSS vulnerabilities +3. Authentication/authorization issues +4. Hardcoded secrets +5. Insecure dependencies +6. OWASP Top 10 issues + +Return JSON: +{ + "issues": [ + { + "type": "sql_injection|xss|auth|secrets|dependency|owasp", + "cwe": "CWE-XX", + "description": "Description", + "severity": "critical|high|medium|low", + "mitigation": "How to fix" + } + ] +}`; + } + + private buildRefactoringPrompt(filePath: string, fileData: FileIndex): string { + return `Suggest refactoring for this code: + +File: ${filePath} +Complexity: ${(fileData as any).complexity || 0} +Functions: ${fileData.functions.length} +Classes: ${fileData.classes.length} + +Suggest improvements for: +1. Reducing complexity +2. Improving readability +3. Better abstraction +4. Performance optimization +5. Design pattern application + +Return JSON: +{ + "improvements": [ + { + "type": "complexity|readability|abstraction|performance|pattern", + "description": "What to improve", + "current": "Current approach", + "suggested": "Better approach", + "benefit": "Expected benefit" + } + ], + "priority": "high|medium|low", + "estimatedEffort": "hours" +}`; + } + + private buildTestGenerationPrompt(func: CriticalFunction): string { + return `Generate comprehensive tests for this function: + +File: ${func.file} +Function: ${func.name} +Parameters: ${JSON.stringify(func.parameters)} +Complexity: ${func.complexity} + +Generate test cases covering: +1. Happy path +2. Edge cases +3. Error conditions +4. Boundary values +5. Null/undefined inputs + +Return JSON: +{ + "testCases": [ + { + "name": "Test case name", + "type": "happy|edge|error|boundary|null", + "input": "Input values", + "expected": "Expected output", + "assertion": "Test assertion code" + } + ], + "framework": "jest|mocha|pytest", + "coverage": "Estimated coverage percentage", + "setup": "Any required setup code" +}`; + } + + /** + * Parse Claude responses into structured data + */ + private parseBugPrediction(response: string, filePath: string): BugPrediction { + try { + const data = JSON.parse(response); + return { + file: filePath, + bugs: data.bugs || [], + probability: data.probability || 0.5, + severity: this.calculateSeverity(data.bugs), + recommendations: data.recommendations || [] + }; + } catch (error) { + return { + file: filePath, + bugs: [], + probability: 0, + severity: 'low', + recommendations: [] + }; + } + } + + private parseCodeSmells(response: string): CodeSmell[] { + try { + const data = JSON.parse(response); + return data.smells || []; + } catch (error) { + return []; + } + } + + private parseSecurityIssues(response: string, filePath: string): SecurityIssue[] { + try { + const data = JSON.parse(response); + return (data.issues || []).map((issue: any) => ({ + ...issue, + file: filePath + })); + } catch (error) { + return []; + } + } + + private parseRefactoring(response: string, filePath: string): RefactoringSuggestion { + try { + const data = JSON.parse(response); + return { + file: filePath, + improvements: data.improvements || [], + priority: data.priority || 'medium', + estimatedEffort: data.estimatedEffort || '4' + }; + } catch (error) { + return { + file: filePath, + improvements: [], + priority: 'low', + estimatedEffort: '0' + }; + } + } + + private parseTestSuggestion(response: string, func: CriticalFunction): TestSuggestion { + try { + const data = JSON.parse(response); + return { + function: func.name, + file: func.file, + testCases: data.testCases || [], + framework: data.framework || 'jest', + coverage: data.coverage || 80, + setup: data.setup || '' + }; + } catch (error) { + return { + function: func.name, + file: func.file, + testCases: [], + framework: 'jest', + coverage: 0, + setup: '' + }; + } + } + + /** + * Helper methods + */ + private createBatches(items: T[], batchSize: number): T[][] { + const batches: T[][] = []; + for (let i = 0; i < items.length; i += batchSize) { + batches.push(items.slice(i, i + batchSize)); + } + return batches; + } + + private identifyCriticalFunctions(index: ProjectIndex): CriticalFunction[] { + const functions: CriticalFunction[] = []; + + for (const [filePath, fileData] of Object.entries(index.files)) { + for (const func of fileData.functions) { + const funcObj = func as FunctionInfo; + if (funcObj.name && !funcObj.name.startsWith('test')) { + functions.push({ + name: funcObj.name, + file: filePath, + parameters: funcObj.parameters || [], + complexity: (funcObj as any).complexity || 5, + returnType: funcObj.returnType || 'unknown' + }); + } + } + } + + // Sort by complexity and return top critical functions + return functions + .sort((a, b) => b.complexity - a.complexity) + .slice(0, 20); + } + + private calculateSeverity(bugs: any[]): 'critical' | 'high' | 'medium' | 'low' { + if (!bugs || bugs.length === 0) return 'low'; + + const severities = bugs.map(b => b.severity); + if (severities.includes('critical')) return 'critical'; + if (severities.includes('high')) return 'high'; + if (severities.includes('medium')) return 'medium'; + return 'low'; + } + + private async generateSummary(analysis: AIAnalysis): Promise { + const stats = { + totalBugs: analysis.bugPredictions.reduce((acc, p) => acc + p.bugs.length, 0), + totalSmells: analysis.codeSmells.length, + totalSecurityIssues: analysis.securityIssues.length, + criticalIssues: analysis.securityIssues.filter(i => i.severity === 'critical').length, + testCoverage: analysis.testSuggestions.length + }; + + return `AI Analysis Summary: +- Predicted Bugs: ${stats.totalBugs} +- Code Smells: ${stats.totalSmells} +- Security Issues: ${stats.totalSecurityIssues} (${stats.criticalIssues} critical) +- Test Suggestions: ${stats.testCoverage} +- Overall Health: ${this.calculateHealthScore(stats)}/100`; + } + + private calculateHealthScore(stats: any): number { + let score = 100; + score -= stats.totalBugs * 2; + score -= stats.totalSmells * 1; + score -= stats.totalSecurityIssues * 3; + score -= stats.criticalIssues * 10; + score = Math.max(0, Math.min(100, score)); + return Math.round(score); + } + + /** + * Mock response for development when API is not available + */ + private getMockResponse(prompt: string): string { + if (prompt.includes('bug')) { + return JSON.stringify({ + bugs: [{ + type: 'null_reference', + description: 'Potential null reference in function', + location: 'line 42', + severity: 'medium', + fix: 'Add null check' + }], + probability: 0.7, + recommendations: ['Add input validation'] + }); + } + + if (prompt.includes('smell')) { + return JSON.stringify({ + smells: [{ + type: 'long_method', + file: 'example.ts', + description: 'Method exceeds 50 lines', + impact: 'medium', + refactoring: 'Extract smaller methods' + }] + }); + } + + if (prompt.includes('security')) { + return JSON.stringify({ + issues: [{ + type: 'sql_injection', + cwe: 'CWE-89', + description: 'Potential SQL injection', + severity: 'high', + mitigation: 'Use parameterized queries' + }] + }); + } + + return JSON.stringify({ result: 'Mock response' }); + } +} + +/** + * Type definitions for AI analysis + */ +export interface AIAnalysis { + bugPredictions: BugPrediction[]; + codeSmells: CodeSmell[]; + securityIssues: SecurityIssue[]; + refactoringSuggestions: RefactoringSuggestion[]; + testSuggestions: TestSuggestion[]; + architectureInsights: ArchitectureInsight[]; + performanceIssues: PerformanceIssue[]; + timestamp: string; + summary: string; +} + +export interface BugPrediction { + file: string; + bugs: Bug[]; + probability: number; + severity: 'critical' | 'high' | 'medium' | 'low'; + recommendations: string[]; +} + +export interface Bug { + type: string; + description: string; + location: string; + severity: string; + fix: string; +} + +export interface CodeSmell { + type: string; + file: string; + description: string; + impact: 'high' | 'medium' | 'low'; + refactoring: string; +} + +export interface SecurityIssue { + type: string; + file: string; + cwe: string; + description: string; + severity: 'critical' | 'high' | 'medium' | 'low'; + mitigation: string; +} + +export interface RefactoringSuggestion { + file: string; + improvements: Improvement[]; + priority: 'high' | 'medium' | 'low'; + estimatedEffort: string; +} + +export interface Improvement { + type: string; + description: string; + current: string; + suggested: string; + benefit: string; +} + +export interface TestSuggestion { + function: string; + file: string; + testCases: TestCase[]; + framework: string; + coverage: number; + setup: string; +} + +export interface TestCase { + name: string; + type: string; + input: string; + expected: string; + assertion: string; +} + +export interface ArchitectureInsight { + type: string; + description: string; + impact: string; + recommendation: string; +} + +export interface PerformanceIssue { + file: string; + type: string; + description: string; + impact: string; + optimization: string; +} + +interface CriticalFunction { + name: string; + file: string; + parameters: any[]; + complexity: number; + returnType: string; } \ No newline at end of file diff --git a/src/ai/hybrid-analyzer.ts b/src/ai/hybrid-analyzer.ts index cca936f..33feea6 100644 --- a/src/ai/hybrid-analyzer.ts +++ b/src/ai/hybrid-analyzer.ts @@ -1,476 +1,476 @@ -import { ProjectIndex, FileIndex } from '../types'; -import { ClaudeCodeAnalyzer, AIAnalysis } from './claude-analyzer'; -import { spawn } from 'child_process'; -import * as path from 'path'; -import * as fs from 'fs/promises'; -import logger from '../utils/logger'; - -/** - * Hybrid AI Analyzer that routes to language-specific analyzers - * Uses Python SDK for Python code, TypeScript SDK for JS/TS - */ -export class HybridAIAnalyzer { - private tsAnalyzer: ClaudeCodeAnalyzer; - private pythonAnalyzerPath: string; - - constructor() { - this.tsAnalyzer = new ClaudeCodeAnalyzer(); - this.pythonAnalyzerPath = path.join(__dirname, 'python', 'claude_analyzer.py'); - } - - /** - * Analyze codebase with language-specific analyzers - */ - async analyzeCodebase(index: ProjectIndex): Promise { - logger.info('Starting hybrid AI analysis...'); - - // Separate files by language - const filesByLanguage = this.categorizeFilesByLanguage(index); - - // Run analyses in parallel for different languages - const [pythonAnalysis, jsAnalysis, tsAnalysis, otherAnalysis] = await Promise.all([ - this.analyzePythonFiles(filesByLanguage.python, index), - this.analyzeJavaScriptFiles(filesByLanguage.javascript, index), - this.analyzeTypeScriptFiles(filesByLanguage.typescript, index), - this.analyzeOtherFiles(filesByLanguage.other, index) - ]); - - // Merge results - return this.mergeAnalyses([pythonAnalysis, jsAnalysis, tsAnalysis, otherAnalysis]); - } - - /** - * Specialized analysis for Skills repository - */ - async analyzeSkillsRepository(skillsPath: string): Promise { - logger.info(`Analyzing skills repository at ${skillsPath}...`); - - try { - // Use Python analyzer for skills repo - const result = await this.runPythonAnalyzer('analyze-skills', skillsPath); - - // Parse and enhance results - const skillsAnalysis = JSON.parse(result) as SkillsAnalysis; - - // Add recommendations based on analysis - skillsAnalysis.recommendations = this.generateSkillsRecommendations(skillsAnalysis); - - return skillsAnalysis; - } catch (error) { - logger.error('Skills analysis error:', error); - throw error; - } - } - - /** - * Categorize files by programming language - */ - private categorizeFilesByLanguage(index: ProjectIndex): FilesByLanguage { - const categorized: FilesByLanguage = { - python: [], - javascript: [], - typescript: [], - go: [], - other: [] - }; - - for (const [filePath, fileData] of Object.entries(index.files)) { - const entry = { path: filePath, data: fileData }; - - switch (fileData.language.toLowerCase()) { - case 'python': - categorized.python.push(entry); - break; - case 'javascript': - case 'jsx': - categorized.javascript.push(entry); - break; - case 'typescript': - case 'tsx': - categorized.typescript.push(entry); - break; - case 'go': - categorized.go.push(entry); - break; - default: - categorized.other.push(entry); - } - } - - return categorized; - } - - /** - * Analyze Python files using Python SDK - */ - private async analyzePythonFiles( - files: FileEntry[], - index: ProjectIndex - ): Promise { - if (files.length === 0) { - return this.createEmptyAnalysis(); - } - - logger.info(`Analyzing ${files.length} Python files with Python SDK...`); - - try { - // Create temporary index with only Python files - const pythonIndex = { - ...index, - files: Object.fromEntries(files.map(f => [f.path, f.data])) - }; - - // Save to temp file for Python analyzer - const tempIndexPath = `/tmp/python-index-${Date.now()}.json`; - await fs.writeFile(tempIndexPath, JSON.stringify(pythonIndex)); - - // Run Python analyzer - const result = await this.runPythonAnalyzer('analyze-index', tempIndexPath); - - // Clean up temp file - await fs.unlink(tempIndexPath).catch(() => {}); - - // Parse Python results - const pythonResults = JSON.parse(result); - - // Convert to our AIAnalysis format - return this.convertPythonAnalysisToAI(pythonResults); - } catch (error) { - logger.error('Python analysis error:', error); - // Fallback to TypeScript analyzer - return this.tsAnalyzer.analyzeCodebase({ - ...index, - files: Object.fromEntries(files.map(f => [f.path, f.data])) - }); - } - } - - /** - * Analyze JavaScript files using TypeScript SDK - */ - private async analyzeJavaScriptFiles( - files: FileEntry[], - index: ProjectIndex - ): Promise { - if (files.length === 0) { - return this.createEmptyAnalysis(); - } - - logger.info(`Analyzing ${files.length} JavaScript files with TypeScript SDK...`); - - const jsIndex = { - ...index, - files: Object.fromEntries(files.map(f => [f.path, f.data])) - }; - - return this.tsAnalyzer.analyzeCodebase(jsIndex); - } - - /** - * Analyze TypeScript files using TypeScript SDK - */ - private async analyzeTypeScriptFiles( - files: FileEntry[], - index: ProjectIndex - ): Promise { - if (files.length === 0) { - return this.createEmptyAnalysis(); - } - - logger.info(`Analyzing ${files.length} TypeScript files with TypeScript SDK...`); - - const tsIndex = { - ...index, - files: Object.fromEntries(files.map(f => [f.path, f.data])) - }; - - return this.tsAnalyzer.analyzeCodebase(tsIndex); - } - - /** - * Analyze other files with generic analyzer - */ - private async analyzeOtherFiles( - files: FileEntry[], - index: ProjectIndex - ): Promise { - if (files.length === 0) { - return this.createEmptyAnalysis(); - } - - logger.info(`Analyzing ${files.length} other files...`); - - const otherIndex = { - ...index, - files: Object.fromEntries(files.map(f => [f.path, f.data])) - }; - - return this.tsAnalyzer.analyzeCodebase(otherIndex); - } - - /** - * Run Python analyzer subprocess - */ - private runPythonAnalyzer(command: string, arg: string): Promise { - return new Promise((resolve, reject) => { - const pythonProcess = spawn('python3', [this.pythonAnalyzerPath, command, arg], { - env: { - ...process.env, - PYTHONUNBUFFERED: '1' - } - } as any); - - let stdout = ''; - let stderr = ''; - - pythonProcess.stdout?.on('data', (data: Buffer) => { - stdout += data.toString(); - }); - - pythonProcess.stderr?.on('data', (data: Buffer) => { - stderr += data.toString(); - logger.warn('Python analyzer warning:', data.toString()); - }); - - pythonProcess.on('close', (code: number | null) => { - if (code !== 0) { - reject(new Error(`Python analyzer exited with code ${code}: ${stderr}`)); - } else { - resolve(stdout); - } - }); - - pythonProcess.on('error', (error: Error) => { - reject(error); - }); - }); - } - - /** - * Convert Python analysis format to AIAnalysis - */ - private convertPythonAnalysisToAI(pythonResults: any[]): AIAnalysis { - const analysis: AIAnalysis = this.createEmptyAnalysis(); - - for (const fileAnalysis of pythonResults) { - // Map bugs - if (fileAnalysis.bugs && fileAnalysis.bugs.length > 0) { - analysis.bugPredictions.push({ - file: fileAnalysis.file, - bugs: fileAnalysis.bugs, - probability: 0.75, - severity: this.calculateSeverity(fileAnalysis.bugs), - recommendations: [] - }); - } - - // Map code smells - if (fileAnalysis.code_smells) { - analysis.codeSmells.push(...fileAnalysis.code_smells); - } - - // Map security issues - if (fileAnalysis.security_issues) { - analysis.securityIssues.push(...fileAnalysis.security_issues.map((issue: any) => ({ - ...issue, - file: fileAnalysis.file - }))); - } - - // Map refactoring suggestions - if (fileAnalysis.refactoring_suggestions && fileAnalysis.refactoring_suggestions.length > 0) { - analysis.refactoringSuggestions.push({ - file: fileAnalysis.file, - improvements: fileAnalysis.refactoring_suggestions, - priority: 'medium', - estimatedEffort: '2' - }); - } - - // Python-specific: Type hints and async issues - if (fileAnalysis.type_hints_missing && fileAnalysis.type_hints_missing.length > 0) { - analysis.codeSmells.push({ - type: 'missing_type_hints', - file: fileAnalysis.file, - description: `Missing type hints: ${fileAnalysis.type_hints_missing.join(', ')}`, - impact: 'medium', - refactoring: 'Add type hints for better type safety' - }); - } - - if (fileAnalysis.async_antipatterns && fileAnalysis.async_antipatterns.length > 0) { - analysis.performanceIssues.push({ - file: fileAnalysis.file, - type: 'async_antipattern', - description: fileAnalysis.async_antipatterns.join('; '), - impact: 'High - can cause deadlocks or poor performance', - optimization: 'Fix async/await patterns' - }); - } - } - - analysis.timestamp = new Date().toISOString(); - analysis.summary = this.generateSummary(analysis); - - return analysis; - } - - /** - * Merge multiple analyses - */ - private mergeAnalyses(analyses: AIAnalysis[]): AIAnalysis { - const merged: AIAnalysis = this.createEmptyAnalysis(); - - for (const analysis of analyses) { - merged.bugPredictions.push(...analysis.bugPredictions); - merged.codeSmells.push(...analysis.codeSmells); - merged.securityIssues.push(...analysis.securityIssues); - merged.refactoringSuggestions.push(...analysis.refactoringSuggestions); - merged.testSuggestions.push(...analysis.testSuggestions); - merged.architectureInsights.push(...analysis.architectureInsights); - merged.performanceIssues.push(...analysis.performanceIssues); - } - - merged.timestamp = new Date().toISOString(); - merged.summary = this.generateSummary(merged); - - return merged; - } - - /** - * Create empty analysis structure - */ - private createEmptyAnalysis(): AIAnalysis { - return { - bugPredictions: [], - codeSmells: [], - securityIssues: [], - refactoringSuggestions: [], - testSuggestions: [], - architectureInsights: [], - performanceIssues: [], - timestamp: new Date().toISOString(), - summary: '' - }; - } - - /** - * Calculate severity from bugs - */ - private calculateSeverity(bugs: any[]): 'critical' | 'high' | 'medium' | 'low' { - const severities = bugs.map(b => b.severity || 'low'); - if (severities.includes('critical')) return 'critical'; - if (severities.includes('high')) return 'high'; - if (severities.includes('medium')) return 'medium'; - return 'low'; - } - - /** - * Generate analysis summary - */ - private generateSummary(analysis: AIAnalysis): string { - const stats = { - totalBugs: analysis.bugPredictions.reduce((acc, p) => acc + p.bugs.length, 0), - totalSmells: analysis.codeSmells.length, - totalSecurityIssues: analysis.securityIssues.length, - criticalIssues: analysis.securityIssues.filter(i => i.severity === 'critical').length, - pythonIssues: analysis.bugPredictions.filter(p => p.file.endsWith('.py')).length, - jstsIssues: analysis.bugPredictions.filter(p => - p.file.endsWith('.js') || p.file.endsWith('.ts') - ).length - }; - - return `Hybrid AI Analysis Summary: -- Total Bugs Predicted: ${stats.totalBugs} -- Code Smells: ${stats.totalSmells} -- Security Issues: ${stats.totalSecurityIssues} (${stats.criticalIssues} critical) -- Python-specific Issues: ${stats.pythonIssues} -- JavaScript/TypeScript Issues: ${stats.jstsIssues} -- Overall Health Score: ${this.calculateHealthScore(stats)}/100 - -Language-Specific Insights: -- Python: Enhanced analysis with Python SDK for better accuracy -- TypeScript/JavaScript: Native TypeScript SDK analysis -- Cross-language: Dependency and integration analysis`; - } - - /** - * Calculate overall health score - */ - private calculateHealthScore(stats: any): number { - let score = 100; - score -= stats.totalBugs * 2; - score -= stats.totalSmells * 1; - score -= stats.totalSecurityIssues * 3; - score -= stats.criticalIssues * 10; - return Math.max(0, Math.min(100, Math.round(score))); - } - - /** - * Generate recommendations for skills repository - */ - private generateSkillsRecommendations(analysis: SkillsAnalysis): string[] { - const recommendations: string[] = []; - - if (analysis.security_vulnerabilities.length > 0) { - recommendations.push('CRITICAL: Fix security vulnerabilities immediately in production skills'); - } - - if (analysis.containerization_issues.length > 5) { - recommendations.push('Standardize containerization practices across all skills'); - } - - if (analysis.missing_tests.length > 0) { - recommendations.push('Add unit tests for critical skill functions'); - } - - if (analysis.performance_bottlenecks.length > 0) { - recommendations.push('Optimize async operations and database queries in skills'); - } - - recommendations.push('Consider implementing a skill template for consistency'); - recommendations.push('Add monitoring and observability to containerized skills'); - - return recommendations; - } -} - -/** - * Type definitions - */ -interface FilesByLanguage { - python: FileEntry[]; - javascript: FileEntry[]; - typescript: FileEntry[]; - go: FileEntry[]; - other: FileEntry[]; -} - -interface FileEntry { - path: string; - data: FileIndex; -} - -export interface SkillsAnalysis { - total_skills: number; - analyzed_skills: Array<{ - file: string; - bugs_found: number; - security_issues: number; - code_smells: number; - critical_issues: any[]; - }>; - common_issues: string[]; - security_vulnerabilities: any[]; - performance_bottlenecks: string[]; - missing_tests: string[]; - containerization_issues: string[]; - best_practices_violations: string[]; - summary: string; - recommendations?: string[]; -} - -/** - * Export for use in API - */ +import { ProjectIndex, FileIndex } from '../types'; +import { ClaudeCodeAnalyzer, AIAnalysis } from './claude-analyzer'; +import { spawn } from 'child_process'; +import * as path from 'path'; +import * as fs from 'fs/promises'; +import logger from '../utils/logger'; + +/** + * Hybrid AI Analyzer that routes to language-specific analyzers + * Uses Python SDK for Python code, TypeScript SDK for JS/TS + */ +export class HybridAIAnalyzer { + private tsAnalyzer: ClaudeCodeAnalyzer; + private pythonAnalyzerPath: string; + + constructor() { + this.tsAnalyzer = new ClaudeCodeAnalyzer(); + this.pythonAnalyzerPath = path.join(__dirname, 'python', 'claude_analyzer.py'); + } + + /** + * Analyze codebase with language-specific analyzers + */ + async analyzeCodebase(index: ProjectIndex): Promise { + logger.info('Starting hybrid AI analysis...'); + + // Separate files by language + const filesByLanguage = this.categorizeFilesByLanguage(index); + + // Run analyses in parallel for different languages + const [pythonAnalysis, jsAnalysis, tsAnalysis, otherAnalysis] = await Promise.all([ + this.analyzePythonFiles(filesByLanguage.python, index), + this.analyzeJavaScriptFiles(filesByLanguage.javascript, index), + this.analyzeTypeScriptFiles(filesByLanguage.typescript, index), + this.analyzeOtherFiles(filesByLanguage.other, index) + ]); + + // Merge results + return this.mergeAnalyses([pythonAnalysis, jsAnalysis, tsAnalysis, otherAnalysis]); + } + + /** + * Specialized analysis for Skills repository + */ + async analyzeSkillsRepository(skillsPath: string): Promise { + logger.info(`Analyzing skills repository at ${skillsPath}...`); + + try { + // Use Python analyzer for skills repo + const result = await this.runPythonAnalyzer('analyze-skills', skillsPath); + + // Parse and enhance results + const skillsAnalysis = JSON.parse(result) as SkillsAnalysis; + + // Add recommendations based on analysis + skillsAnalysis.recommendations = this.generateSkillsRecommendations(skillsAnalysis); + + return skillsAnalysis; + } catch (error) { + logger.error('Skills analysis error:', error); + throw error; + } + } + + /** + * Categorize files by programming language + */ + private categorizeFilesByLanguage(index: ProjectIndex): FilesByLanguage { + const categorized: FilesByLanguage = { + python: [], + javascript: [], + typescript: [], + go: [], + other: [] + }; + + for (const [filePath, fileData] of Object.entries(index.files)) { + const entry = { path: filePath, data: fileData }; + + switch (fileData.language.toLowerCase()) { + case 'python': + categorized.python.push(entry); + break; + case 'javascript': + case 'jsx': + categorized.javascript.push(entry); + break; + case 'typescript': + case 'tsx': + categorized.typescript.push(entry); + break; + case 'go': + categorized.go.push(entry); + break; + default: + categorized.other.push(entry); + } + } + + return categorized; + } + + /** + * Analyze Python files using Python SDK + */ + private async analyzePythonFiles( + files: FileEntry[], + index: ProjectIndex + ): Promise { + if (files.length === 0) { + return this.createEmptyAnalysis(); + } + + logger.info(`Analyzing ${files.length} Python files with Python SDK...`); + + try { + // Create temporary index with only Python files + const pythonIndex = { + ...index, + files: Object.fromEntries(files.map(f => [f.path, f.data])) + }; + + // Save to temp file for Python analyzer + const tempIndexPath = `/tmp/python-index-${Date.now()}.json`; + await fs.writeFile(tempIndexPath, JSON.stringify(pythonIndex)); + + // Run Python analyzer + const result = await this.runPythonAnalyzer('analyze-index', tempIndexPath); + + // Clean up temp file + await fs.unlink(tempIndexPath).catch(() => {}); + + // Parse Python results + const pythonResults = JSON.parse(result); + + // Convert to our AIAnalysis format + return this.convertPythonAnalysisToAI(pythonResults); + } catch (error) { + logger.error('Python analysis error:', error); + // Fallback to TypeScript analyzer + return this.tsAnalyzer.analyzeCodebase({ + ...index, + files: Object.fromEntries(files.map(f => [f.path, f.data])) + }); + } + } + + /** + * Analyze JavaScript files using TypeScript SDK + */ + private async analyzeJavaScriptFiles( + files: FileEntry[], + index: ProjectIndex + ): Promise { + if (files.length === 0) { + return this.createEmptyAnalysis(); + } + + logger.info(`Analyzing ${files.length} JavaScript files with TypeScript SDK...`); + + const jsIndex = { + ...index, + files: Object.fromEntries(files.map(f => [f.path, f.data])) + }; + + return this.tsAnalyzer.analyzeCodebase(jsIndex); + } + + /** + * Analyze TypeScript files using TypeScript SDK + */ + private async analyzeTypeScriptFiles( + files: FileEntry[], + index: ProjectIndex + ): Promise { + if (files.length === 0) { + return this.createEmptyAnalysis(); + } + + logger.info(`Analyzing ${files.length} TypeScript files with TypeScript SDK...`); + + const tsIndex = { + ...index, + files: Object.fromEntries(files.map(f => [f.path, f.data])) + }; + + return this.tsAnalyzer.analyzeCodebase(tsIndex); + } + + /** + * Analyze other files with generic analyzer + */ + private async analyzeOtherFiles( + files: FileEntry[], + index: ProjectIndex + ): Promise { + if (files.length === 0) { + return this.createEmptyAnalysis(); + } + + logger.info(`Analyzing ${files.length} other files...`); + + const otherIndex = { + ...index, + files: Object.fromEntries(files.map(f => [f.path, f.data])) + }; + + return this.tsAnalyzer.analyzeCodebase(otherIndex); + } + + /** + * Run Python analyzer subprocess + */ + private runPythonAnalyzer(command: string, arg: string): Promise { + return new Promise((resolve, reject) => { + const pythonProcess = spawn('python3', [this.pythonAnalyzerPath, command, arg], { + env: { + ...process.env, + PYTHONUNBUFFERED: '1' + } + } as any); + + let stdout = ''; + let stderr = ''; + + pythonProcess.stdout?.on('data', (data: Buffer) => { + stdout += data.toString(); + }); + + pythonProcess.stderr?.on('data', (data: Buffer) => { + stderr += data.toString(); + logger.warn('Python analyzer warning:', data.toString()); + }); + + pythonProcess.on('close', (code: number | null) => { + if (code !== 0) { + reject(new Error(`Python analyzer exited with code ${code}: ${stderr}`)); + } else { + resolve(stdout); + } + }); + + pythonProcess.on('error', (error: Error) => { + reject(error); + }); + }); + } + + /** + * Convert Python analysis format to AIAnalysis + */ + private convertPythonAnalysisToAI(pythonResults: any[]): AIAnalysis { + const analysis: AIAnalysis = this.createEmptyAnalysis(); + + for (const fileAnalysis of pythonResults) { + // Map bugs + if (fileAnalysis.bugs && fileAnalysis.bugs.length > 0) { + analysis.bugPredictions.push({ + file: fileAnalysis.file, + bugs: fileAnalysis.bugs, + probability: 0.75, + severity: this.calculateSeverity(fileAnalysis.bugs), + recommendations: [] + }); + } + + // Map code smells + if (fileAnalysis.code_smells) { + analysis.codeSmells.push(...fileAnalysis.code_smells); + } + + // Map security issues + if (fileAnalysis.security_issues) { + analysis.securityIssues.push(...fileAnalysis.security_issues.map((issue: any) => ({ + ...issue, + file: fileAnalysis.file + }))); + } + + // Map refactoring suggestions + if (fileAnalysis.refactoring_suggestions && fileAnalysis.refactoring_suggestions.length > 0) { + analysis.refactoringSuggestions.push({ + file: fileAnalysis.file, + improvements: fileAnalysis.refactoring_suggestions, + priority: 'medium', + estimatedEffort: '2' + }); + } + + // Python-specific: Type hints and async issues + if (fileAnalysis.type_hints_missing && fileAnalysis.type_hints_missing.length > 0) { + analysis.codeSmells.push({ + type: 'missing_type_hints', + file: fileAnalysis.file, + description: `Missing type hints: ${fileAnalysis.type_hints_missing.join(', ')}`, + impact: 'medium', + refactoring: 'Add type hints for better type safety' + }); + } + + if (fileAnalysis.async_antipatterns && fileAnalysis.async_antipatterns.length > 0) { + analysis.performanceIssues.push({ + file: fileAnalysis.file, + type: 'async_antipattern', + description: fileAnalysis.async_antipatterns.join('; '), + impact: 'High - can cause deadlocks or poor performance', + optimization: 'Fix async/await patterns' + }); + } + } + + analysis.timestamp = new Date().toISOString(); + analysis.summary = this.generateSummary(analysis); + + return analysis; + } + + /** + * Merge multiple analyses + */ + private mergeAnalyses(analyses: AIAnalysis[]): AIAnalysis { + const merged: AIAnalysis = this.createEmptyAnalysis(); + + for (const analysis of analyses) { + merged.bugPredictions.push(...analysis.bugPredictions); + merged.codeSmells.push(...analysis.codeSmells); + merged.securityIssues.push(...analysis.securityIssues); + merged.refactoringSuggestions.push(...analysis.refactoringSuggestions); + merged.testSuggestions.push(...analysis.testSuggestions); + merged.architectureInsights.push(...analysis.architectureInsights); + merged.performanceIssues.push(...analysis.performanceIssues); + } + + merged.timestamp = new Date().toISOString(); + merged.summary = this.generateSummary(merged); + + return merged; + } + + /** + * Create empty analysis structure + */ + private createEmptyAnalysis(): AIAnalysis { + return { + bugPredictions: [], + codeSmells: [], + securityIssues: [], + refactoringSuggestions: [], + testSuggestions: [], + architectureInsights: [], + performanceIssues: [], + timestamp: new Date().toISOString(), + summary: '' + }; + } + + /** + * Calculate severity from bugs + */ + private calculateSeverity(bugs: any[]): 'critical' | 'high' | 'medium' | 'low' { + const severities = bugs.map(b => b.severity || 'low'); + if (severities.includes('critical')) return 'critical'; + if (severities.includes('high')) return 'high'; + if (severities.includes('medium')) return 'medium'; + return 'low'; + } + + /** + * Generate analysis summary + */ + private generateSummary(analysis: AIAnalysis): string { + const stats = { + totalBugs: analysis.bugPredictions.reduce((acc, p) => acc + p.bugs.length, 0), + totalSmells: analysis.codeSmells.length, + totalSecurityIssues: analysis.securityIssues.length, + criticalIssues: analysis.securityIssues.filter(i => i.severity === 'critical').length, + pythonIssues: analysis.bugPredictions.filter(p => p.file.endsWith('.py')).length, + jstsIssues: analysis.bugPredictions.filter(p => + p.file.endsWith('.js') || p.file.endsWith('.ts') + ).length + }; + + return `Hybrid AI Analysis Summary: +- Total Bugs Predicted: ${stats.totalBugs} +- Code Smells: ${stats.totalSmells} +- Security Issues: ${stats.totalSecurityIssues} (${stats.criticalIssues} critical) +- Python-specific Issues: ${stats.pythonIssues} +- JavaScript/TypeScript Issues: ${stats.jstsIssues} +- Overall Health Score: ${this.calculateHealthScore(stats)}/100 + +Language-Specific Insights: +- Python: Enhanced analysis with Python SDK for better accuracy +- TypeScript/JavaScript: Native TypeScript SDK analysis +- Cross-language: Dependency and integration analysis`; + } + + /** + * Calculate overall health score + */ + private calculateHealthScore(stats: any): number { + let score = 100; + score -= stats.totalBugs * 2; + score -= stats.totalSmells * 1; + score -= stats.totalSecurityIssues * 3; + score -= stats.criticalIssues * 10; + return Math.max(0, Math.min(100, Math.round(score))); + } + + /** + * Generate recommendations for skills repository + */ + private generateSkillsRecommendations(analysis: SkillsAnalysis): string[] { + const recommendations: string[] = []; + + if (analysis.security_vulnerabilities.length > 0) { + recommendations.push('CRITICAL: Fix security vulnerabilities immediately in production skills'); + } + + if (analysis.containerization_issues.length > 5) { + recommendations.push('Standardize containerization practices across all skills'); + } + + if (analysis.missing_tests.length > 0) { + recommendations.push('Add unit tests for critical skill functions'); + } + + if (analysis.performance_bottlenecks.length > 0) { + recommendations.push('Optimize async operations and database queries in skills'); + } + + recommendations.push('Consider implementing a skill template for consistency'); + recommendations.push('Add monitoring and observability to containerized skills'); + + return recommendations; + } +} + +/** + * Type definitions + */ +interface FilesByLanguage { + python: FileEntry[]; + javascript: FileEntry[]; + typescript: FileEntry[]; + go: FileEntry[]; + other: FileEntry[]; +} + +interface FileEntry { + path: string; + data: FileIndex; +} + +export interface SkillsAnalysis { + total_skills: number; + analyzed_skills: Array<{ + file: string; + bugs_found: number; + security_issues: number; + code_smells: number; + critical_issues: any[]; + }>; + common_issues: string[]; + security_vulnerabilities: any[]; + performance_bottlenecks: string[]; + missing_tests: string[]; + containerization_issues: string[]; + best_practices_violations: string[]; + summary: string; + recommendations?: string[]; +} + +/** + * Export for use in API + */ export default HybridAIAnalyzer; \ No newline at end of file diff --git a/src/ai/python/claude_analyzer.py b/src/ai/python/claude_analyzer.py index 8b24963..8cd3bac 100644 --- a/src/ai/python/claude_analyzer.py +++ b/src/ai/python/claude_analyzer.py @@ -1,731 +1,731 @@ -#!/usr/bin/env python3 -""" -Python-based Claude Code Analyzer -Specialized for Python code analysis using the Claude Code SDK -Perfect for analyzing Python skills repositories -""" - -import asyncio -import json -import sys -import os -from typing import List, Dict, Any, Optional -from pathlib import Path -from dataclasses import dataclass, asdict -import ast -import logging - -# Claude Code SDK imports -try: - from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions, query -except ImportError: - print("Installing claude-code-sdk...") - import subprocess - subprocess.check_call([sys.executable, "-m", "pip", "install", "claude-code-sdk"]) - from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions, query - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - - -@dataclass -class PythonAnalysis: - """Results from Python-specific analysis""" - file: str - bugs: List[Dict[str, Any]] - code_smells: List[Dict[str, Any]] - security_issues: List[Dict[str, Any]] - refactoring_suggestions: List[Dict[str, Any]] - test_coverage_analysis: Dict[str, Any] - type_hints_missing: List[str] - docstring_issues: List[str] - async_antipatterns: List[str] - performance_issues: List[str] - dependencies_analysis: Dict[str, Any] - - -class PythonClaudeAnalyzer: - """ - Specialized Python code analyzer using Claude Code SDK - Optimized for Python skills repositories and containerized functions - """ - - def __init__(self, api_key: Optional[str] = None): - self.api_key = api_key or os.environ.get('ANTHROPIC_API_KEY') - self.system_prompt = """You are an expert Python developer and code analyst. -You specialize in: -- Python best practices and PEP standards -- Async/await patterns and concurrency -- Type hints and static typing -- Security vulnerabilities in Python -- Performance optimization -- Testing strategies for Python -- Containerized Python functions -- Data science and ML code patterns - -Provide structured JSON responses for all analyses.""" - - async def analyze_python_file(self, file_path: str, content: str) -> PythonAnalysis: - """ - Comprehensive analysis of a Python file - """ - analysis = PythonAnalysis( - file=file_path, - bugs=[], - code_smells=[], - security_issues=[], - refactoring_suggestions=[], - test_coverage_analysis={}, - type_hints_missing=[], - docstring_issues=[], - async_antipatterns=[], - performance_issues=[], - dependencies_analysis={} - ) - - # Parse Python AST for initial analysis - try: - tree = ast.parse(content) - ast_analysis = self._analyze_ast(tree, file_path) - - # Enhance with Claude analysis - analysis = await self._enhance_with_claude( - file_path, content, ast_analysis, analysis - ) - except SyntaxError as e: - analysis.bugs.append({ - "type": "syntax_error", - "line": e.lineno, - "description": str(e), - "severity": "critical" - }) - - return analysis - - def _analyze_ast(self, tree: ast.AST, file_path: str) -> Dict[str, Any]: - """ - Static AST analysis for Python code - """ - analysis = { - "functions": [], - "classes": [], - "async_functions": [], - "decorators": [], - "imports": [], - "globals": [], - "complexity": 0 - } - - for node in ast.walk(tree): - if isinstance(node, ast.FunctionDef): - analysis["functions"].append({ - "name": node.name, - "lineno": node.lineno, - "args": [arg.arg for arg in node.args.args], - "has_return_type": node.returns is not None, - "has_docstring": ast.get_docstring(node) is not None, - "decorators": [self._get_decorator_name(d) for d in node.decorator_list] - }) - - elif isinstance(node, ast.AsyncFunctionDef): - analysis["async_functions"].append({ - "name": node.name, - "lineno": node.lineno, - "args": [arg.arg for arg in node.args.args] - }) - - elif isinstance(node, ast.ClassDef): - analysis["classes"].append({ - "name": node.name, - "lineno": node.lineno, - "bases": [self._get_name(base) for base in node.bases], - "methods": self._get_class_methods(node), - "has_docstring": ast.get_docstring(node) is not None - }) - - elif isinstance(node, (ast.Import, ast.ImportFrom)): - analysis["imports"].append(self._get_import_info(node)) - - # Calculate cyclomatic complexity - analysis["complexity"] = self._calculate_complexity(tree) - - return analysis - - def _get_decorator_name(self, decorator: ast.AST) -> str: - """Extract decorator name""" - if isinstance(decorator, ast.Name): - return decorator.id - elif isinstance(decorator, ast.Call): - if isinstance(decorator.func, ast.Name): - return decorator.func.id - return "unknown" - - def _get_name(self, node: ast.AST) -> str: - """Get name from AST node""" - if isinstance(node, ast.Name): - return node.id - elif isinstance(node, ast.Attribute): - return f"{self._get_name(node.value)}.{node.attr}" - return "unknown" - - def _get_class_methods(self, class_node: ast.ClassDef) -> List[str]: - """Get methods from class definition""" - methods = [] - for node in class_node.body: - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - methods.append(node.name) - return methods - - def _get_import_info(self, node: ast.AST) -> Dict[str, Any]: - """Extract import information""" - if isinstance(node, ast.Import): - return { - "type": "import", - "modules": [alias.name for alias in node.names] - } - elif isinstance(node, ast.ImportFrom): - return { - "type": "from", - "module": node.module, - "names": [alias.name for alias in node.names] - } - return {} - - def _calculate_complexity(self, tree: ast.AST) -> int: - """Calculate cyclomatic complexity""" - complexity = 1 - for node in ast.walk(tree): - if isinstance(node, (ast.If, ast.While, ast.For, ast.ExceptHandler)): - complexity += 1 - elif isinstance(node, ast.BoolOp): - complexity += len(node.values) - 1 - return complexity - - async def _enhance_with_claude( - self, - file_path: str, - content: str, - ast_analysis: Dict[str, Any], - analysis: PythonAnalysis - ) -> PythonAnalysis: - """ - Enhance analysis with Claude's insights - """ - async with ClaudeSDKClient( - options=ClaudeCodeOptions( - system_prompt=self.system_prompt, - max_turns=2, - permission_mode='plan', # Read-only analysis - allowed_tools=["Read", "Grep"] - ) - ) as client: - - # Analyze for bugs - bugs = await self._analyze_bugs(client, file_path, content, ast_analysis) - analysis.bugs.extend(bugs) - - # Analyze for code smells - smells = await self._analyze_code_smells(client, file_path, content, ast_analysis) - analysis.code_smells.extend(smells) - - # Security analysis - security = await self._analyze_security(client, file_path, content) - analysis.security_issues.extend(security) - - # Python-specific issues - python_issues = await self._analyze_python_specific(client, file_path, content, ast_analysis) - analysis.type_hints_missing = python_issues.get("type_hints_missing", []) - analysis.docstring_issues = python_issues.get("docstring_issues", []) - analysis.async_antipatterns = python_issues.get("async_antipatterns", []) - analysis.performance_issues = python_issues.get("performance_issues", []) - - # Test coverage analysis - test_analysis = await self._analyze_test_coverage(client, file_path, ast_analysis) - analysis.test_coverage_analysis = test_analysis - - # Dependencies analysis - deps_analysis = await self._analyze_dependencies(client, file_path, ast_analysis) - analysis.dependencies_analysis = deps_analysis - - return analysis - - async def _analyze_bugs( - self, - client: ClaudeSDKClient, - file_path: str, - content: str, - ast_analysis: Dict[str, Any] - ) -> List[Dict[str, Any]]: - """ - Analyze for potential bugs - """ - prompt = f"""Analyze this Python code for bugs: - -File: {file_path} -Functions: {len(ast_analysis['functions'])} -Classes: {len(ast_analysis['classes'])} -Async Functions: {len(ast_analysis['async_functions'])} -Complexity: {ast_analysis['complexity']} - -Code: -```python -{content[:3000]} # Limit for token efficiency -``` - -Identify bugs including: -1. Unhandled exceptions -2. Race conditions in async code -3. Mutable default arguments -4. Variable scope issues -5. Type mismatches -6. Resource leaks -7. Infinite loops/recursion - -Return JSON: -{{ - "bugs": [ - {{ - "type": "category", - "line": number, - "description": "description", - "severity": "critical|high|medium|low", - "fix": "suggested fix" - }} - ] -}}""" - - await client.query(prompt) - - response_text = "" - async for message in client.receive_response(): - if hasattr(message, 'content'): - for block in message.content: - if hasattr(block, 'text'): - response_text += block.text - - try: - result = json.loads(response_text) - return result.get("bugs", []) - except json.JSONDecodeError: - logger.warning(f"Failed to parse bug analysis for {file_path}") - return [] - - async def _analyze_code_smells( - self, - client: ClaudeSDKClient, - file_path: str, - content: str, - ast_analysis: Dict[str, Any] - ) -> List[Dict[str, Any]]: - """ - Detect Python code smells - """ - prompt = f"""Analyze this Python code for code smells: - -File: {file_path} -AST Analysis: {json.dumps(ast_analysis, indent=2)} - -Code snippet: -```python -{content[:2000]} -``` - -Identify Python-specific code smells: -1. Long functions (>50 lines) -2. Too many parameters (>5) -3. Duplicate code -4. Dead code -5. Global state mutation -6. Inappropriate use of eval/exec -7. Nested loops/conditions (>3 levels) -8. God classes - -Return JSON: -{{ - "smells": [ - {{ - "type": "smell_type", - "location": "function/class name", - "description": "description", - "impact": "high|medium|low", - "refactoring": "suggested refactoring" - }} - ] -}}""" - - await client.query(prompt) - - response_text = "" - async for message in client.receive_response(): - if hasattr(message, 'content'): - for block in message.content: - if hasattr(block, 'text'): - response_text += block.text - - try: - result = json.loads(response_text) - return result.get("smells", []) - except json.JSONDecodeError: - return [] - - async def _analyze_security( - self, - client: ClaudeSDKClient, - file_path: str, - content: str - ) -> List[Dict[str, Any]]: - """ - Security vulnerability analysis for Python - """ - prompt = f"""Analyze this Python code for security vulnerabilities: - -File: {file_path} - -Code: -```python -{content[:2500]} -``` - -Check for Python security issues: -1. SQL injection (raw queries) -2. Command injection (subprocess, os.system) -3. Path traversal -4. Insecure deserialization (pickle, yaml) -5. Hardcoded secrets/credentials -6. Weak cryptography -7. XXE in XML parsing -8. SSRF vulnerabilities -9. Insecure random number generation -10. Missing input validation - -Return JSON: -{{ - "security_issues": [ - {{ - "type": "vulnerability_type", - "cwe": "CWE-XXX", - "line": number, - "description": "description", - "severity": "critical|high|medium|low", - "mitigation": "how to fix" - }} - ] -}}""" - - await client.query(prompt) - - response_text = "" - async for message in client.receive_response(): - if hasattr(message, 'content'): - for block in message.content: - if hasattr(block, 'text'): - response_text += block.text - - try: - result = json.loads(response_text) - return result.get("security_issues", []) - except json.JSONDecodeError: - return [] - - async def _analyze_python_specific( - self, - client: ClaudeSDKClient, - file_path: str, - content: str, - ast_analysis: Dict[str, Any] - ) -> Dict[str, Any]: - """ - Python-specific analysis - """ - prompt = f"""Analyze Python-specific issues: - -File: {file_path} -Functions without type hints: {[f['name'] for f in ast_analysis['functions'] if not f['has_return_type']]} -Functions without docstrings: {[f['name'] for f in ast_analysis['functions'] if not f['has_docstring']]} -Async functions: {[f['name'] for f in ast_analysis['async_functions']]} - -Code: -```python -{content[:2000]} -``` - -Identify: -1. Missing type hints (parameters and returns) -2. Missing or poor docstrings -3. Async antipatterns (blocking I/O, missing await, sync in async) -4. Performance issues (N+1 queries, inefficient loops, memory leaks) - -Return JSON: -{{ - "type_hints_missing": ["function names"], - "docstring_issues": ["function/class names"], - "async_antipatterns": ["description of issues"], - "performance_issues": ["description of issues"] -}}""" - - await client.query(prompt) - - response_text = "" - async for message in client.receive_response(): - if hasattr(message, 'content'): - for block in message.content: - if hasattr(block, 'text'): - response_text += block.text - - try: - return json.loads(response_text) - except json.JSONDecodeError: - return {} - - async def _analyze_test_coverage( - self, - client: ClaudeSDKClient, - file_path: str, - ast_analysis: Dict[str, Any] - ) -> Dict[str, Any]: - """ - Analyze test coverage needs - """ - prompt = f"""Analyze test coverage needs for: - -File: {file_path} -Functions: {[f['name'] for f in ast_analysis['functions']]} -Classes: {[c['name'] for c in ast_analysis['classes']]} - -Suggest test cases for critical functions including: -1. Happy path tests -2. Edge cases -3. Error handling -4. Async behavior -5. Mocking requirements - -Return JSON: -{{ - "functions_needing_tests": ["function names"], - "suggested_test_count": number, - "critical_paths": ["descriptions"], - "mocking_requirements": ["what needs mocking"] -}}""" - - await client.query(prompt) - - response_text = "" - async for message in client.receive_response(): - if hasattr(message, 'content'): - for block in message.content: - if hasattr(block, 'text'): - response_text += block.text - - try: - return json.loads(response_text) - except json.JSONDecodeError: - return {} - - async def _analyze_dependencies( - self, - client: ClaudeSDKClient, - file_path: str, - ast_analysis: Dict[str, Any] - ) -> Dict[str, Any]: - """ - Analyze Python dependencies - """ - imports = ast_analysis.get("imports", []) - - prompt = f"""Analyze Python dependencies: - -File: {file_path} -Imports: {json.dumps(imports)} - -Check for: -1. Unused imports -2. Missing imports (referenced but not imported) -3. Circular dependencies -4. Version compatibility issues -5. Security vulnerabilities in dependencies - -Return JSON: -{{ - "unused_imports": ["module names"], - "missing_imports": ["module names"], - "security_concerns": ["dependency: issue"], - "recommendations": ["suggestions"] -}}""" - - await client.query(prompt) - - response_text = "" - async for message in client.receive_response(): - if hasattr(message, 'content'): - for block in message.content: - if hasattr(block, 'text'): - response_text += block.text - - try: - return json.loads(response_text) - except json.JSONDecodeError: - return {} - - async def analyze_skills_repository(self, skills_path: str) -> Dict[str, Any]: - """ - Specialized analysis for Python skills repository - Analyzes containerized Python functions - """ - skills_analysis = { - "total_skills": 0, - "analyzed_skills": [], - "common_issues": [], - "security_vulnerabilities": [], - "performance_bottlenecks": [], - "missing_tests": [], - "containerization_issues": [], - "best_practices_violations": [] - } - - skills_dir = Path(skills_path) - - # Find all Python skill files - python_files = list(skills_dir.rglob("*.py")) - skills_analysis["total_skills"] = len(python_files) - - for py_file in python_files[:10]: # Limit for efficiency - try: - content = py_file.read_text() - - # Analyze individual skill - analysis = await self.analyze_python_file(str(py_file), content) - - skill_summary = { - "file": str(py_file.relative_to(skills_dir)), - "bugs_found": len(analysis.bugs), - "security_issues": len(analysis.security_issues), - "code_smells": len(analysis.code_smells), - "critical_issues": [ - bug for bug in analysis.bugs - if bug.get("severity") == "critical" - ] - } - - skills_analysis["analyzed_skills"].append(skill_summary) - - # Aggregate issues - if analysis.security_issues: - skills_analysis["security_vulnerabilities"].extend(analysis.security_issues) - - if analysis.performance_issues: - skills_analysis["performance_bottlenecks"].extend(analysis.performance_issues) - - # Check for containerization best practices - container_issues = await self._check_container_best_practices(py_file, content) - if container_issues: - skills_analysis["containerization_issues"].extend(container_issues) - - except Exception as e: - logger.error(f"Error analyzing {py_file}: {e}") - - # Generate summary - skills_analysis["summary"] = self._generate_skills_summary(skills_analysis) - - return skills_analysis - - async def _check_container_best_practices(self, file_path: Path, content: str) -> List[str]: - """ - Check for containerization best practices - """ - issues = [] - - # Check for common container antipatterns - if "print(" in content and "logging" not in content: - issues.append(f"{file_path.name}: Using print instead of logging") - - if "sys.exit(" in content: - issues.append(f"{file_path.name}: Using sys.exit instead of proper error handling") - - if "os.environ" not in content and "config" not in content.lower(): - issues.append(f"{file_path.name}: No environment variable usage detected") - - # Check for requirements.txt or pyproject.toml - parent_dir = file_path.parent - if not (parent_dir / "requirements.txt").exists() and not (parent_dir / "pyproject.toml").exists(): - issues.append(f"{file_path.name}: Missing requirements file") - - return issues - - def _generate_skills_summary(self, analysis: Dict[str, Any]) -> str: - """ - Generate executive summary of skills analysis - """ - total_bugs = sum(skill["bugs_found"] for skill in analysis["analyzed_skills"]) - total_security = len(analysis["security_vulnerabilities"]) - - return f"""Skills Repository Analysis Summary: -- Total Skills Analyzed: {len(analysis['analyzed_skills'])}/{analysis['total_skills']} -- Total Bugs Found: {total_bugs} -- Security Issues: {total_security} -- Container Issues: {len(analysis['containerization_issues'])} -- Performance Issues: {len(analysis['performance_bottlenecks'])} - -Top Priority Actions: -1. Fix {total_security} security vulnerabilities -2. Address critical bugs in production skills -3. Add missing tests for core functionality -4. Improve containerization practices -""" - - -async def main(): - """ - Main entry point for standalone execution - """ - if len(sys.argv) < 2: - print("Usage: python claude_analyzer.py [args]") - print("Commands:") - print(" analyze-file - Analyze a single Python file") - print(" analyze-skills - Analyze skills repository") - print(" analyze-index - Analyze from indexer JSON") - sys.exit(1) - - command = sys.argv[1] - analyzer = PythonClaudeAnalyzer() - - if command == "analyze-file" and len(sys.argv) > 2: - file_path = sys.argv[2] - with open(file_path, 'r') as f: - content = f.read() - - analysis = await analyzer.analyze_python_file(file_path, content) - print(json.dumps(asdict(analysis), indent=2)) - - elif command == "analyze-skills" and len(sys.argv) > 2: - skills_path = sys.argv[2] - analysis = await analyzer.analyze_skills_repository(skills_path) - print(json.dumps(analysis, indent=2)) - - elif command == "analyze-index" and len(sys.argv) > 2: - index_json = sys.argv[2] - # This would integrate with the indexer's JSON output - with open(index_json, 'r') as f: - index_data = json.load(f) - - results = [] - for file_path, file_data in index_data.get("files", {}).items(): - if file_path.endswith(".py"): - # Read actual file content - try: - with open(file_path, 'r') as f: - content = f.read() - analysis = await analyzer.analyze_python_file(file_path, content) - results.append(asdict(analysis)) - except Exception as e: - logger.error(f"Error analyzing {file_path}: {e}") - - print(json.dumps(results, indent=2)) - - else: - print(f"Unknown command: {command}") - sys.exit(1) - - -if __name__ == "__main__": +#!/usr/bin/env python3 +""" +Python-based Claude Code Analyzer +Specialized for Python code analysis using the Claude Code SDK +Perfect for analyzing Python skills repositories +""" + +import asyncio +import json +import sys +import os +from typing import List, Dict, Any, Optional +from pathlib import Path +from dataclasses import dataclass, asdict +import ast +import logging + +# Claude Code SDK imports +try: + from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions, query +except ImportError: + print("Installing claude-code-sdk...") + import subprocess + subprocess.check_call([sys.executable, "-m", "pip", "install", "claude-code-sdk"]) + from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions, query + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +@dataclass +class PythonAnalysis: + """Results from Python-specific analysis""" + file: str + bugs: List[Dict[str, Any]] + code_smells: List[Dict[str, Any]] + security_issues: List[Dict[str, Any]] + refactoring_suggestions: List[Dict[str, Any]] + test_coverage_analysis: Dict[str, Any] + type_hints_missing: List[str] + docstring_issues: List[str] + async_antipatterns: List[str] + performance_issues: List[str] + dependencies_analysis: Dict[str, Any] + + +class PythonClaudeAnalyzer: + """ + Specialized Python code analyzer using Claude Code SDK + Optimized for Python skills repositories and containerized functions + """ + + def __init__(self, api_key: Optional[str] = None): + self.api_key = api_key or os.environ.get('ANTHROPIC_API_KEY') + self.system_prompt = """You are an expert Python developer and code analyst. +You specialize in: +- Python best practices and PEP standards +- Async/await patterns and concurrency +- Type hints and static typing +- Security vulnerabilities in Python +- Performance optimization +- Testing strategies for Python +- Containerized Python functions +- Data science and ML code patterns + +Provide structured JSON responses for all analyses.""" + + async def analyze_python_file(self, file_path: str, content: str) -> PythonAnalysis: + """ + Comprehensive analysis of a Python file + """ + analysis = PythonAnalysis( + file=file_path, + bugs=[], + code_smells=[], + security_issues=[], + refactoring_suggestions=[], + test_coverage_analysis={}, + type_hints_missing=[], + docstring_issues=[], + async_antipatterns=[], + performance_issues=[], + dependencies_analysis={} + ) + + # Parse Python AST for initial analysis + try: + tree = ast.parse(content) + ast_analysis = self._analyze_ast(tree, file_path) + + # Enhance with Claude analysis + analysis = await self._enhance_with_claude( + file_path, content, ast_analysis, analysis + ) + except SyntaxError as e: + analysis.bugs.append({ + "type": "syntax_error", + "line": e.lineno, + "description": str(e), + "severity": "critical" + }) + + return analysis + + def _analyze_ast(self, tree: ast.AST, file_path: str) -> Dict[str, Any]: + """ + Static AST analysis for Python code + """ + analysis = { + "functions": [], + "classes": [], + "async_functions": [], + "decorators": [], + "imports": [], + "globals": [], + "complexity": 0 + } + + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef): + analysis["functions"].append({ + "name": node.name, + "lineno": node.lineno, + "args": [arg.arg for arg in node.args.args], + "has_return_type": node.returns is not None, + "has_docstring": ast.get_docstring(node) is not None, + "decorators": [self._get_decorator_name(d) for d in node.decorator_list] + }) + + elif isinstance(node, ast.AsyncFunctionDef): + analysis["async_functions"].append({ + "name": node.name, + "lineno": node.lineno, + "args": [arg.arg for arg in node.args.args] + }) + + elif isinstance(node, ast.ClassDef): + analysis["classes"].append({ + "name": node.name, + "lineno": node.lineno, + "bases": [self._get_name(base) for base in node.bases], + "methods": self._get_class_methods(node), + "has_docstring": ast.get_docstring(node) is not None + }) + + elif isinstance(node, (ast.Import, ast.ImportFrom)): + analysis["imports"].append(self._get_import_info(node)) + + # Calculate cyclomatic complexity + analysis["complexity"] = self._calculate_complexity(tree) + + return analysis + + def _get_decorator_name(self, decorator: ast.AST) -> str: + """Extract decorator name""" + if isinstance(decorator, ast.Name): + return decorator.id + elif isinstance(decorator, ast.Call): + if isinstance(decorator.func, ast.Name): + return decorator.func.id + return "unknown" + + def _get_name(self, node: ast.AST) -> str: + """Get name from AST node""" + if isinstance(node, ast.Name): + return node.id + elif isinstance(node, ast.Attribute): + return f"{self._get_name(node.value)}.{node.attr}" + return "unknown" + + def _get_class_methods(self, class_node: ast.ClassDef) -> List[str]: + """Get methods from class definition""" + methods = [] + for node in class_node.body: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + methods.append(node.name) + return methods + + def _get_import_info(self, node: ast.AST) -> Dict[str, Any]: + """Extract import information""" + if isinstance(node, ast.Import): + return { + "type": "import", + "modules": [alias.name for alias in node.names] + } + elif isinstance(node, ast.ImportFrom): + return { + "type": "from", + "module": node.module, + "names": [alias.name for alias in node.names] + } + return {} + + def _calculate_complexity(self, tree: ast.AST) -> int: + """Calculate cyclomatic complexity""" + complexity = 1 + for node in ast.walk(tree): + if isinstance(node, (ast.If, ast.While, ast.For, ast.ExceptHandler)): + complexity += 1 + elif isinstance(node, ast.BoolOp): + complexity += len(node.values) - 1 + return complexity + + async def _enhance_with_claude( + self, + file_path: str, + content: str, + ast_analysis: Dict[str, Any], + analysis: PythonAnalysis + ) -> PythonAnalysis: + """ + Enhance analysis with Claude's insights + """ + async with ClaudeSDKClient( + options=ClaudeCodeOptions( + system_prompt=self.system_prompt, + max_turns=2, + permission_mode='plan', # Read-only analysis + allowed_tools=["Read", "Grep"] + ) + ) as client: + + # Analyze for bugs + bugs = await self._analyze_bugs(client, file_path, content, ast_analysis) + analysis.bugs.extend(bugs) + + # Analyze for code smells + smells = await self._analyze_code_smells(client, file_path, content, ast_analysis) + analysis.code_smells.extend(smells) + + # Security analysis + security = await self._analyze_security(client, file_path, content) + analysis.security_issues.extend(security) + + # Python-specific issues + python_issues = await self._analyze_python_specific(client, file_path, content, ast_analysis) + analysis.type_hints_missing = python_issues.get("type_hints_missing", []) + analysis.docstring_issues = python_issues.get("docstring_issues", []) + analysis.async_antipatterns = python_issues.get("async_antipatterns", []) + analysis.performance_issues = python_issues.get("performance_issues", []) + + # Test coverage analysis + test_analysis = await self._analyze_test_coverage(client, file_path, ast_analysis) + analysis.test_coverage_analysis = test_analysis + + # Dependencies analysis + deps_analysis = await self._analyze_dependencies(client, file_path, ast_analysis) + analysis.dependencies_analysis = deps_analysis + + return analysis + + async def _analyze_bugs( + self, + client: ClaudeSDKClient, + file_path: str, + content: str, + ast_analysis: Dict[str, Any] + ) -> List[Dict[str, Any]]: + """ + Analyze for potential bugs + """ + prompt = f"""Analyze this Python code for bugs: + +File: {file_path} +Functions: {len(ast_analysis['functions'])} +Classes: {len(ast_analysis['classes'])} +Async Functions: {len(ast_analysis['async_functions'])} +Complexity: {ast_analysis['complexity']} + +Code: +```python +{content[:3000]} # Limit for token efficiency +``` + +Identify bugs including: +1. Unhandled exceptions +2. Race conditions in async code +3. Mutable default arguments +4. Variable scope issues +5. Type mismatches +6. Resource leaks +7. Infinite loops/recursion + +Return JSON: +{{ + "bugs": [ + {{ + "type": "category", + "line": number, + "description": "description", + "severity": "critical|high|medium|low", + "fix": "suggested fix" + }} + ] +}}""" + + await client.query(prompt) + + response_text = "" + async for message in client.receive_response(): + if hasattr(message, 'content'): + for block in message.content: + if hasattr(block, 'text'): + response_text += block.text + + try: + result = json.loads(response_text) + return result.get("bugs", []) + except json.JSONDecodeError: + logger.warning(f"Failed to parse bug analysis for {file_path}") + return [] + + async def _analyze_code_smells( + self, + client: ClaudeSDKClient, + file_path: str, + content: str, + ast_analysis: Dict[str, Any] + ) -> List[Dict[str, Any]]: + """ + Detect Python code smells + """ + prompt = f"""Analyze this Python code for code smells: + +File: {file_path} +AST Analysis: {json.dumps(ast_analysis, indent=2)} + +Code snippet: +```python +{content[:2000]} +``` + +Identify Python-specific code smells: +1. Long functions (>50 lines) +2. Too many parameters (>5) +3. Duplicate code +4. Dead code +5. Global state mutation +6. Inappropriate use of eval/exec +7. Nested loops/conditions (>3 levels) +8. God classes + +Return JSON: +{{ + "smells": [ + {{ + "type": "smell_type", + "location": "function/class name", + "description": "description", + "impact": "high|medium|low", + "refactoring": "suggested refactoring" + }} + ] +}}""" + + await client.query(prompt) + + response_text = "" + async for message in client.receive_response(): + if hasattr(message, 'content'): + for block in message.content: + if hasattr(block, 'text'): + response_text += block.text + + try: + result = json.loads(response_text) + return result.get("smells", []) + except json.JSONDecodeError: + return [] + + async def _analyze_security( + self, + client: ClaudeSDKClient, + file_path: str, + content: str + ) -> List[Dict[str, Any]]: + """ + Security vulnerability analysis for Python + """ + prompt = f"""Analyze this Python code for security vulnerabilities: + +File: {file_path} + +Code: +```python +{content[:2500]} +``` + +Check for Python security issues: +1. SQL injection (raw queries) +2. Command injection (subprocess, os.system) +3. Path traversal +4. Insecure deserialization (pickle, yaml) +5. Hardcoded secrets/credentials +6. Weak cryptography +7. XXE in XML parsing +8. SSRF vulnerabilities +9. Insecure random number generation +10. Missing input validation + +Return JSON: +{{ + "security_issues": [ + {{ + "type": "vulnerability_type", + "cwe": "CWE-XXX", + "line": number, + "description": "description", + "severity": "critical|high|medium|low", + "mitigation": "how to fix" + }} + ] +}}""" + + await client.query(prompt) + + response_text = "" + async for message in client.receive_response(): + if hasattr(message, 'content'): + for block in message.content: + if hasattr(block, 'text'): + response_text += block.text + + try: + result = json.loads(response_text) + return result.get("security_issues", []) + except json.JSONDecodeError: + return [] + + async def _analyze_python_specific( + self, + client: ClaudeSDKClient, + file_path: str, + content: str, + ast_analysis: Dict[str, Any] + ) -> Dict[str, Any]: + """ + Python-specific analysis + """ + prompt = f"""Analyze Python-specific issues: + +File: {file_path} +Functions without type hints: {[f['name'] for f in ast_analysis['functions'] if not f['has_return_type']]} +Functions without docstrings: {[f['name'] for f in ast_analysis['functions'] if not f['has_docstring']]} +Async functions: {[f['name'] for f in ast_analysis['async_functions']]} + +Code: +```python +{content[:2000]} +``` + +Identify: +1. Missing type hints (parameters and returns) +2. Missing or poor docstrings +3. Async antipatterns (blocking I/O, missing await, sync in async) +4. Performance issues (N+1 queries, inefficient loops, memory leaks) + +Return JSON: +{{ + "type_hints_missing": ["function names"], + "docstring_issues": ["function/class names"], + "async_antipatterns": ["description of issues"], + "performance_issues": ["description of issues"] +}}""" + + await client.query(prompt) + + response_text = "" + async for message in client.receive_response(): + if hasattr(message, 'content'): + for block in message.content: + if hasattr(block, 'text'): + response_text += block.text + + try: + return json.loads(response_text) + except json.JSONDecodeError: + return {} + + async def _analyze_test_coverage( + self, + client: ClaudeSDKClient, + file_path: str, + ast_analysis: Dict[str, Any] + ) -> Dict[str, Any]: + """ + Analyze test coverage needs + """ + prompt = f"""Analyze test coverage needs for: + +File: {file_path} +Functions: {[f['name'] for f in ast_analysis['functions']]} +Classes: {[c['name'] for c in ast_analysis['classes']]} + +Suggest test cases for critical functions including: +1. Happy path tests +2. Edge cases +3. Error handling +4. Async behavior +5. Mocking requirements + +Return JSON: +{{ + "functions_needing_tests": ["function names"], + "suggested_test_count": number, + "critical_paths": ["descriptions"], + "mocking_requirements": ["what needs mocking"] +}}""" + + await client.query(prompt) + + response_text = "" + async for message in client.receive_response(): + if hasattr(message, 'content'): + for block in message.content: + if hasattr(block, 'text'): + response_text += block.text + + try: + return json.loads(response_text) + except json.JSONDecodeError: + return {} + + async def _analyze_dependencies( + self, + client: ClaudeSDKClient, + file_path: str, + ast_analysis: Dict[str, Any] + ) -> Dict[str, Any]: + """ + Analyze Python dependencies + """ + imports = ast_analysis.get("imports", []) + + prompt = f"""Analyze Python dependencies: + +File: {file_path} +Imports: {json.dumps(imports)} + +Check for: +1. Unused imports +2. Missing imports (referenced but not imported) +3. Circular dependencies +4. Version compatibility issues +5. Security vulnerabilities in dependencies + +Return JSON: +{{ + "unused_imports": ["module names"], + "missing_imports": ["module names"], + "security_concerns": ["dependency: issue"], + "recommendations": ["suggestions"] +}}""" + + await client.query(prompt) + + response_text = "" + async for message in client.receive_response(): + if hasattr(message, 'content'): + for block in message.content: + if hasattr(block, 'text'): + response_text += block.text + + try: + return json.loads(response_text) + except json.JSONDecodeError: + return {} + + async def analyze_skills_repository(self, skills_path: str) -> Dict[str, Any]: + """ + Specialized analysis for Python skills repository + Analyzes containerized Python functions + """ + skills_analysis = { + "total_skills": 0, + "analyzed_skills": [], + "common_issues": [], + "security_vulnerabilities": [], + "performance_bottlenecks": [], + "missing_tests": [], + "containerization_issues": [], + "best_practices_violations": [] + } + + skills_dir = Path(skills_path) + + # Find all Python skill files + python_files = list(skills_dir.rglob("*.py")) + skills_analysis["total_skills"] = len(python_files) + + for py_file in python_files[:10]: # Limit for efficiency + try: + content = py_file.read_text() + + # Analyze individual skill + analysis = await self.analyze_python_file(str(py_file), content) + + skill_summary = { + "file": str(py_file.relative_to(skills_dir)), + "bugs_found": len(analysis.bugs), + "security_issues": len(analysis.security_issues), + "code_smells": len(analysis.code_smells), + "critical_issues": [ + bug for bug in analysis.bugs + if bug.get("severity") == "critical" + ] + } + + skills_analysis["analyzed_skills"].append(skill_summary) + + # Aggregate issues + if analysis.security_issues: + skills_analysis["security_vulnerabilities"].extend(analysis.security_issues) + + if analysis.performance_issues: + skills_analysis["performance_bottlenecks"].extend(analysis.performance_issues) + + # Check for containerization best practices + container_issues = await self._check_container_best_practices(py_file, content) + if container_issues: + skills_analysis["containerization_issues"].extend(container_issues) + + except Exception as e: + logger.error(f"Error analyzing {py_file}: {e}") + + # Generate summary + skills_analysis["summary"] = self._generate_skills_summary(skills_analysis) + + return skills_analysis + + async def _check_container_best_practices(self, file_path: Path, content: str) -> List[str]: + """ + Check for containerization best practices + """ + issues = [] + + # Check for common container antipatterns + if "print(" in content and "logging" not in content: + issues.append(f"{file_path.name}: Using print instead of logging") + + if "sys.exit(" in content: + issues.append(f"{file_path.name}: Using sys.exit instead of proper error handling") + + if "os.environ" not in content and "config" not in content.lower(): + issues.append(f"{file_path.name}: No environment variable usage detected") + + # Check for requirements.txt or pyproject.toml + parent_dir = file_path.parent + if not (parent_dir / "requirements.txt").exists() and not (parent_dir / "pyproject.toml").exists(): + issues.append(f"{file_path.name}: Missing requirements file") + + return issues + + def _generate_skills_summary(self, analysis: Dict[str, Any]) -> str: + """ + Generate executive summary of skills analysis + """ + total_bugs = sum(skill["bugs_found"] for skill in analysis["analyzed_skills"]) + total_security = len(analysis["security_vulnerabilities"]) + + return f"""Skills Repository Analysis Summary: +- Total Skills Analyzed: {len(analysis['analyzed_skills'])}/{analysis['total_skills']} +- Total Bugs Found: {total_bugs} +- Security Issues: {total_security} +- Container Issues: {len(analysis['containerization_issues'])} +- Performance Issues: {len(analysis['performance_bottlenecks'])} + +Top Priority Actions: +1. Fix {total_security} security vulnerabilities +2. Address critical bugs in production skills +3. Add missing tests for core functionality +4. Improve containerization practices +""" + + +async def main(): + """ + Main entry point for standalone execution + """ + if len(sys.argv) < 2: + print("Usage: python claude_analyzer.py [args]") + print("Commands:") + print(" analyze-file - Analyze a single Python file") + print(" analyze-skills - Analyze skills repository") + print(" analyze-index - Analyze from indexer JSON") + sys.exit(1) + + command = sys.argv[1] + analyzer = PythonClaudeAnalyzer() + + if command == "analyze-file" and len(sys.argv) > 2: + file_path = sys.argv[2] + with open(file_path, 'r') as f: + content = f.read() + + analysis = await analyzer.analyze_python_file(file_path, content) + print(json.dumps(asdict(analysis), indent=2)) + + elif command == "analyze-skills" and len(sys.argv) > 2: + skills_path = sys.argv[2] + analysis = await analyzer.analyze_skills_repository(skills_path) + print(json.dumps(analysis, indent=2)) + + elif command == "analyze-index" and len(sys.argv) > 2: + index_json = sys.argv[2] + # This would integrate with the indexer's JSON output + with open(index_json, 'r') as f: + index_data = json.load(f) + + results = [] + for file_path, file_data in index_data.get("files", {}).items(): + if file_path.endswith(".py"): + # Read actual file content + try: + with open(file_path, 'r') as f: + content = f.read() + analysis = await analyzer.analyze_python_file(file_path, content) + results.append(asdict(analysis)) + except Exception as e: + logger.error(f"Error analyzing {file_path}: {e}") + + print(json.dumps(results, indent=2)) + + else: + print(f"Unknown command: {command}") + sys.exit(1) + + +if __name__ == "__main__": asyncio.run(main()) \ No newline at end of file diff --git a/src/ai/sdk-analyzer.ts b/src/ai/sdk-analyzer.ts index a879183..c5ec0ec 100644 --- a/src/ai/sdk-analyzer.ts +++ b/src/ai/sdk-analyzer.ts @@ -1,759 +1,759 @@ -/** - * Advanced Claude Code Analyzer using TypeScript SDK - * Leverages the full power of the Claude Code SDK with MCP, streaming, and advanced features - */ - -import { query } from '@anthropic-ai/claude-code'; -import { ProjectIndex} from '../types'; -import { EventEmitter } from 'events'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import logger from '../utils/logger'; - -/** - * SDK-based Claude analyzer with advanced features - */ -export class ClaudeSDKAnalyzer extends EventEmitter { - private apiKey: string; - private model: string; - private mcpConfig: MCPConfiguration | null = null; - private sessionCache: Map = new Map(); - private abortControllers: Map = new Map(); - - constructor() { - super(); - this.apiKey = process.env.ANTHROPIC_API_KEY || ''; - this.model = process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805'; - - if (!this.apiKey) { - logger.warn('Warning: ANTHROPIC_API_KEY not set. AI features will be limited.'); - } - - // Load MCP configuration if available - this.loadMCPConfig(); - } - - /** - * Load MCP server configuration - */ - private async loadMCPConfig(): Promise { - try { - const configPath = path.join(process.cwd(), 'config', 'mcp-servers.json'); - const configData = await fs.readFile(configPath, 'utf-8'); - this.mcpConfig = JSON.parse(configData); - logger.info('MCP configuration loaded successfully'); - } catch (error) { - logger.info('No MCP configuration found, using default tools only'); - } - } - - /** - * Comprehensive analysis with streaming support - */ - async *analyzeCodebaseStreaming(index: ProjectIndex): AsyncGenerator { - const analysisId = `analysis-${Date.now()}`; - const abortController = new AbortController(); - this.abortControllers.set(analysisId, abortController); - - try { - // Emit start event - yield { - type: 'start', - analysisId, - timestamp: new Date().toISOString(), - message: 'Starting comprehensive codebase analysis...' - }; - - // Run specialized analyses in parallel - const analyses = [ - this.runSecurityAnalysis(index, abortController), - this.runPerformanceAnalysis(index, abortController), - this.runArchitectureAnalysis(index, abortController), - this.runTestCoverageAnalysis(index, abortController) - ]; - - let completedCount = 0; - const totalAnalyses = analyses.length; - - // Wait for all analyses to complete - const results = await Promise.allSettled(analyses); - - for (const result of results) { - completedCount++; - - yield { - type: 'progress', - analysisId, - timestamp: new Date().toISOString(), - progress: (completedCount / totalAnalyses) * 100, - result: result.status === 'fulfilled' ? result.value : null - }; - } - - // Final summary - const summary = await this.generateExecutiveSummary(index, abortController); - - yield { - type: 'complete', - analysisId, - timestamp: new Date().toISOString(), - summary - }; - - } catch (error) { - yield { - type: 'error', - analysisId, - timestamp: new Date().toISOString(), - error: error instanceof Error ? error.message : 'Unknown error' - }; - } finally { - this.abortControllers.delete(analysisId); - } - } - - /** - * Security-focused analysis with MCP tools - */ - private async runSecurityAnalysis( - index: ProjectIndex, - abortController: AbortController - ): Promise { - const results: SecurityAnalysisResult = { - type: 'security', - vulnerabilities: [], - recommendations: [], - score: 100 - }; - - const prompt = this.buildSecurityPrompt(index); - - for await (const message of query({ - prompt, - options: { - abortController, - model: this.model, - customSystemPrompt: `You are a security expert specializing in application security and OWASP compliance. -Analyze code for vulnerabilities, security anti-patterns, and compliance issues. -Focus on: SQL injection, XSS, authentication issues, hardcoded secrets, insecure dependencies.`, - maxTurns: 5, - permissionMode: 'plan', - allowedTools: [ - 'Read', - 'Grep', - 'WebSearch', - ...(this.mcpConfig ? [ - 'mcp__semgrep', - 'mcp__snyk', - 'mcp__github__security_alerts' - ] : []) - ], - // MCP servers configuration would go here if needed - // mcpServers: this.mcpConfig ? loadMcpServers() : undefined - } - })) { - // Process streaming messages - if (message.type === 'assistant') { - this.emit('security-update', message); - - // Parse security findings from the message - const findings = this.parseSecurityFindings(message); - if (findings) { - results.vulnerabilities.push(...findings.vulnerabilities); - results.recommendations.push(...findings.recommendations); - } - } - - if (message.type === 'result') { - // Final processing - results.score = this.calculateSecurityScore(results.vulnerabilities); - - // Store session for follow-up questions - if (message.session_id) { - this.sessionCache.set('security', message.session_id); - } - } - } - - return results; - } - - /** - * Performance analysis with specialized agent - */ - private async runPerformanceAnalysis( - index: ProjectIndex, - abortController: AbortController - ): Promise { - const results: PerformanceAnalysisResult = { - type: 'performance', - issues: [], - optimizations: [], - metrics: {} - }; - - const prompt = this.buildPerformancePrompt(index); - - for await (const message of query({ - prompt, - options: { - abortController, - model: this.model, - customSystemPrompt: `You are a performance engineer specializing in optimization and scalability. -Identify performance bottlenecks, memory leaks, and inefficient algorithms. -Suggest concrete optimizations with measurable impact.`, - maxTurns: 4, - permissionMode: 'plan', - allowedTools: [ - 'Read', - 'Grep', - ...(this.mcpConfig ? [ - 'mcp__profiler', - 'mcp__benchmark', - 'mcp__datadog__metrics' - ] : []) - ] - } - })) { - if (message.type === 'assistant') { - this.emit('performance-update', message); - - const findings = this.parsePerformanceFindings(message); - if (findings) { - results.issues.push(...findings.issues); - results.optimizations.push(...findings.optimizations); - } - } - - if (message.type === 'result') { - results.metrics = this.extractPerformanceMetrics((message as any).result || ''); - - if (message.session_id) { - this.sessionCache.set('performance', message.session_id); - } - } - } - - return results; - } - - /** - * Architecture review with pattern detection - */ - private async runArchitectureAnalysis( - index: ProjectIndex, - abortController: AbortController - ): Promise { - const results: ArchitectureAnalysisResult = { - type: 'architecture', - patterns: [], - antiPatterns: [], - suggestions: [], - dependencies: {} - }; - - const prompt = this.buildArchitecturePrompt(index); - - for await (const message of query({ - prompt, - options: { - abortController, - model: this.model, - customSystemPrompt: `You are a software architect specializing in clean architecture and design patterns. -Identify architectural patterns, anti-patterns, and structural issues. -Suggest improvements for maintainability, scalability, and testability.`, - maxTurns: 4, - permissionMode: 'plan', - allowedTools: [ - 'Read', - 'Grep', - 'Glob', - ...(this.mcpConfig ? [ - 'mcp__dependency_analyzer', - 'mcp__code_metrics' - ] : []) - ] - } - })) { - if (message.type === 'assistant') { - this.emit('architecture-update', message); - - const findings = this.parseArchitectureFindings(message); - if (findings) { - results.patterns.push(...findings.patterns); - results.antiPatterns.push(...findings.antiPatterns); - results.suggestions.push(...findings.suggestions); - } - } - - if (message.type === 'result') { - results.dependencies = this.analyzeDependencyGraph(index); - - if (message.session_id) { - this.sessionCache.set('architecture', message.session_id); - } - } - } - - return results; - } - - /** - * Test coverage and quality analysis - */ - private async runTestCoverageAnalysis( - index: ProjectIndex, - abortController: AbortController - ): Promise { - const results: TestAnalysisResult = { - type: 'testing', - coverage: 0, - missingTests: [], - testSuggestions: [], - qualityIssues: [] - }; - - const prompt = this.buildTestingPrompt(index); - - for await (const message of query({ - prompt, - options: { - abortController, - model: this.model, - customSystemPrompt: `You are a test engineer specializing in comprehensive test coverage and TDD. -Identify untested code, suggest test cases, and evaluate test quality. -Generate specific test implementations for critical paths.`, - maxTurns: 3, - permissionMode: 'acceptEdits', // Allow test generation - allowedTools: [ - 'Read', - 'Write', - 'Grep', - ...(this.mcpConfig ? [ - 'mcp__jest', - 'mcp__coverage_reporter' - ] : []) - ] - } - })) { - if (message.type === 'assistant') { - this.emit('testing-update', message); - - const findings = this.parseTestFindings(message); - if (findings) { - results.missingTests.push(...findings.missingTests); - results.testSuggestions.push(...findings.suggestions); - results.qualityIssues.push(...findings.qualityIssues); - } - } - - if (message.type === 'result') { - results.coverage = this.calculateTestCoverage(index); - - if (message.session_id) { - this.sessionCache.set('testing', message.session_id); - } - } - } - - return results; - } - - /** - * Continue a previous analysis session - */ - async continueSession( - sessionType: 'security' | 'performance' | 'architecture' | 'testing', - followUpPrompt: string - ): Promise { - const sessionId = this.sessionCache.get(sessionType); - - if (!sessionId) { - throw new Error(`No active session found for ${sessionType} analysis`); - } - - const results: any[] = []; - - for await (const message of query({ - prompt: followUpPrompt, - options: { - model: this.model, - continue: sessionId ? true : false, - maxTurns: 2 - } - })) { - if (message.type === 'result') { - return (message as any).result || ''; - } - - results.push(message); - } - - return results; - } - - /** - * Cancel an ongoing analysis - */ - cancelAnalysis(analysisId: string): void { - const controller = this.abortControllers.get(analysisId); - if (controller) { - controller.abort(); - this.abortControllers.delete(analysisId); - this.emit('analysis-cancelled', analysisId); - } - } - - /** - * Generate executive summary - */ - private async generateExecutiveSummary( - index: ProjectIndex, - abortController: AbortController - ): Promise { - const prompt = `Generate an executive summary of the codebase analysis: -- Total files: ${Object.keys(index.files).length} -- Languages: ${this.getLanguageDistribution(index)} -- Key findings from security, performance, architecture, and testing analyses -- Top 3 priority recommendations -- Overall health score`; - - for await (const message of query({ - prompt, - options: { - abortController, - model: this.model, - customSystemPrompt: 'You are a technical lead providing executive summaries.', - maxTurns: 1, - permissionMode: 'plan' - } - })) { - if (message.type === 'result') { - return (message as any).result || ''; - } - } - - return 'Summary generation failed'; - } - - /** - * Helper: Race multiple async generators to completion - */ - // Removed unused raceToCompletion method - replaced with Promise.allSettled - - /** - * Prompt builders - */ - private buildSecurityPrompt(index: ProjectIndex): string { - const criticalFiles = Object.entries(index.files) - .filter(([path]) => - path.includes('auth') || - path.includes('security') || - path.includes('api') || - path.includes('database') - ) - .slice(0, 20); - - return `Analyze the following codebase for security vulnerabilities: - -Project Statistics: -- Total Files: ${Object.keys(index.files).length} -- Critical Security Files: ${criticalFiles.length} - -Focus Areas: -${criticalFiles.map(([path]) => `- ${path}`).join('\n')} - -Identify: -1. OWASP Top 10 vulnerabilities -2. Authentication/authorization issues -3. Hardcoded secrets or API keys -4. SQL injection risks -5. XSS vulnerabilities -6. Insecure dependencies -7. Missing security headers -8. Weak cryptography - -Provide specific file locations and remediation steps.`; - } - - private buildPerformancePrompt(index: ProjectIndex): string { - const complexFiles = Object.entries(index.files) - .filter(([_, file]) => (file as any).complexity > 10) - .slice(0, 15); - - return `Analyze the codebase for performance issues: - -High Complexity Files: -${complexFiles.map(([path, file]) => - `- ${path} (complexity: ${(file as any).complexity || 0})` -).join('\n')} - -Identify: -1. O(n²) or worse algorithms -2. Memory leaks -3. Unnecessary re-renders (React/Vue) -4. Database query optimization needs -5. Missing caching opportunities -6. Bundle size issues -7. Synchronous blocking operations - -Provide specific optimizations with expected impact.`; - } - - private buildArchitecturePrompt(index: ProjectIndex): string { - return `Review the architecture of this codebase: - -Structure: -- Total Files: ${Object.keys(index.files).length} -- Languages: ${this.getLanguageDistribution(index)} - -Analyze: -1. Architectural patterns (MVC, Clean, Hexagonal, etc.) -2. Anti-patterns (God objects, spaghetti code, etc.) -3. Dependency management -4. Module boundaries -5. Coupling and cohesion -6. SOLID principle violations -7. Testing architecture - -Provide architectural improvement suggestions.`; - } - - private buildTestingPrompt(index: ProjectIndex): string { - const untestableFunctions = this.findUntestableFunctions(index); - - return `Analyze test coverage and quality: - -Codebase: -- Total Functions: ${this.countTotalFunctions(index)} -- Potentially Untested: ${untestableFunctions.length} - -Focus: -1. Identify untested critical paths -2. Suggest test cases for complex functions -3. Evaluate existing test quality -4. Generate test implementations -5. Identify testing anti-patterns -6. Suggest testing strategy improvements - -Generate specific test examples for high-risk areas.`; - } - - /** - * Parsing helpers - */ - private parseSecurityFindings(message: any): any { - // Parse security vulnerabilities from assistant message - try { - // Implementation would parse the actual message content - return { - vulnerabilities: [], - recommendations: [] - }; - } catch { - return null; - } - } - - private parsePerformanceFindings(message: any): any { - // Parse performance issues from assistant message - try { - return { - issues: [], - optimizations: [] - }; - } catch { - return null; - } - } - - private parseArchitectureFindings(message: any): any { - // Parse architecture findings from assistant message - try { - return { - patterns: [], - antiPatterns: [], - suggestions: [] - }; - } catch { - return null; - } - } - - private parseTestFindings(message: any): any { - // Parse test findings from assistant message - try { - return { - missingTests: [], - suggestions: [], - qualityIssues: [] - }; - } catch { - return null; - } - } - - /** - * Analysis helpers - */ - private calculateSecurityScore(vulnerabilities: any[]): number { - let score = 100; - - for (const vuln of vulnerabilities) { - if (vuln.severity === 'critical') score -= 20; - else if (vuln.severity === 'high') score -= 10; - else if (vuln.severity === 'medium') score -= 5; - else if (vuln.severity === 'low') score -= 2; - } - - return Math.max(0, score); - } - - private extractPerformanceMetrics(result: string): any { - // Extract performance metrics from analysis result - return { - avgComplexity: 0, - memoryLeaks: 0, - slowQueries: 0 - }; - } - - private analyzeDependencyGraph(index: ProjectIndex): any { - // Analyze dependency relationships - return { - circular: [], - unused: [], - outdated: [] - }; - } - - private calculateTestCoverage(index: ProjectIndex): number { - // Calculate approximate test coverage - const totalFunctions = this.countTotalFunctions(index); - const testedFunctions = this.countTestedFunctions(index); - - return totalFunctions > 0 ? (testedFunctions / totalFunctions) * 100 : 0; - } - - private getLanguageDistribution(index: ProjectIndex): string { - const languages: Record = {}; - - for (const file of Object.values(index.files)) { - languages[file.language] = (languages[file.language] || 0) + 1; - } - - return Object.entries(languages) - .map(([lang, count]) => `${lang}: ${count}`) - .join(', '); - } - - private findUntestableFunctions(index: ProjectIndex): any[] { - const functions: any[] = []; - - for (const [path, file] of Object.entries(index.files)) { - for (const func of file.functions) { - if (!path.includes('test') && !path.includes('spec')) { - functions.push({ path, function: func }); - } - } - } - - return functions; - } - - private countTotalFunctions(index: ProjectIndex): number { - return Object.values(index.files) - .reduce((sum, file) => sum + file.functions.length, 0); - } - - private countTestedFunctions(index: ProjectIndex): number { - // Estimate based on test file presence - return Object.entries(index.files) - .filter(([path]) => path.includes('test') || path.includes('spec')) - .reduce((sum, [_, file]) => sum + file.functions.length, 0); - } -} - -/** - * Type definitions - */ -export interface MCPConfiguration { - mcpServers: { - [key: string]: { - command: string; - args: string[]; - env?: Record; - }; - }; -} - -export interface AnalysisUpdate { - type: 'start' | 'progress' | 'complete' | 'error'; - analysisId: string; - timestamp: string; - message?: string; - progress?: number; - result?: any; - summary?: string; - error?: string; -} - -export interface SecurityAnalysisResult { - type: 'security'; - vulnerabilities: SecurityVulnerability[]; - recommendations: string[]; - score: number; -} - -export interface SecurityVulnerability { - type: string; - severity: 'critical' | 'high' | 'medium' | 'low'; - file: string; - line?: number; - description: string; - remediation: string; - cwe?: string; -} - -export interface PerformanceAnalysisResult { - type: 'performance'; - issues: PerformanceIssue[]; - optimizations: Optimization[]; - metrics: Record; -} - -export interface PerformanceIssue { - type: string; - file: string; - description: string; - impact: 'high' | 'medium' | 'low'; -} - -export interface Optimization { - description: string; - implementation: string; - expectedImprovement: string; -} - -export interface ArchitectureAnalysisResult { - type: 'architecture'; - patterns: string[]; - antiPatterns: string[]; - suggestions: string[]; - dependencies: any; -} - -export interface TestAnalysisResult { - type: 'testing'; - coverage: number; - missingTests: string[]; - testSuggestions: TestSuggestion[]; - qualityIssues: string[]; -} - -export interface TestSuggestion { - file: string; - function: string; - testCases: string[]; - implementation?: string; +/** + * Advanced Claude Code Analyzer using TypeScript SDK + * Leverages the full power of the Claude Code SDK with MCP, streaming, and advanced features + */ + +import { query } from '@anthropic-ai/claude-code'; +import { ProjectIndex} from '../types'; +import { EventEmitter } from 'events'; +import * as fs from 'fs/promises'; +import * as path from 'path'; +import logger from '../utils/logger'; + +/** + * SDK-based Claude analyzer with advanced features + */ +export class ClaudeSDKAnalyzer extends EventEmitter { + private apiKey: string; + private model: string; + private mcpConfig: MCPConfiguration | null = null; + private sessionCache: Map = new Map(); + private abortControllers: Map = new Map(); + + constructor() { + super(); + this.apiKey = process.env.ANTHROPIC_API_KEY || ''; + this.model = process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805'; + + if (!this.apiKey) { + logger.warn('Warning: ANTHROPIC_API_KEY not set. AI features will be limited.'); + } + + // Load MCP configuration if available + this.loadMCPConfig(); + } + + /** + * Load MCP server configuration + */ + private async loadMCPConfig(): Promise { + try { + const configPath = path.join(process.cwd(), 'config', 'mcp-servers.json'); + const configData = await fs.readFile(configPath, 'utf-8'); + this.mcpConfig = JSON.parse(configData); + logger.info('MCP configuration loaded successfully'); + } catch (error) { + logger.info('No MCP configuration found, using default tools only'); + } + } + + /** + * Comprehensive analysis with streaming support + */ + async *analyzeCodebaseStreaming(index: ProjectIndex): AsyncGenerator { + const analysisId = `analysis-${Date.now()}`; + const abortController = new AbortController(); + this.abortControllers.set(analysisId, abortController); + + try { + // Emit start event + yield { + type: 'start', + analysisId, + timestamp: new Date().toISOString(), + message: 'Starting comprehensive codebase analysis...' + }; + + // Run specialized analyses in parallel + const analyses = [ + this.runSecurityAnalysis(index, abortController), + this.runPerformanceAnalysis(index, abortController), + this.runArchitectureAnalysis(index, abortController), + this.runTestCoverageAnalysis(index, abortController) + ]; + + let completedCount = 0; + const totalAnalyses = analyses.length; + + // Wait for all analyses to complete + const results = await Promise.allSettled(analyses); + + for (const result of results) { + completedCount++; + + yield { + type: 'progress', + analysisId, + timestamp: new Date().toISOString(), + progress: (completedCount / totalAnalyses) * 100, + result: result.status === 'fulfilled' ? result.value : null + }; + } + + // Final summary + const summary = await this.generateExecutiveSummary(index, abortController); + + yield { + type: 'complete', + analysisId, + timestamp: new Date().toISOString(), + summary + }; + + } catch (error) { + yield { + type: 'error', + analysisId, + timestamp: new Date().toISOString(), + error: error instanceof Error ? error.message : 'Unknown error' + }; + } finally { + this.abortControllers.delete(analysisId); + } + } + + /** + * Security-focused analysis with MCP tools + */ + private async runSecurityAnalysis( + index: ProjectIndex, + abortController: AbortController + ): Promise { + const results: SecurityAnalysisResult = { + type: 'security', + vulnerabilities: [], + recommendations: [], + score: 100 + }; + + const prompt = this.buildSecurityPrompt(index); + + for await (const message of query({ + prompt, + options: { + abortController, + model: this.model, + customSystemPrompt: `You are a security expert specializing in application security and OWASP compliance. +Analyze code for vulnerabilities, security anti-patterns, and compliance issues. +Focus on: SQL injection, XSS, authentication issues, hardcoded secrets, insecure dependencies.`, + maxTurns: 5, + permissionMode: 'plan', + allowedTools: [ + 'Read', + 'Grep', + 'WebSearch', + ...(this.mcpConfig ? [ + 'mcp__semgrep', + 'mcp__snyk', + 'mcp__github__security_alerts' + ] : []) + ], + // MCP servers configuration would go here if needed + // mcpServers: this.mcpConfig ? loadMcpServers() : undefined + } + })) { + // Process streaming messages + if (message.type === 'assistant') { + this.emit('security-update', message); + + // Parse security findings from the message + const findings = this.parseSecurityFindings(message); + if (findings) { + results.vulnerabilities.push(...findings.vulnerabilities); + results.recommendations.push(...findings.recommendations); + } + } + + if (message.type === 'result') { + // Final processing + results.score = this.calculateSecurityScore(results.vulnerabilities); + + // Store session for follow-up questions + if (message.session_id) { + this.sessionCache.set('security', message.session_id); + } + } + } + + return results; + } + + /** + * Performance analysis with specialized agent + */ + private async runPerformanceAnalysis( + index: ProjectIndex, + abortController: AbortController + ): Promise { + const results: PerformanceAnalysisResult = { + type: 'performance', + issues: [], + optimizations: [], + metrics: {} + }; + + const prompt = this.buildPerformancePrompt(index); + + for await (const message of query({ + prompt, + options: { + abortController, + model: this.model, + customSystemPrompt: `You are a performance engineer specializing in optimization and scalability. +Identify performance bottlenecks, memory leaks, and inefficient algorithms. +Suggest concrete optimizations with measurable impact.`, + maxTurns: 4, + permissionMode: 'plan', + allowedTools: [ + 'Read', + 'Grep', + ...(this.mcpConfig ? [ + 'mcp__profiler', + 'mcp__benchmark', + 'mcp__datadog__metrics' + ] : []) + ] + } + })) { + if (message.type === 'assistant') { + this.emit('performance-update', message); + + const findings = this.parsePerformanceFindings(message); + if (findings) { + results.issues.push(...findings.issues); + results.optimizations.push(...findings.optimizations); + } + } + + if (message.type === 'result') { + results.metrics = this.extractPerformanceMetrics((message as any).result || ''); + + if (message.session_id) { + this.sessionCache.set('performance', message.session_id); + } + } + } + + return results; + } + + /** + * Architecture review with pattern detection + */ + private async runArchitectureAnalysis( + index: ProjectIndex, + abortController: AbortController + ): Promise { + const results: ArchitectureAnalysisResult = { + type: 'architecture', + patterns: [], + antiPatterns: [], + suggestions: [], + dependencies: {} + }; + + const prompt = this.buildArchitecturePrompt(index); + + for await (const message of query({ + prompt, + options: { + abortController, + model: this.model, + customSystemPrompt: `You are a software architect specializing in clean architecture and design patterns. +Identify architectural patterns, anti-patterns, and structural issues. +Suggest improvements for maintainability, scalability, and testability.`, + maxTurns: 4, + permissionMode: 'plan', + allowedTools: [ + 'Read', + 'Grep', + 'Glob', + ...(this.mcpConfig ? [ + 'mcp__dependency_analyzer', + 'mcp__code_metrics' + ] : []) + ] + } + })) { + if (message.type === 'assistant') { + this.emit('architecture-update', message); + + const findings = this.parseArchitectureFindings(message); + if (findings) { + results.patterns.push(...findings.patterns); + results.antiPatterns.push(...findings.antiPatterns); + results.suggestions.push(...findings.suggestions); + } + } + + if (message.type === 'result') { + results.dependencies = this.analyzeDependencyGraph(index); + + if (message.session_id) { + this.sessionCache.set('architecture', message.session_id); + } + } + } + + return results; + } + + /** + * Test coverage and quality analysis + */ + private async runTestCoverageAnalysis( + index: ProjectIndex, + abortController: AbortController + ): Promise { + const results: TestAnalysisResult = { + type: 'testing', + coverage: 0, + missingTests: [], + testSuggestions: [], + qualityIssues: [] + }; + + const prompt = this.buildTestingPrompt(index); + + for await (const message of query({ + prompt, + options: { + abortController, + model: this.model, + customSystemPrompt: `You are a test engineer specializing in comprehensive test coverage and TDD. +Identify untested code, suggest test cases, and evaluate test quality. +Generate specific test implementations for critical paths.`, + maxTurns: 3, + permissionMode: 'acceptEdits', // Allow test generation + allowedTools: [ + 'Read', + 'Write', + 'Grep', + ...(this.mcpConfig ? [ + 'mcp__jest', + 'mcp__coverage_reporter' + ] : []) + ] + } + })) { + if (message.type === 'assistant') { + this.emit('testing-update', message); + + const findings = this.parseTestFindings(message); + if (findings) { + results.missingTests.push(...findings.missingTests); + results.testSuggestions.push(...findings.suggestions); + results.qualityIssues.push(...findings.qualityIssues); + } + } + + if (message.type === 'result') { + results.coverage = this.calculateTestCoverage(index); + + if (message.session_id) { + this.sessionCache.set('testing', message.session_id); + } + } + } + + return results; + } + + /** + * Continue a previous analysis session + */ + async continueSession( + sessionType: 'security' | 'performance' | 'architecture' | 'testing', + followUpPrompt: string + ): Promise { + const sessionId = this.sessionCache.get(sessionType); + + if (!sessionId) { + throw new Error(`No active session found for ${sessionType} analysis`); + } + + const results: any[] = []; + + for await (const message of query({ + prompt: followUpPrompt, + options: { + model: this.model, + continue: sessionId ? true : false, + maxTurns: 2 + } + })) { + if (message.type === 'result') { + return (message as any).result || ''; + } + + results.push(message); + } + + return results; + } + + /** + * Cancel an ongoing analysis + */ + cancelAnalysis(analysisId: string): void { + const controller = this.abortControllers.get(analysisId); + if (controller) { + controller.abort(); + this.abortControllers.delete(analysisId); + this.emit('analysis-cancelled', analysisId); + } + } + + /** + * Generate executive summary + */ + private async generateExecutiveSummary( + index: ProjectIndex, + abortController: AbortController + ): Promise { + const prompt = `Generate an executive summary of the codebase analysis: +- Total files: ${Object.keys(index.files).length} +- Languages: ${this.getLanguageDistribution(index)} +- Key findings from security, performance, architecture, and testing analyses +- Top 3 priority recommendations +- Overall health score`; + + for await (const message of query({ + prompt, + options: { + abortController, + model: this.model, + customSystemPrompt: 'You are a technical lead providing executive summaries.', + maxTurns: 1, + permissionMode: 'plan' + } + })) { + if (message.type === 'result') { + return (message as any).result || ''; + } + } + + return 'Summary generation failed'; + } + + /** + * Helper: Race multiple async generators to completion + */ + // Removed unused raceToCompletion method - replaced with Promise.allSettled + + /** + * Prompt builders + */ + private buildSecurityPrompt(index: ProjectIndex): string { + const criticalFiles = Object.entries(index.files) + .filter(([path]) => + path.includes('auth') || + path.includes('security') || + path.includes('api') || + path.includes('database') + ) + .slice(0, 20); + + return `Analyze the following codebase for security vulnerabilities: + +Project Statistics: +- Total Files: ${Object.keys(index.files).length} +- Critical Security Files: ${criticalFiles.length} + +Focus Areas: +${criticalFiles.map(([path]) => `- ${path}`).join('\n')} + +Identify: +1. OWASP Top 10 vulnerabilities +2. Authentication/authorization issues +3. Hardcoded secrets or API keys +4. SQL injection risks +5. XSS vulnerabilities +6. Insecure dependencies +7. Missing security headers +8. Weak cryptography + +Provide specific file locations and remediation steps.`; + } + + private buildPerformancePrompt(index: ProjectIndex): string { + const complexFiles = Object.entries(index.files) + .filter(([_, file]) => (file as any).complexity > 10) + .slice(0, 15); + + return `Analyze the codebase for performance issues: + +High Complexity Files: +${complexFiles.map(([path, file]) => + `- ${path} (complexity: ${(file as any).complexity || 0})` +).join('\n')} + +Identify: +1. O(n²) or worse algorithms +2. Memory leaks +3. Unnecessary re-renders (React/Vue) +4. Database query optimization needs +5. Missing caching opportunities +6. Bundle size issues +7. Synchronous blocking operations + +Provide specific optimizations with expected impact.`; + } + + private buildArchitecturePrompt(index: ProjectIndex): string { + return `Review the architecture of this codebase: + +Structure: +- Total Files: ${Object.keys(index.files).length} +- Languages: ${this.getLanguageDistribution(index)} + +Analyze: +1. Architectural patterns (MVC, Clean, Hexagonal, etc.) +2. Anti-patterns (God objects, spaghetti code, etc.) +3. Dependency management +4. Module boundaries +5. Coupling and cohesion +6. SOLID principle violations +7. Testing architecture + +Provide architectural improvement suggestions.`; + } + + private buildTestingPrompt(index: ProjectIndex): string { + const untestableFunctions = this.findUntestableFunctions(index); + + return `Analyze test coverage and quality: + +Codebase: +- Total Functions: ${this.countTotalFunctions(index)} +- Potentially Untested: ${untestableFunctions.length} + +Focus: +1. Identify untested critical paths +2. Suggest test cases for complex functions +3. Evaluate existing test quality +4. Generate test implementations +5. Identify testing anti-patterns +6. Suggest testing strategy improvements + +Generate specific test examples for high-risk areas.`; + } + + /** + * Parsing helpers + */ + private parseSecurityFindings(message: any): any { + // Parse security vulnerabilities from assistant message + try { + // Implementation would parse the actual message content + return { + vulnerabilities: [], + recommendations: [] + }; + } catch { + return null; + } + } + + private parsePerformanceFindings(message: any): any { + // Parse performance issues from assistant message + try { + return { + issues: [], + optimizations: [] + }; + } catch { + return null; + } + } + + private parseArchitectureFindings(message: any): any { + // Parse architecture findings from assistant message + try { + return { + patterns: [], + antiPatterns: [], + suggestions: [] + }; + } catch { + return null; + } + } + + private parseTestFindings(message: any): any { + // Parse test findings from assistant message + try { + return { + missingTests: [], + suggestions: [], + qualityIssues: [] + }; + } catch { + return null; + } + } + + /** + * Analysis helpers + */ + private calculateSecurityScore(vulnerabilities: any[]): number { + let score = 100; + + for (const vuln of vulnerabilities) { + if (vuln.severity === 'critical') score -= 20; + else if (vuln.severity === 'high') score -= 10; + else if (vuln.severity === 'medium') score -= 5; + else if (vuln.severity === 'low') score -= 2; + } + + return Math.max(0, score); + } + + private extractPerformanceMetrics(result: string): any { + // Extract performance metrics from analysis result + return { + avgComplexity: 0, + memoryLeaks: 0, + slowQueries: 0 + }; + } + + private analyzeDependencyGraph(index: ProjectIndex): any { + // Analyze dependency relationships + return { + circular: [], + unused: [], + outdated: [] + }; + } + + private calculateTestCoverage(index: ProjectIndex): number { + // Calculate approximate test coverage + const totalFunctions = this.countTotalFunctions(index); + const testedFunctions = this.countTestedFunctions(index); + + return totalFunctions > 0 ? (testedFunctions / totalFunctions) * 100 : 0; + } + + private getLanguageDistribution(index: ProjectIndex): string { + const languages: Record = {}; + + for (const file of Object.values(index.files)) { + languages[file.language] = (languages[file.language] || 0) + 1; + } + + return Object.entries(languages) + .map(([lang, count]) => `${lang}: ${count}`) + .join(', '); + } + + private findUntestableFunctions(index: ProjectIndex): any[] { + const functions: any[] = []; + + for (const [path, file] of Object.entries(index.files)) { + for (const func of file.functions) { + if (!path.includes('test') && !path.includes('spec')) { + functions.push({ path, function: func }); + } + } + } + + return functions; + } + + private countTotalFunctions(index: ProjectIndex): number { + return Object.values(index.files) + .reduce((sum, file) => sum + file.functions.length, 0); + } + + private countTestedFunctions(index: ProjectIndex): number { + // Estimate based on test file presence + return Object.entries(index.files) + .filter(([path]) => path.includes('test') || path.includes('spec')) + .reduce((sum, [_, file]) => sum + file.functions.length, 0); + } +} + +/** + * Type definitions + */ +export interface MCPConfiguration { + mcpServers: { + [key: string]: { + command: string; + args: string[]; + env?: Record; + }; + }; +} + +export interface AnalysisUpdate { + type: 'start' | 'progress' | 'complete' | 'error'; + analysisId: string; + timestamp: string; + message?: string; + progress?: number; + result?: any; + summary?: string; + error?: string; +} + +export interface SecurityAnalysisResult { + type: 'security'; + vulnerabilities: SecurityVulnerability[]; + recommendations: string[]; + score: number; +} + +export interface SecurityVulnerability { + type: string; + severity: 'critical' | 'high' | 'medium' | 'low'; + file: string; + line?: number; + description: string; + remediation: string; + cwe?: string; +} + +export interface PerformanceAnalysisResult { + type: 'performance'; + issues: PerformanceIssue[]; + optimizations: Optimization[]; + metrics: Record; +} + +export interface PerformanceIssue { + type: string; + file: string; + description: string; + impact: 'high' | 'medium' | 'low'; +} + +export interface Optimization { + description: string; + implementation: string; + expectedImprovement: string; +} + +export interface ArchitectureAnalysisResult { + type: 'architecture'; + patterns: string[]; + antiPatterns: string[]; + suggestions: string[]; + dependencies: any; +} + +export interface TestAnalysisResult { + type: 'testing'; + coverage: number; + missingTests: string[]; + testSuggestions: TestSuggestion[]; + qualityIssues: string[]; +} + +export interface TestSuggestion { + file: string; + function: string; + testCases: string[]; + implementation?: string; } \ No newline at end of file diff --git a/src/api/controllers/ai.controller.ts b/src/api/controllers/ai.controller.ts index e0b0135..41641a7 100644 --- a/src/api/controllers/ai.controller.ts +++ b/src/api/controllers/ai.controller.ts @@ -1,306 +1,306 @@ -/** - * AI Controller - * Handles AI analysis and intelligence endpoints - */ - -import { Request, Response } from 'express'; -import { BaseController } from './base.controller'; -import { Indexer } from '../../core/indexer'; -import { ClaudeCodeAnalyzer } from '../../ai/claude-analyzer'; -import HybridAIAnalyzer from '../../ai/hybrid-analyzer'; - -export class AIController extends BaseController { - private aiAnalyzer: ClaudeCodeAnalyzer; - private hybridAnalyzer: HybridAIAnalyzer; - - constructor(indexer: Indexer) { - super(indexer); - this.aiAnalyzer = new ClaudeCodeAnalyzer(); - this.hybridAnalyzer = new HybridAIAnalyzer(); - } - - /** - * Run comprehensive AI analysis - * POST /api/ai/analyze - */ - analyze = async (req: Request, res: Response) => { - try { - const { path: analyzePath, options, useHybrid = true } = req.body; - - // Get current index or build new one - let index = await this.indexer.getIndex(); - if (!index || Object.keys(index.files).length === 0) { - index = await this.indexer.buildIndex(analyzePath || process.cwd(), options); - } - - // Use hybrid analyzer for better language-specific analysis - const analysis = useHybrid - ? await this.hybridAnalyzer.analyzeCodebase(index) - : await this.aiAnalyzer.analyzeCodebase(index); - - this.success(res, analysis); - } catch (error) { - this.handleError(error, res, 'AIController.analyze'); - } - }; - - /** - * Predict potential bugs - * POST /api/ai/predict-bugs - */ - predictBugs = async (req: Request, res: Response) => { - try { - const { files } = req.body; - const index = await this.indexer.getIndex(); - - if (!index) { - return this.error(res, 'No index available', 400); - } - - // Filter index if specific files requested - let filteredIndex = index; - if (files && Array.isArray(files)) { - filteredIndex = { - ...index, - files: Object.fromEntries( - Object.entries(index.files).filter(([path]) => files.includes(path)) - ) - }; - } - - const predictions = await this.aiAnalyzer.predictBugs(filteredIndex); - this.success(res, predictions); - } catch (error) { - this.handleError(error, res, 'AIController.predictBugs'); - } - }; - - /** - * Detect code smells - * POST /api/ai/detect-smells - */ - detectCodeSmells = async (req: Request, res: Response) => { - try { - const index = await this.indexer.getIndex(); - - if (!index) { - return this.error(res, 'No index available', 400); - } - - const smells = await this.aiAnalyzer.detectCodeSmells(index); - this.success(res, smells); - } catch (error) { - this.handleError(error, res, 'AIController.detectCodeSmells'); - } - }; - - /** - * Analyze security vulnerabilities - * POST /api/ai/analyze-security - */ - analyzeSecurity = async (req: Request, res: Response) => { - try { - const index = await this.indexer.getIndex(); - - if (!index) { - return this.error(res, 'No index available', 400); - } - - const issues = await this.aiAnalyzer.analyzeSecurity(index); - this.success(res, issues); - } catch (error) { - this.handleError(error, res, 'AIController.analyzeSecurity'); - } - }; - - /** - * Suggest refactoring - * POST /api/ai/suggest-refactoring - */ - suggestRefactoring = async (req: Request, res: Response) => { - try { - const { file } = req.body; - const index = await this.indexer.getIndex(); - - if (!index) { - return this.error(res, 'No index available', 400); - } - - // Filter to specific file if requested - let filteredIndex = index; - if (file) { - if (!index.files[file]) { - return this.error(res, 'File not found', 404); - } - filteredIndex = { - ...index, - files: { [file]: index.files[file] } - }; - } - - const suggestions = await this.aiAnalyzer.suggestRefactoring(filteredIndex); - this.success(res, suggestions); - } catch (error) { - this.handleError(error, res, 'AIController.suggestRefactoring'); - } - }; - - /** - * Generate test suggestions - * POST /api/ai/generate-tests - */ - generateTests = async (req: Request, res: Response) => { - try { - const { function: funcName, file } = req.body; - const index = await this.indexer.getIndex(); - - if (!index) { - return this.error(res, 'No index available', 400); - } - - // Filter to specific function if requested - let filteredIndex = index; - if (file && funcName) { - if (!index.files[file]) { - return this.error(res, 'File not found', 404); - } - const func = index.files[file].functions.find(f => f.name === funcName); - if (!func) { - return this.error(res, 'Function not found', 404); - } - // Create minimal index with just this function - filteredIndex = { - ...index, - files: { - [file]: { - ...index.files[file], - functions: [func] - } - } - }; - } - - const tests = await this.aiAnalyzer.generateTestSuggestions(filteredIndex); - this.success(res, tests); - } catch (error) { - this.handleError(error, res, 'AIController.generateTests'); - } - }; - - /** - * Analyze Python skills repository - * POST /api/ai/analyze-skills - */ - analyzeSkills = async (req: Request, res: Response) => { - try { - const { path: skillsPath } = req.body; - - if (!skillsPath) { - return this.error(res, 'Skills path is required', 400); - } - - // Use hybrid analyzer for Python skills - const analysis = await this.hybridAnalyzer.analyzeSkillsRepository(skillsPath); - - this.success(res, analysis); - } catch (error) { - this.handleError(error, res, 'AIController.analyzeSkills'); - } - }; - - /** - * Python-specific analysis - * POST /api/ai/analyze-python - */ - analyzePython = async (req: Request, res: Response) => { - try { - const { files } = req.body; - const index = await this.indexer.getIndex(); - - if (!index) { - return this.error(res, 'No index available', 400); - } - - // Filter to Python files only - const pythonFiles = files - ? files.filter((f: string) => f.endsWith('.py')) - : Object.keys(index.files).filter(f => f.endsWith('.py')); - - if (pythonFiles.length === 0) { - return this.error(res, 'No Python files found', 400); - } - - // Create Python-only index - const pythonIndex = { - ...index, - files: Object.fromEntries( - pythonFiles - .filter(f => index.files[f]) - .map(f => [f, index.files[f]]) - ) - }; - - // Use hybrid analyzer for Python-specific analysis - const analysis = await this.hybridAnalyzer.analyzeCodebase(pythonIndex); - - this.success(res, analysis); - } catch (error) { - this.handleError(error, res, 'AIController.analyzePython'); - } - }; - - /** - * Get language-specific statistics - * GET /api/ai/language-stats - */ - getLanguageStats = async (req: Request, res: Response) => { - try { - const index = await this.indexer.getIndex(); - - if (!index) { - return this.error(res, 'No index available', 400); - } - - // Calculate language statistics - const stats: Record = {}; - - for (const [filePath, fileData] of Object.entries(index.files)) { - const lang = fileData.language.toLowerCase(); - - if (!stats[lang]) { - stats[lang] = { - files: 0, - functions: 0, - classes: 0, - complexity: 0, - analyzer: lang === 'python' ? 'Python SDK' : 'TypeScript SDK' - }; - } - - stats[lang].files++; - stats[lang].functions += fileData.functions.length; - stats[lang].classes += fileData.classes.length; - stats[lang].complexity += (fileData as any).complexity || 0; - } - - // Add recommendations - const recommendations = []; - if (stats.python && stats.python.files > 10) { - recommendations.push('Use Python SDK analyzer for better Python-specific insights'); - } - if (stats.typescript || stats.javascript) { - recommendations.push('TypeScript SDK provides native JS/TS analysis'); - } - - this.success(res, { - languages: stats, - recommendations, - hybridAnalyzerEnabled: true, - pythonSdkAvailable: true, - typescriptSdkAvailable: true - }); - } catch (error) { - this.handleError(error, res, 'AIController.getLanguageStats'); - } - }; +/** + * AI Controller + * Handles AI analysis and intelligence endpoints + */ + +import { Request, Response } from 'express'; +import { BaseController } from './base.controller'; +import { Indexer } from '../../core/indexer'; +import { ClaudeCodeAnalyzer } from '../../ai/claude-analyzer'; +import HybridAIAnalyzer from '../../ai/hybrid-analyzer'; + +export class AIController extends BaseController { + private aiAnalyzer: ClaudeCodeAnalyzer; + private hybridAnalyzer: HybridAIAnalyzer; + + constructor(indexer: Indexer) { + super(indexer); + this.aiAnalyzer = new ClaudeCodeAnalyzer(); + this.hybridAnalyzer = new HybridAIAnalyzer(); + } + + /** + * Run comprehensive AI analysis + * POST /api/ai/analyze + */ + analyze = async (req: Request, res: Response) => { + try { + const { path: analyzePath, options, useHybrid = true } = req.body; + + // Get current index or build new one + let index = await this.indexer.getIndex(); + if (!index || Object.keys(index.files).length === 0) { + index = await this.indexer.buildIndex(analyzePath || process.cwd(), options); + } + + // Use hybrid analyzer for better language-specific analysis + const analysis = useHybrid + ? await this.hybridAnalyzer.analyzeCodebase(index) + : await this.aiAnalyzer.analyzeCodebase(index); + + this.success(res, analysis); + } catch (error) { + this.handleError(error, res, 'AIController.analyze'); + } + }; + + /** + * Predict potential bugs + * POST /api/ai/predict-bugs + */ + predictBugs = async (req: Request, res: Response) => { + try { + const { files } = req.body; + const index = await this.indexer.getIndex(); + + if (!index) { + return this.error(res, 'No index available', 400); + } + + // Filter index if specific files requested + let filteredIndex = index; + if (files && Array.isArray(files)) { + filteredIndex = { + ...index, + files: Object.fromEntries( + Object.entries(index.files).filter(([path]) => files.includes(path)) + ) + }; + } + + const predictions = await this.aiAnalyzer.predictBugs(filteredIndex); + this.success(res, predictions); + } catch (error) { + this.handleError(error, res, 'AIController.predictBugs'); + } + }; + + /** + * Detect code smells + * POST /api/ai/detect-smells + */ + detectCodeSmells = async (req: Request, res: Response) => { + try { + const index = await this.indexer.getIndex(); + + if (!index) { + return this.error(res, 'No index available', 400); + } + + const smells = await this.aiAnalyzer.detectCodeSmells(index); + this.success(res, smells); + } catch (error) { + this.handleError(error, res, 'AIController.detectCodeSmells'); + } + }; + + /** + * Analyze security vulnerabilities + * POST /api/ai/analyze-security + */ + analyzeSecurity = async (req: Request, res: Response) => { + try { + const index = await this.indexer.getIndex(); + + if (!index) { + return this.error(res, 'No index available', 400); + } + + const issues = await this.aiAnalyzer.analyzeSecurity(index); + this.success(res, issues); + } catch (error) { + this.handleError(error, res, 'AIController.analyzeSecurity'); + } + }; + + /** + * Suggest refactoring + * POST /api/ai/suggest-refactoring + */ + suggestRefactoring = async (req: Request, res: Response) => { + try { + const { file } = req.body; + const index = await this.indexer.getIndex(); + + if (!index) { + return this.error(res, 'No index available', 400); + } + + // Filter to specific file if requested + let filteredIndex = index; + if (file) { + if (!index.files[file]) { + return this.error(res, 'File not found', 404); + } + filteredIndex = { + ...index, + files: { [file]: index.files[file] } + }; + } + + const suggestions = await this.aiAnalyzer.suggestRefactoring(filteredIndex); + this.success(res, suggestions); + } catch (error) { + this.handleError(error, res, 'AIController.suggestRefactoring'); + } + }; + + /** + * Generate test suggestions + * POST /api/ai/generate-tests + */ + generateTests = async (req: Request, res: Response) => { + try { + const { function: funcName, file } = req.body; + const index = await this.indexer.getIndex(); + + if (!index) { + return this.error(res, 'No index available', 400); + } + + // Filter to specific function if requested + let filteredIndex = index; + if (file && funcName) { + if (!index.files[file]) { + return this.error(res, 'File not found', 404); + } + const func = index.files[file].functions.find(f => f.name === funcName); + if (!func) { + return this.error(res, 'Function not found', 404); + } + // Create minimal index with just this function + filteredIndex = { + ...index, + files: { + [file]: { + ...index.files[file], + functions: [func] + } + } + }; + } + + const tests = await this.aiAnalyzer.generateTestSuggestions(filteredIndex); + this.success(res, tests); + } catch (error) { + this.handleError(error, res, 'AIController.generateTests'); + } + }; + + /** + * Analyze Python skills repository + * POST /api/ai/analyze-skills + */ + analyzeSkills = async (req: Request, res: Response) => { + try { + const { path: skillsPath } = req.body; + + if (!skillsPath) { + return this.error(res, 'Skills path is required', 400); + } + + // Use hybrid analyzer for Python skills + const analysis = await this.hybridAnalyzer.analyzeSkillsRepository(skillsPath); + + this.success(res, analysis); + } catch (error) { + this.handleError(error, res, 'AIController.analyzeSkills'); + } + }; + + /** + * Python-specific analysis + * POST /api/ai/analyze-python + */ + analyzePython = async (req: Request, res: Response) => { + try { + const { files } = req.body; + const index = await this.indexer.getIndex(); + + if (!index) { + return this.error(res, 'No index available', 400); + } + + // Filter to Python files only + const pythonFiles = files + ? files.filter((f: string) => f.endsWith('.py')) + : Object.keys(index.files).filter(f => f.endsWith('.py')); + + if (pythonFiles.length === 0) { + return this.error(res, 'No Python files found', 400); + } + + // Create Python-only index + const pythonIndex = { + ...index, + files: Object.fromEntries( + pythonFiles + .filter((f: string) => index.files[f]) + .map((f: string) => [f, index.files[f]]) + ) + }; + + // Use hybrid analyzer for Python-specific analysis + const analysis = await this.hybridAnalyzer.analyzeCodebase(pythonIndex); + + this.success(res, analysis); + } catch (error) { + this.handleError(error, res, 'AIController.analyzePython'); + } + }; + + /** + * Get language-specific statistics + * GET /api/ai/language-stats + */ + getLanguageStats = async (req: Request, res: Response) => { + try { + const index = await this.indexer.getIndex(); + + if (!index) { + return this.error(res, 'No index available', 400); + } + + // Calculate language statistics + const stats: Record = {}; + + for (const [filePath, fileData] of Object.entries(index.files)) { + const lang = fileData.language.toLowerCase(); + + if (!stats[lang]) { + stats[lang] = { + files: 0, + functions: 0, + classes: 0, + complexity: 0, + analyzer: lang === 'python' ? 'Python SDK' : 'TypeScript SDK' + }; + } + + stats[lang].files++; + stats[lang].functions += fileData.functions.length; + stats[lang].classes += fileData.classes.length; + stats[lang].complexity += (fileData as any).complexity || 0; + } + + // Add recommendations + const recommendations = []; + if (stats.python && stats.python.files > 10) { + recommendations.push('Use Python SDK analyzer for better Python-specific insights'); + } + if (stats.typescript || stats.javascript) { + recommendations.push('TypeScript SDK provides native JS/TS analysis'); + } + + this.success(res, { + languages: stats, + recommendations, + hybridAnalyzerEnabled: true, + pythonSdkAvailable: true, + typescriptSdkAvailable: true + }); + } catch (error) { + this.handleError(error, res, 'AIController.getLanguageStats'); + } + }; } \ No newline at end of file diff --git a/src/api/controllers/base.controller.ts b/src/api/controllers/base.controller.ts index 78a11fe..7ac142a 100644 --- a/src/api/controllers/base.controller.ts +++ b/src/api/controllers/base.controller.ts @@ -1,67 +1,67 @@ -/** - * Base Controller - * Provides common functionality for all controllers - */ - -import { Request, Response, NextFunction } from 'express'; -import { Indexer } from '../../core/indexer'; -import logger from '../../utils/logger'; - -export class BaseController { - protected indexer: Indexer; - - constructor(indexer: Indexer) { - this.indexer = indexer; - } - - /** - * Wrap async route handlers to catch errors - */ - protected asyncHandler(fn: Function) { - return (req: Request, res: Response, next: NextFunction) => { - Promise.resolve(fn(req, res, next)).catch(next); - }; - } - - /** - * Send success response - */ - protected success(res: Response, data: any, message?: string) { - res.json({ - success: true, - message: message || 'Success', - data - }); - } - - /** - * Send error response - */ - protected error(res: Response, message: string, statusCode: number = 500) { - res.status(statusCode).json({ - success: false, - message, - error: message - }); - } - - /** - * Validate required fields in request body - */ - protected validateRequired(body: any, fields: string[]): string | null { - for (const field of fields) { - if (!body[field]) { - return `Missing required field: ${field}`; - } - } - return null; - } - - /** - * Log and handle errors - */ - protected handleError(error: any, res: Response, context: string) { - logger.error(`Error in ${context}:`, error); - this.error(res, error.message || 'Internal server error', 500); - } +/** + * Base Controller + * Provides common functionality for all controllers + */ + +import { Request, Response, NextFunction } from 'express'; +import { Indexer } from '../../core/indexer'; +import logger from '../../utils/logger'; + +export class BaseController { + protected indexer: Indexer; + + constructor(indexer: Indexer) { + this.indexer = indexer; + } + + /** + * Wrap async route handlers to catch errors + */ + protected asyncHandler(fn: Function) { + return (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req, res, next)).catch(next); + }; + } + + /** + * Send success response + */ + protected success(res: Response, data: any, message?: string) { + res.json({ + success: true, + message: message || 'Success', + data + }); + } + + /** + * Send error response + */ + protected error(res: Response, message: string, statusCode: number = 500) { + res.status(statusCode).json({ + success: false, + message, + error: message + }); + } + + /** + * Validate required fields in request body + */ + protected validateRequired(body: any, fields: string[]): string | null { + for (const field of fields) { + if (!body[field]) { + return `Missing required field: ${field}`; + } + } + return null; + } + + /** + * Log and handle errors + */ + protected handleError(error: any, res: Response, context: string) { + logger.error(`Error in ${context}:`, error); + this.error(res, error.message || 'Internal server error', 500); + } } \ No newline at end of file diff --git a/src/api/controllers/export.controller.ts b/src/api/controllers/export.controller.ts index 52002c4..bb18c04 100644 --- a/src/api/controllers/export.controller.ts +++ b/src/api/controllers/export.controller.ts @@ -1,35 +1,35 @@ -/** - * Export Controller - * Handles index export in various formats - */ - -import { Request, Response } from 'express'; -import { BaseController } from './base.controller'; -import { Indexer } from '../../core/indexer'; -import { ExportRequest } from '../types'; - -export class ExportController extends BaseController { - constructor(indexer: Indexer) { - super(indexer); - } - - /** - * Export index in specified format - * POST /api/export - */ - exportIndex = async (req: Request, res: Response) => { - try { - const { format, options } = req.body as ExportRequest; - - if (!format) { - return this.error(res, 'Format is required', 400); - } - - const exportResult = await this.indexer.export(format, options); - - this.success(res, exportResult, 'Index exported successfully'); - } catch (error) { - this.handleError(error, res, 'ExportController.exportIndex'); - } - }; +/** + * Export Controller + * Handles index export in various formats + */ + +import { Request, Response } from 'express'; +import { BaseController } from './base.controller'; +import { Indexer } from '../../core/indexer'; +import { ExportRequest } from '../types'; + +export class ExportController extends BaseController { + constructor(indexer: Indexer) { + super(indexer); + } + + /** + * Export index in specified format + * POST /api/export + */ + exportIndex = async (req: Request, res: Response) => { + try { + const { format, options } = req.body as ExportRequest; + + if (!format) { + return this.error(res, 'Format is required', 400); + } + + const exportResult = await this.indexer.export(format, options); + + this.success(res, exportResult, 'Index exported successfully'); + } catch (error) { + this.handleError(error, res, 'ExportController.exportIndex'); + } + }; } \ No newline at end of file diff --git a/src/api/controllers/files.controller.ts b/src/api/controllers/files.controller.ts index 60307f5..15d5f70 100644 --- a/src/api/controllers/files.controller.ts +++ b/src/api/controllers/files.controller.ts @@ -1,158 +1,168 @@ -/** - * Files Controller - * Handles file management endpoints - */ - -import { Request, Response } from 'express'; -import { BaseController } from './base.controller'; -import { Indexer } from '../../core/indexer'; - -export class FilesController extends BaseController { - constructor(indexer: Indexer) { - super(indexer); - } - - /** - * List indexed files - * GET /api/files - */ - getFiles = async (req: Request, res: Response) => { - try { - const { language, limit = 100, offset = 0 } = req.query; - - const index = await this.indexer.getIndex(); - let files = Object.keys(index.files); - - // Filter by language if specified - if (language) { - files = files.filter(f => - index.files[f].language === language - ); - } - - // Pagination - const paginatedFiles = files.slice( - Number(offset), - Number(offset) + Number(limit) - ); - - this.success(res, { - files: paginatedFiles.map(f => ({ - path: f, - ...index.files[f] - })), - total: files.length, - limit: Number(limit), - offset: Number(offset) - }); - } catch (error) { - this.handleError(error, res, 'FilesController.getFiles'); - } - }; - - /** - * Get details for a specific file - * GET /api/files/:filePath - */ - getFile = async (req: Request, res: Response) => { - try { - const filePath = req.params[0]; - const index = await this.indexer.getIndex(); - - const fileData = index.files[filePath]; - if (!fileData) { - return this.error(res, 'File not found in index', 404); - } - - this.success(res, { - path: filePath, - ...fileData - }); - } catch (error) { - this.handleError(error, res, 'FilesController.getFile'); - } - }; - - /** - * Start/stop file watching - * POST /api/watch - */ - watchFiles = async (req: Request, res: Response) => { - try { - const { action, path: watchPath } = req.body; - - if (action === 'start') { - await this.indexer.startWatching(watchPath || process.cwd()); - this.success(res, { status: 'watching' }); - } else if (action === 'stop') { - await this.indexer.stopWatching(); - this.success(res, { status: 'stopped' }); - } else { - this.error(res, 'Invalid action', 400); - } - } catch (error) { - this.handleError(error, res, 'FilesController.watchFiles'); - } - }; - - /** - * Get comprehensive statistics - * GET /api/stats - */ - getStats = async (req: Request, res: Response) => { - try { - const index = await this.indexer.getIndex(); - - const stats = { - totalFiles: Object.keys(index.files).length, - totalFunctions: 0, - totalClasses: 0, - totalImports: 0, - totalExports: 0, - languages: {} as Record, - complexity: { - average: 0, - max: 0, - distribution: {} as Record - }, - indexingTime: 0, - memoryUsage: process.memoryUsage().heapUsed - }; - - // Calculate statistics - const complexities: number[] = []; - - for (const fileData of Object.values(index.files)) { - stats.totalFunctions += fileData.functions.length; - stats.totalClasses += fileData.classes.length; - stats.totalImports += fileData.imports.length; - stats.totalExports += fileData.exports.length; - - // Language distribution - stats.languages[fileData.language] = (stats.languages[fileData.language] || 0) + 1; - - // Complexity - if ((fileData as any).complexity) { - complexities.push((fileData as any).complexity); - } - } - - // Calculate complexity stats - if (complexities.length > 0) { - stats.complexity.average = complexities.reduce((a, b) => a + b, 0) / complexities.length; - stats.complexity.max = Math.max(...complexities); - - // Complexity distribution - for (const c of complexities) { - const bucket = Math.floor(c / 10) * 10; - const key = `${bucket}-${bucket + 9}`; - stats.complexity.distribution[key] = (stats.complexity.distribution[key] || 0) + 1; - } - } - - this.success(res, stats); - } catch (error) { - this.handleError(error, res, 'FilesController.getStats'); - } - }; +/** + * Files Controller + * Handles file management endpoints + */ + +import { Request, Response } from 'express'; +import { BaseController } from './base.controller'; +import { Indexer } from '../../core/indexer'; + +export class FilesController extends BaseController { + constructor(indexer: Indexer) { + super(indexer); + } + + /** + * List indexed files + * GET /api/files + */ + getFiles = async (req: Request, res: Response) => { + try { + const { language, limit = 100, offset = 0 } = req.query; + + const index = await this.indexer.getIndex(); + if (!index) { + return this.error(res, 'No index found', 404); + } + + let files = Object.keys(index.files); + + // Filter by language if specified + if (language) { + files = files.filter(f => + index.files[f].language === language + ); + } + + // Pagination + const paginatedFiles = files.slice( + Number(offset), + Number(offset) + Number(limit) + ); + + this.success(res, { + files: paginatedFiles.map(f => ({ + path: f, + ...index.files[f] + })), + total: files.length, + limit: Number(limit), + offset: Number(offset) + }); + } catch (error) { + this.handleError(error, res, 'FilesController.getFiles'); + } + }; + + /** + * Get details for a specific file + * GET /api/files/:filePath + */ + getFile = async (req: Request, res: Response) => { + try { + const filePath = req.params[0]; + const index = await this.indexer.getIndex(); + if (!index) { + return this.error(res, 'No index found', 404); + } + + const fileData = index.files[filePath]; + if (!fileData) { + return this.error(res, 'File not found in index', 404); + } + + this.success(res, { + path: filePath, + ...fileData + }); + } catch (error) { + this.handleError(error, res, 'FilesController.getFile'); + } + }; + + /** + * Start/stop file watching + * POST /api/watch + */ + watchFiles = async (req: Request, res: Response) => { + try { + const { action, path: watchPath } = req.body; + + if (action === 'start') { + await this.indexer.startWatching(watchPath || process.cwd()); + this.success(res, { status: 'watching' }); + } else if (action === 'stop') { + await this.indexer.stopWatching(); + this.success(res, { status: 'stopped' }); + } else { + this.error(res, 'Invalid action', 400); + } + } catch (error) { + this.handleError(error, res, 'FilesController.watchFiles'); + } + }; + + /** + * Get comprehensive statistics + * GET /api/stats + */ + getStats = async (req: Request, res: Response) => { + try { + const index = await this.indexer.getIndex(); + if (!index) { + return this.error(res, 'No index found', 404); + } + + const stats = { + totalFiles: Object.keys(index.files).length, + totalFunctions: 0, + totalClasses: 0, + totalImports: 0, + totalExports: 0, + languages: {} as Record, + complexity: { + average: 0, + max: 0, + distribution: {} as Record + }, + indexingTime: 0, + memoryUsage: process.memoryUsage().heapUsed + }; + + // Calculate statistics + const complexities: number[] = []; + + for (const fileData of Object.values(index.files)) { + stats.totalFunctions += fileData.functions.length; + stats.totalClasses += fileData.classes.length; + stats.totalImports += fileData.imports.length; + stats.totalExports += fileData.exports.length; + + // Language distribution + stats.languages[fileData.language] = (stats.languages[fileData.language] || 0) + 1; + + // Complexity + if ((fileData as any).complexity) { + complexities.push((fileData as any).complexity); + } + } + + // Calculate complexity stats + if (complexities.length > 0) { + stats.complexity.average = complexities.reduce((a, b) => a + b, 0) / complexities.length; + stats.complexity.max = Math.max(...complexities); + + // Complexity distribution + for (const c of complexities) { + const bucket = Math.floor(c / 10) * 10; + const key = `${bucket}-${bucket + 9}`; + stats.complexity.distribution[key] = (stats.complexity.distribution[key] || 0) + 1; + } + } + + this.success(res, stats); + } catch (error) { + this.handleError(error, res, 'FilesController.getStats'); + } + }; } \ No newline at end of file diff --git a/src/api/controllers/health.controller.ts b/src/api/controllers/health.controller.ts index d9cc81d..47f9a36 100644 --- a/src/api/controllers/health.controller.ts +++ b/src/api/controllers/health.controller.ts @@ -1,61 +1,61 @@ -/** - * Health Controller - * Handles health check endpoints - */ - -import { Request, Response } from 'express'; -import { BaseController } from './base.controller'; -import { Indexer } from '../../core/indexer'; -import * as fs from 'fs/promises'; -import * as path from 'path'; - -export class HealthController extends BaseController { - constructor(indexer: Indexer) { - super(indexer); - } - - /** - * Basic health check - * GET /api/health - */ - getHealth = async (req: Request, res: Response) => { - try { - this.success(res, { - status: 'healthy', - timestamp: new Date().toISOString() - }); - } catch (error) { - this.handleError(error, res, 'HealthController.getHealth'); - } - }; - - /** - * Detailed health check with component status - * GET /api/health/detailed - */ - getDetailedHealth = async (req: Request, res: Response) => { - try { - // Get package.json version - const packageJsonPath = path.join(process.cwd(), 'package.json'); - const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')); - - const health = { - status: 'healthy', - timestamp: new Date().toISOString(), - uptime: process.uptime(), - memory: process.memoryUsage(), - components: { - indexer: 'healthy', - cache: 'healthy', - watcher: this.indexer.isWatching() ? 'active' : 'inactive', - ai: process.env.ANTHROPIC_API_KEY ? 'configured' : 'not configured' - }, - version: packageJson.version - }; - - this.success(res, health); - } catch (error) { - this.handleError(error, res, 'HealthController.getDetailedHealth'); - } - }; +/** + * Health Controller + * Handles health check endpoints + */ + +import { Request, Response } from 'express'; +import { BaseController } from './base.controller'; +import { Indexer } from '../../core/indexer'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +export class HealthController extends BaseController { + constructor(indexer: Indexer) { + super(indexer); + } + + /** + * Basic health check + * GET /api/health + */ + getHealth = async (req: Request, res: Response) => { + try { + this.success(res, { + status: 'healthy', + timestamp: new Date().toISOString() + }); + } catch (error) { + this.handleError(error, res, 'HealthController.getHealth'); + } + }; + + /** + * Detailed health check with component status + * GET /api/health/detailed + */ + getDetailedHealth = async (req: Request, res: Response) => { + try { + // Get package.json version + const packageJsonPath = path.join(process.cwd(), 'package.json'); + const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')); + + const health = { + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + memory: process.memoryUsage(), + components: { + indexer: 'healthy', + cache: 'healthy', + watcher: this.indexer.isWatching() ? 'active' : 'inactive', + ai: process.env.ANTHROPIC_API_KEY ? 'configured' : 'not configured' + }, + version: packageJson.version + }; + + this.success(res, health); + } catch (error) { + this.handleError(error, res, 'HealthController.getDetailedHealth'); + } + }; } \ No newline at end of file diff --git a/src/api/controllers/index.ts b/src/api/controllers/index.ts index bed24c3..d85a06e 100644 --- a/src/api/controllers/index.ts +++ b/src/api/controllers/index.ts @@ -1,12 +1,12 @@ -/** - * API Controllers Registry - * Central export point for all API controllers - */ - -export * from './base.controller'; -export * from './indexer.controller'; -export * from './query.controller'; -export * from './files.controller'; -export * from './export.controller'; -export * from './health.controller'; +/** + * API Controllers Registry + * Central export point for all API controllers + */ + +export * from './base.controller'; +export * from './indexer.controller'; +export * from './query.controller'; +export * from './files.controller'; +export * from './export.controller'; +export * from './health.controller'; export * from './ai.controller'; \ No newline at end of file diff --git a/src/api/controllers/indexer.controller.ts b/src/api/controllers/indexer.controller.ts index c9197ed..6fa897b 100644 --- a/src/api/controllers/indexer.controller.ts +++ b/src/api/controllers/indexer.controller.ts @@ -1,100 +1,107 @@ -/** - * Indexer Controller - * Handles index building and management endpoints - */ - -import { Request, Response } from 'express'; -import { BaseController } from './base.controller'; -import { Indexer } from '../../core/indexer'; -import { IndexerOptions } from '../../types'; -import * as fs from 'fs/promises'; - -export class IndexerController extends BaseController { - constructor(indexer: Indexer) { - super(indexer); - } - - /** - * Build a new index - * POST /api/index - */ - buildIndex = async (req: Request, res: Response) => { - try { - const options: IndexerOptions = { - incremental: req.body.incremental, - parallel: req.body.parallel !== false, - workers: req.body.workers, - force: req.body.force - }; - - const directory = req.body.directory || process.cwd(); - const index = await this.indexer.buildIndex(directory, options); - - this.success(res, { - statistics: index.statistics, - timestamp: index.timestamp - }, 'Index built successfully'); - } catch (error) { - this.handleError(error, res, 'IndexerController.buildIndex'); - } - }; - - /** - * Get index status - * GET /api/index/status - */ - getIndexStatus = async (req: Request, res: Response) => { - try { - const index = await this.indexer.getIndex(); - if (!index) { - return this.error(res, 'No index found', 404); - } - - this.success(res, { - exists: true, - timestamp: index.timestamp, - statistics: index.statistics - }); - } catch (error) { - this.handleError(error, res, 'IndexerController.getIndexStatus'); - } - }; - - /** - * Get current index - * GET /api/index - */ - getIndex = async (req: Request, res: Response) => { - try { - const index = await this.indexer.getIndex(); - if (!index) { - return this.error(res, 'No index found', 404); - } - - this.success(res, index); - } catch (error) { - this.handleError(error, res, 'IndexerController.getIndex'); - } - }; - - /** - * Update index incrementally - * PATCH /api/index - */ - updateIndex = async (req: Request, res: Response) => { - try { - const filePath = req.body.filePath; - if (!filePath) { - return this.error(res, 'File path required', 400); - } - - const index = await this.indexer.updateFile(filePath); - this.success(res, { - statistics: index.statistics, - timestamp: index.timestamp - }, 'Index updated successfully'); - } catch (error) { - this.handleError(error, res, 'IndexerController.updateIndex'); - } - }; +/** + * Indexer Controller + * Handles index building and management endpoints + */ + +import { Request, Response } from 'express'; +import { BaseController } from './base.controller'; +import { Indexer } from '../../core/indexer'; +import { IndexerOptions } from '../../types'; +import * as fs from 'fs/promises'; + +export class IndexerController extends BaseController { + constructor(indexer: Indexer) { + super(indexer); + } + + /** + * Build a new index + * POST /api/index + */ + buildIndex = async (req: Request, res: Response) => { + try { + const options: IndexerOptions = { + incremental: req.body.incremental, + parallel: typeof req.body.parallel === 'number' ? req.body.parallel : (req.body.parallel !== false ? 4 : 1), + force: req.body.force + }; + + const directory = req.body.directory || process.cwd(); + const index = await this.indexer.buildIndex(directory, options); + + this.success(res, { + statistics: index.statistics, + timestamp: index.timestamp + }, 'Index built successfully'); + } catch (error) { + this.handleError(error, res, 'IndexerController.buildIndex'); + } + }; + + /** + * Get index status + * GET /api/index/status + */ + getIndexStatus = async (req: Request, res: Response) => { + try { + const index = await this.indexer.getIndex(); + if (!index) { + return this.error(res, 'No index found', 404); + } + + this.success(res, { + exists: true, + timestamp: index.timestamp, + statistics: index.statistics + }); + } catch (error) { + this.handleError(error, res, 'IndexerController.getIndexStatus'); + } + }; + + /** + * Get current index + * GET /api/index + */ + getIndex = async (req: Request, res: Response) => { + try { + const index = await this.indexer.getIndex(); + if (!index) { + return this.error(res, 'No index found', 404); + } + + this.success(res, index); + } catch (error) { + this.handleError(error, res, 'IndexerController.getIndex'); + } + }; + + /** + * Update index incrementally + * PATCH /api/index + */ + updateIndex = async (req: Request, res: Response) => { + try { + const filePath = req.body.filePath; + if (!filePath) { + return this.error(res, 'File path required', 400); + } + + const projectRoot = req.body.projectRoot || process.cwd(); + const index = await this.indexer.updateFile(filePath, projectRoot); + if (!index) { + return this.error(res, 'Failed to update file index', 500); + } + + // Get current project index to return statistics + const projectIndex = await this.indexer.getIndex(); + this.success(res, { + fileIndex: index, + statistics: projectIndex?.statistics, + timestamp: new Date().toISOString() + }, 'Index updated successfully'); + } catch (error) { + this.handleError(error, res, 'IndexerController.updateIndex'); + } + }; } \ No newline at end of file diff --git a/src/api/controllers/query.controller.ts b/src/api/controllers/query.controller.ts index 1632dd0..72620ce 100644 --- a/src/api/controllers/query.controller.ts +++ b/src/api/controllers/query.controller.ts @@ -1,199 +1,220 @@ -/** - * Query Controller - * Handles search and query endpoints - */ - -import { Request, Response } from 'express'; -import { BaseController } from './base.controller'; -import { Indexer } from '../../core/indexer'; -import { QueryRequest, FileSearchResult } from '../types'; - -export class QueryController extends BaseController { - constructor(indexer: Indexer) { - super(indexer); - } - - /** - * Execute query on index - * POST /api/query - */ - query = async (req: Request, res: Response) => { - try { - const { query, options } = req.body as QueryRequest; - - if (!query) { - return this.error(res, 'Query is required', 400); - } - - const results = await this.indexer.query(query, options); - const searchResults: FileSearchResult[] = results.map(r => ({ - path: r.file, - language: r.language, - functions: r.functions || [], - classes: r.classes || [], - matches: r.matches || [], - score: r.score || 0 - })); - - this.success(res, searchResults); - } catch (error) { - this.handleError(error, res, 'QueryController.query'); - } - }; - - /** - * List all functions - * GET /api/functions - */ - getFunctions = async (req: Request, res: Response) => { - try { - const { file, name, limit = 100, offset = 0 } = req.query; - const index = await this.indexer.getIndex(); - - const functions: any[] = []; - - for (const [filePath, fileData] of Object.entries(index.files)) { - // Filter by file if specified - if (file && !filePath.includes(String(file))) continue; - - for (const func of fileData.functions) { - // Filter by name if specified - if (name && !func.name.includes(String(name))) continue; - - functions.push({ - file: filePath, - ...func - }); - } - } - - // Pagination - const paginatedFunctions = functions.slice( - Number(offset), - Number(offset) + Number(limit) - ); - - this.success(res, { - functions: paginatedFunctions, - total: functions.length, - limit: Number(limit), - offset: Number(offset) - }); - } catch (error) { - this.handleError(error, res, 'QueryController.getFunctions'); - } - }; - - /** - * List all classes - * GET /api/classes - */ - getClasses = async (req: Request, res: Response) => { - try { - const { file, name, limit = 100, offset = 0 } = req.query; - const index = await this.indexer.getIndex(); - - const classes: any[] = []; - - for (const [filePath, fileData] of Object.entries(index.files)) { - // Filter by file if specified - if (file && !filePath.includes(String(file))) continue; - - for (const cls of fileData.classes) { - // Filter by name if specified - if (name && !cls.name.includes(String(name))) continue; - - classes.push({ - file: filePath, - ...cls - }); - } - } - - // Pagination - const paginatedClasses = classes.slice( - Number(offset), - Number(offset) + Number(limit) - ); - - this.success(res, { - classes: paginatedClasses, - total: classes.length, - limit: Number(limit), - offset: Number(offset) - }); - } catch (error) { - this.handleError(error, res, 'QueryController.getClasses'); - } - }; - - /** - * Get dependency graph - * GET /api/dependencies - */ - getDependencies = async (req: Request, res: Response) => { - try { - const { file, depth = 1 } = req.query; - const index = await this.indexer.getIndex(); - - const graph = { - nodes: [], - edges: [], - stats: { - totalNodes: 0, - totalEdges: 0, - avgDegree: 0, - maxDegree: 0, - circularDependencies: 0 - } - }; - - // Build nodes - for (const [filePath, fileData] of Object.entries(index.files)) { - if (file && !filePath.includes(String(file))) continue; - - graph.nodes.push({ - id: filePath, - path: filePath, - type: 'file', - language: fileData.language, - size: fileData.size || 0, - complexity: fileData.complexity || 0 - }); - } - - // Build edges from dependency graph - if (index.dependencyGraph) { - for (const [from, targets] of Object.entries(index.dependencyGraph)) { - for (const to of targets) { - graph.edges.push({ - from, - to, - type: 'import', - weight: 1 - }); - } - } - } - - // Calculate stats - graph.stats.totalNodes = graph.nodes.length; - graph.stats.totalEdges = graph.edges.length; - - if (graph.nodes.length > 0) { - const degrees = new Map(); - for (const edge of graph.edges) { - degrees.set(edge.from, (degrees.get(edge.from) || 0) + 1); - } - - const degreeValues = Array.from(degrees.values()); - graph.stats.avgDegree = degreeValues.reduce((a, b) => a + b, 0) / degreeValues.length; - graph.stats.maxDegree = Math.max(...degreeValues); - } - - this.success(res, graph); - } catch (error) { - this.handleError(error, res, 'QueryController.getDependencies'); - } - }; +/** + * Query Controller + * Handles search and query endpoints + */ + +import { Request, Response } from 'express'; +import { BaseController } from './base.controller'; +import { Indexer } from '../../core/indexer'; +import { QueryRequest, FileSearchResult } from '../types'; + +export class QueryController extends BaseController { + constructor(indexer: Indexer) { + super(indexer); + } + + /** + * Execute query on index + * POST /api/query + */ + query = async (req: Request, res: Response) => { + try { + const { query, options } = req.body as QueryRequest; + + if (!query) { + return this.error(res, 'Query is required', 400); + } + + const results = await this.indexer.query(query, options); + const searchResults: FileSearchResult[] = results.map(r => ({ + path: r.file, + language: r.language, + functions: r.functions || [], + classes: r.classes || [], + matches: r.matches || [], + score: r.score || 0 + })); + + this.success(res, searchResults); + } catch (error) { + this.handleError(error, res, 'QueryController.query'); + } + }; + + /** + * List all functions + * GET /api/functions + */ + getFunctions = async (req: Request, res: Response) => { + try { + const { file, name, limit = 100, offset = 0 } = req.query; + const index = await this.indexer.getIndex(); + if (!index) { + return this.error(res, 'No index found', 404); + } + + const functions: any[] = []; + + for (const [filePath, fileData] of Object.entries(index.files)) { + // Filter by file if specified + if (file && !filePath.includes(String(file))) continue; + + for (const func of fileData.functions) { + // Filter by name if specified + if (name && !func.name.includes(String(name))) continue; + + functions.push({ + file: filePath, + ...func + }); + } + } + + // Pagination + const paginatedFunctions = functions.slice( + Number(offset), + Number(offset) + Number(limit) + ); + + this.success(res, { + functions: paginatedFunctions, + total: functions.length, + limit: Number(limit), + offset: Number(offset) + }); + } catch (error) { + this.handleError(error, res, 'QueryController.getFunctions'); + } + }; + + /** + * List all classes + * GET /api/classes + */ + getClasses = async (req: Request, res: Response) => { + try { + const { file, name, limit = 100, offset = 0 } = req.query; + const index = await this.indexer.getIndex(); + if (!index) { + return this.error(res, 'No index found', 404); + } + + const classes: any[] = []; + + for (const [filePath, fileData] of Object.entries(index.files)) { + // Filter by file if specified + if (file && !filePath.includes(String(file))) continue; + + for (const cls of fileData.classes) { + // Filter by name if specified + if (name && !cls.name.includes(String(name))) continue; + + classes.push({ + file: filePath, + ...cls + }); + } + } + + // Pagination + const paginatedClasses = classes.slice( + Number(offset), + Number(offset) + Number(limit) + ); + + this.success(res, { + classes: paginatedClasses, + total: classes.length, + limit: Number(limit), + offset: Number(offset) + }); + } catch (error) { + this.handleError(error, res, 'QueryController.getClasses'); + } + }; + + /** + * Get dependency graph + * GET /api/dependencies + */ + getDependencies = async (req: Request, res: Response) => { + try { + const { file, depth = 1 } = req.query; + const index = await this.indexer.getIndex(); + if (!index) { + return this.error(res, 'No index found', 404); + } + + const graph: { + nodes: any[]; + edges: any[]; + stats: { + totalNodes: number; + totalEdges: number; + avgDegree: number; + maxDegree: number; + circularDependencies: number; + }; + } = { + nodes: [], + edges: [], + stats: { + totalNodes: 0, + totalEdges: 0, + avgDegree: 0, + maxDegree: 0, + circularDependencies: 0 + } + }; + + // Build nodes + for (const [filePath, fileData] of Object.entries(index.files)) { + if (file && !filePath.includes(String(file))) continue; + + graph.nodes.push({ + id: filePath, + path: filePath, + type: 'file', + language: fileData.language, + size: fileData.size || 0, + complexity: fileData.complexity || 0 + }); + } + + // Build edges from dependency graph + if (index.dependencyGraph) { + for (const [from, targets] of Object.entries(index.dependencyGraph)) { + if (targets) { + for (const to of targets) { + graph.edges.push({ + from, + to, + type: 'import', + weight: 1 + }); + } + } + } + } + + // Calculate stats + graph.stats.totalNodes = graph.nodes.length; + graph.stats.totalEdges = graph.edges.length; + + if (graph.nodes.length > 0) { + const degrees = new Map(); + for (const edge of graph.edges) { + degrees.set(edge.from, (degrees.get(edge.from) || 0) + 1); + } + + const degreeValues = Array.from(degrees.values()); + graph.stats.avgDegree = degreeValues.reduce((a, b) => a + b, 0) / degreeValues.length; + graph.stats.maxDegree = Math.max(...degreeValues); + } + + this.success(res, graph); + } catch (error) { + this.handleError(error, res, 'QueryController.getDependencies'); + } + }; } \ No newline at end of file diff --git a/src/api/graphql/resolvers.ts b/src/api/graphql/resolvers.ts index a1475f3..2171488 100644 --- a/src/api/graphql/resolvers.ts +++ b/src/api/graphql/resolvers.ts @@ -1,496 +1,505 @@ -import { Indexer } from '../../core/indexer'; -import { PubSub } from 'graphql-subscriptions'; - -const pubsub = new PubSub(); - -export function resolvers(indexer: Indexer) { - return { - Query: { - // Index operations - getIndex: async () => { - const index = await indexer.getIndex(); - return { - ...index, - timestamp: new Date().toISOString(), - statistics: await calculateStatistics(index), - dependencyGraph: buildDependencyGraph(index) - }; - }, - - getFile: async (_: any, { path }: { path: string }) => { - const index = await indexer.getIndex(); - const fileData = index.files[path]; - if (!fileData) return null; - - return { - path, - ...fileData, - lastModified: new Date().toISOString() - }; - }, - - getFiles: async (_: any, { language, limit = 100, offset = 0 }: any) => { - const index = await indexer.getIndex(); - if (!index) return []; - - let files = Object.entries(index.files); - - if (language) { - files = files.filter(([_, data]: [string, any]) => data.language === language); - } - - return files - .slice(offset, offset + limit) - .map(([path, data]) => ({ - path, - ...data, - lastModified: new Date().toISOString() - })); - }, - - // Search operations - search: async (_: any, { query, options }: any) => { - const results = await indexer.query(query, options); - return results.map(r => ({ - path: r.file, - language: r.language, - functions: r.functions || [], - classes: r.classes || [], - matches: r.matches || [], - score: r.score || 0 - })); - }, - - searchFunctions: async (_: any, { name, file, limit = 100, offset = 0 }: any) => { - const index = await indexer.getIndex(); - const functions: any[] = []; - - for (const [filePath, fileData] of Object.entries(index.files)) { - if (file && !filePath.includes(file)) continue; - - for (const func of fileData.functions) { - if (name && !func.name.includes(name)) continue; - functions.push({ ...func, file: filePath }); - } - } - - return functions.slice(offset, offset + limit); - }, - - searchClasses: async (_: any, { name, file, limit = 100, offset = 0 }: any) => { - const index = await indexer.getIndex(); - const classes: any[] = []; - - for (const [filePath, fileData] of Object.entries(index.files)) { - if (file && !filePath.includes(file)) continue; - - for (const cls of fileData.classes) { - if (name && !cls.name.includes(name)) continue; - classes.push({ ...cls, file: filePath }); - } - } - - return classes.slice(offset, offset + limit); - }, - - // Analysis operations - getDependencies: async (_: any, { file, depth = 1 }: any) => { - const index = await indexer.getIndex(); - return buildDependencyGraph(index, file, depth); - }, - - getStatistics: async () => { - const index = await indexer.getIndex(); - return calculateStatistics(index); - }, - - getComplexity: async (_: any, { file }: any) => { - const index = await indexer.getIndex(); - const complexities: number[] = []; - - if (file) { - const fileData = index.files[file]; - if (fileData?.complexity) { - complexities.push(fileData.complexity); - } - } else { - for (const fileData of Object.values(index.files)) { - if (fileData.complexity) { - complexities.push(fileData.complexity); - } - } - } - - return calculateComplexityStats(complexities); - }, - - findCircularDependencies: async () => { - const index = await indexer.getIndex(); - return findCircularDeps(index.dependencyGraph || {}); - }, - - findUnusedExports: async () => { - const index = await indexer.getIndex(); - const usedImports = new Set(); - - // Collect all imported names - for (const fileData of Object.values(index.files)) { - for (const imp of fileData.imports) { - for (const spec of imp.specifiers) { - usedImports.add(spec.name); - } - } - } - - // Find unused exports - const unusedExports: any[] = []; - for (const [filePath, fileData] of Object.entries(index.files)) { - for (const exp of fileData.exports) { - if (!usedImports.has(exp.name)) { - unusedExports.push({ ...exp, file: filePath }); - } - } - } - - return unusedExports; - }, - - // System operations - getHealth: async () => ({ - status: 'healthy', - timestamp: new Date().toISOString(), - uptime: process.uptime(), - memory: process.memoryUsage(), - components: { - indexer: 'healthy', - cache: 'healthy', - watcher: indexer.isWatching() ? 'active' : 'inactive' - }, - version: require('../../../package.json').version - }), - - getWatchStatus: async () => ({ - active: indexer.isWatching(), - path: indexer.getWatchPath(), - filesWatched: 0, // TODO: Implement file count - lastUpdate: new Date().toISOString() - }), - - getSupportedLanguages: async () => { - return indexer.getSupportedLanguages(); - }, - - getSupportedExtensions: async () => { - return indexer.getSupportedExtensions(); - } - }, - - Mutation: { - // Index operations - index: async (_: any, { path, options }: any) => { - const startTime = Date.now(); - try { - const result = await indexer.buildIndex(path || process.cwd(), options); - const duration = Date.now() - startTime; - - // Publish update - pubsub.publish('INDEX_UPDATED', { - indexUpdated: { - filesAdded: Object.keys(result.files).length, - filesModified: 0, - filesRemoved: 0, - timestamp: new Date().toISOString() - } - }); - - return { - success: true, - duration: duration / 1000, - filesIndexed: Object.keys(result.files).length, - errors: [] - }; - } catch (error: any) { - return { - success: false, - duration: (Date.now() - startTime) / 1000, - filesIndexed: 0, - errors: [error.message] - }; - } - }, - - updateFile: async (_: any, { path }: { path: string }) => { - await indexer.updateFile(path); - const index = await indexer.getIndex(); - const fileData = index.files[path]; - - // Publish update - pubsub.publish('FILE_UPDATED', { - fileUpdated: { - type: 'modified', - path, - timestamp: new Date().toISOString(), - changes: fileData - } - }); - - return { - path, - ...fileData, - lastModified: new Date().toISOString() - }; - }, - - clearCache: async () => { - indexer.clearCache(); - return true; - }, - - // Export operations - export: async (_: any, { format, options }: any) => { - try { - const result = await indexer.export(format.toLowerCase(), options); - return { - success: true, - format, - content: typeof result === 'string' ? result : JSON.stringify(result), - path: null, - size: 0 - }; - } catch (error: any) { - return { - success: false, - format, - content: null, - path: null, - size: 0 - }; - } - }, - - // Watch operations - startWatching: async (_: any, { path }: { path: string }) => { - await indexer.startWatching(path); - return { - active: true, - path, - filesWatched: 0, - lastUpdate: new Date().toISOString() - }; - }, - - stopWatching: async () => { - await indexer.stopWatching(); - return { - active: false, - path: null, - filesWatched: 0, - lastUpdate: new Date().toISOString() - }; - }, - - // Maintenance operations - optimize: async () => { - // TODO: Implement optimization - return true; - }, - - validate: async () => { - // TODO: Implement validation - return true; - }, - - migrate: async (_: any, { version }: { version: string }) => { - // TODO: Implement migration - return true; - } - }, - - Subscription: { - fileUpdated: { - subscribe: (_: any, { path }: any) => { - if (path) { - return pubsub.asyncIterator([`FILE_UPDATED_${path}`]); - } - return pubsub.asyncIterator(['FILE_UPDATED']); - } - }, - - indexUpdated: { - subscribe: () => pubsub.asyncIterator(['INDEX_UPDATED']) - }, - - indexProgress: { - subscribe: () => pubsub.asyncIterator(['INDEX_PROGRESS']) - }, - - errorOccurred: { - subscribe: () => pubsub.asyncIterator(['ERROR_OCCURRED']) - } - } - }; -} - -// Helper functions -async function calculateStatistics(index: any) { - const stats = { - totalFiles: Object.keys(index.files).length, - totalFunctions: 0, - totalClasses: 0, - totalImports: 0, - totalExports: 0, - languages: {} as Record, - complexity: { - average: 0, - max: 0, - distribution: {} as Record - }, - indexingTime: 0, - memoryUsage: process.memoryUsage().heapUsed / 1024 / 1024 // MB - }; - - const complexities: number[] = []; - - for (const fileData of Object.values(index.files) as any[]) { - stats.totalFunctions += fileData.functions.length; - stats.totalClasses += fileData.classes.length; - stats.totalImports += fileData.imports.length; - stats.totalExports += fileData.exports.length; - - stats.languages[fileData.language] = (stats.languages[fileData.language] || 0) + 1; - - if (fileData.complexity) { - complexities.push(fileData.complexity); - } - } - - stats.complexity = calculateComplexityStats(complexities); - - return stats; -} - -function calculateComplexityStats(complexities: number[]) { - const stats = { - average: 0, - max: 0, - distribution: {} as Record - }; - - if (complexities.length > 0) { - stats.average = complexities.reduce((a, b) => a + b, 0) / complexities.length; - stats.max = Math.max(...complexities); - - for (const c of complexities) { - const bucket = Math.floor(c / 10) * 10; - const key = `${bucket}-${bucket + 9}`; - stats.distribution[key] = (stats.distribution[key] || 0) + 1; - } - } - - return stats; -} - -function buildDependencyGraph(index: any, filterFile?: string, depth: number = 1) { - const graph = { - nodes: [] as any[], - edges: [] as any[], - clusters: {}, - stats: { - totalNodes: 0, - totalEdges: 0, - avgDegree: 0, - maxDegree: 0, - circularDependencies: 0 - } - }; - - // Build nodes - for (const [filePath, fileData] of Object.entries(index.files) as any[]) { - if (filterFile && !filePath.includes(filterFile)) continue; - - graph.nodes.push({ - id: filePath, - path: filePath, - type: 'FILE', - language: fileData.language, - size: fileData.size || 0, - complexity: fileData.complexity || 0 - }); - } - - // Build edges - if (index.dependencyGraph) { - for (const [from, targets] of Object.entries(index.dependencyGraph) as any[]) { - if (filterFile && !from.includes(filterFile)) continue; - - for (const to of targets) { - graph.edges.push({ - from, - to, - type: 'import', - weight: 1 - }); - } - } - } - - // Calculate stats - graph.stats.totalNodes = graph.nodes.length; - graph.stats.totalEdges = graph.edges.length; - - if (graph.nodes.length > 0) { - const degrees = new Map(); - for (const edge of graph.edges) { - degrees.set(edge.from, (degrees.get(edge.from) || 0) + 1); - } - - const degreeValues = Array.from(degrees.values()); - if (degreeValues.length > 0) { - graph.stats.avgDegree = degreeValues.reduce((a, b) => a + b, 0) / degreeValues.length; - graph.stats.maxDegree = Math.max(...degreeValues); - } - - // Count circular dependencies - const circles = findCircularDeps(index.dependencyGraph || {}); - graph.stats.circularDependencies = circles.length; - } - - return graph; -} - -function findCircularDeps(depGraph: Record): string[][] { - const circles: string[][] = []; - const visited = new Set(); - const stack = new Set(); - - function dfs(node: string, path: string[]): void { - if (stack.has(node)) { - const cycleStart = path.indexOf(node); - if (cycleStart !== -1) { - circles.push(path.slice(cycleStart)); - } - return; - } - - if (visited.has(node)) return; - - visited.add(node); - stack.add(node); - path.push(node); - - const deps = depGraph[node] || []; - for (const dep of deps) { - dfs(dep, [...path]); - } - - stack.delete(node); - } - - for (const node of Object.keys(depGraph)) { - if (!visited.has(node)) { - dfs(node, []); - } - } - - return circles; +import { Indexer } from '../../core/indexer'; +import { PubSub } from 'graphql-subscriptions'; + +const pubsub = new PubSub(); + +export function resolvers(indexer: Indexer) { + return { + Query: { + // Index operations + getIndex: async () => { + const index = await indexer.getIndex(); + return { + ...index, + timestamp: new Date().toISOString(), + statistics: await calculateStatistics(index), + dependencyGraph: buildDependencyGraph(index) + }; + }, + + getFile: async (_: any, { path }: { path: string }) => { + const index = await indexer.getIndex(); + if (!index) return null; + const fileData = index.files[path]; + if (!fileData) return null; + + return { + path, + ...fileData, + lastModified: new Date().toISOString() + }; + }, + + getFiles: async (_: any, { language, limit = 100, offset = 0 }: any) => { + const index = await indexer.getIndex(); + if (!index) return []; + + let files = Object.entries(index.files); + + if (language) { + files = files.filter(([_, data]: [string, any]) => data.language === language); + } + + return files + .slice(offset, offset + limit) + .map(([path, data]) => ({ + path, + ...data, + lastModified: new Date().toISOString() + })); + }, + + // Search operations + search: async (_: any, { query, options }: any) => { + const results = await indexer.query(query, options); + return results.map(r => ({ + path: r.file, + language: r.language, + functions: r.functions || [], + classes: r.classes || [], + matches: r.matches || [], + score: r.score || 0 + })); + }, + + searchFunctions: async (_: any, { name, file, limit = 100, offset = 0 }: any) => { + const index = await indexer.getIndex(); + if (!index) return []; + const functions: any[] = []; + + for (const [filePath, fileData] of Object.entries(index.files)) { + if (file && !filePath.includes(file)) continue; + + for (const func of fileData.functions) { + if (name && !func.name.includes(name)) continue; + functions.push({ ...func, file: filePath }); + } + } + + return functions.slice(offset, offset + limit); + }, + + searchClasses: async (_: any, { name, file, limit = 100, offset = 0 }: any) => { + const index = await indexer.getIndex(); + if (!index) return []; + const classes: any[] = []; + + for (const [filePath, fileData] of Object.entries(index.files)) { + if (file && !filePath.includes(file)) continue; + + for (const cls of fileData.classes) { + if (name && !cls.name.includes(name)) continue; + classes.push({ ...cls, file: filePath }); + } + } + + return classes.slice(offset, offset + limit); + }, + + // Analysis operations + getDependencies: async (_: any, { file, depth = 1 }: any) => { + const index = await indexer.getIndex(); + if (!index) return { nodes: [], edges: [], clusters: {}, stats: { totalNodes: 0, totalEdges: 0, avgDegree: 0, maxDegree: 0, circularDependencies: 0 } }; + return buildDependencyGraph(index, file, depth); + }, + + getStatistics: async () => { + const index = await indexer.getIndex(); + if (!index) return { totalFiles: 0, totalFunctions: 0, totalClasses: 0, totalImports: 0, totalExports: 0, languages: {}, complexity: { average: 0, max: 0, distribution: {} }, indexingTime: 0, memoryUsage: 0 }; + return calculateStatistics(index); + }, + + getComplexity: async (_: any, { file }: any) => { + const index = await indexer.getIndex(); + if (!index) return calculateComplexityStats([]); + const complexities: number[] = []; + + if (file) { + const fileData = index.files[file]; + if (fileData?.complexity) { + complexities.push(fileData.complexity); + } + } else { + for (const fileData of Object.values(index.files)) { + if (fileData.complexity) { + complexities.push(fileData.complexity); + } + } + } + + return calculateComplexityStats(complexities); + }, + + findCircularDependencies: async () => { + const index = await indexer.getIndex(); + if (!index) return []; + return findCircularDeps(index.dependencyGraph || {}); + }, + + findUnusedExports: async () => { + const index = await indexer.getIndex(); + if (!index) return []; + const usedImports = new Set(); + + // Collect all imported names + for (const fileData of Object.values(index.files)) { + for (const imp of fileData.imports) { + for (const spec of imp.specifiers) { + usedImports.add(spec.name); + } + } + } + + // Find unused exports + const unusedExports: any[] = []; + for (const [filePath, fileData] of Object.entries(index.files)) { + for (const exp of fileData.exports) { + if (!usedImports.has(exp.name)) { + unusedExports.push({ ...exp, file: filePath }); + } + } + } + + return unusedExports; + }, + + // System operations + getHealth: async () => ({ + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + memory: process.memoryUsage(), + components: { + indexer: 'healthy', + cache: 'healthy', + watcher: indexer.isWatching() ? 'active' : 'inactive' + }, + version: require('../../../package.json').version + }), + + getWatchStatus: async () => ({ + active: indexer.isWatching(), + path: indexer.getWatchPath(), + filesWatched: 0, // TODO: Implement file count + lastUpdate: new Date().toISOString() + }), + + getSupportedLanguages: async () => { + return indexer.getSupportedLanguages(); + }, + + getSupportedExtensions: async () => { + return indexer.getSupportedExtensions(); + } + }, + + Mutation: { + // Index operations + index: async (_: any, { path, options }: any) => { + const startTime = Date.now(); + try { + const result = await indexer.buildIndex(path || process.cwd(), options); + const duration = Date.now() - startTime; + + // Publish update + pubsub.publish('INDEX_UPDATED', { + indexUpdated: { + filesAdded: Object.keys(result.files).length, + filesModified: 0, + filesRemoved: 0, + timestamp: new Date().toISOString() + } + }); + + return { + success: true, + duration: duration / 1000, + filesIndexed: Object.keys(result.files).length, + errors: [] + }; + } catch (error: any) { + return { + success: false, + duration: (Date.now() - startTime) / 1000, + filesIndexed: 0, + errors: [error.message] + }; + } + }, + + updateFile: async (_: any, { path }: { path: string }) => { + await indexer.updateFile(path, process.cwd()); + const index = await indexer.getIndex(); + if (!index) return null; + const fileData = index.files[path]; + + // Publish update + pubsub.publish('FILE_UPDATED', { + fileUpdated: { + type: 'modified', + path, + timestamp: new Date().toISOString(), + changes: fileData + } + }); + + return { + path, + ...fileData, + lastModified: new Date().toISOString() + }; + }, + + clearCache: async () => { + indexer.clearCache(); + return true; + }, + + // Export operations + export: async (_: any, { format, options }: any) => { + try { + const result = await indexer.export(format.toLowerCase(), options); + return { + success: true, + format, + content: typeof result === 'string' ? result : JSON.stringify(result), + path: null, + size: 0 + }; + } catch (error: any) { + return { + success: false, + format, + content: null, + path: null, + size: 0 + }; + } + }, + + // Watch operations + startWatching: async (_: any, { path }: { path: string }) => { + await indexer.startWatching(path); + return { + active: true, + path, + filesWatched: 0, + lastUpdate: new Date().toISOString() + }; + }, + + stopWatching: async () => { + await indexer.stopWatching(); + return { + active: false, + path: null, + filesWatched: 0, + lastUpdate: new Date().toISOString() + }; + }, + + // Maintenance operations + optimize: async () => { + // TODO: Implement optimization + return true; + }, + + validate: async () => { + // TODO: Implement validation + return true; + }, + + migrate: async (_: any, { version }: { version: string }) => { + // TODO: Implement migration + return true; + } + }, + + // Subscription: { + // fileUpdated: { + // subscribe: (_: any, { path }: any) => { + // if (path) { + // return pubsub.asyncIterator([`FILE_UPDATED_${path}`]); + // } + // return pubsub.asyncIterator(['FILE_UPDATED']); + // } + // }, + + // indexUpdated: { + // subscribe: () => pubsub.asyncIterator(['INDEX_UPDATED']) + // }, + + // indexProgress: { + // subscribe: () => pubsub.asyncIterator(['INDEX_PROGRESS']) + // }, + + // errorOccurred: { + // subscribe: () => pubsub.asyncIterator(['ERROR_OCCURRED']) + // } + // } + }; +} + +// Helper functions +async function calculateStatistics(index: any) { + const stats = { + totalFiles: Object.keys(index.files).length, + totalFunctions: 0, + totalClasses: 0, + totalImports: 0, + totalExports: 0, + languages: {} as Record, + complexity: { + average: 0, + max: 0, + distribution: {} as Record + }, + indexingTime: 0, + memoryUsage: process.memoryUsage().heapUsed / 1024 / 1024 // MB + }; + + const complexities: number[] = []; + + for (const fileData of Object.values(index.files) as any[]) { + stats.totalFunctions += fileData.functions.length; + stats.totalClasses += fileData.classes.length; + stats.totalImports += fileData.imports.length; + stats.totalExports += fileData.exports.length; + + stats.languages[fileData.language] = (stats.languages[fileData.language] || 0) + 1; + + if (fileData.complexity) { + complexities.push(fileData.complexity); + } + } + + stats.complexity = calculateComplexityStats(complexities); + + return stats; +} + +function calculateComplexityStats(complexities: number[]) { + const stats = { + average: 0, + max: 0, + distribution: {} as Record + }; + + if (complexities.length > 0) { + stats.average = complexities.reduce((a, b) => a + b, 0) / complexities.length; + stats.max = Math.max(...complexities); + + for (const c of complexities) { + const bucket = Math.floor(c / 10) * 10; + const key = `${bucket}-${bucket + 9}`; + stats.distribution[key] = (stats.distribution[key] || 0) + 1; + } + } + + return stats; +} + +function buildDependencyGraph(index: any, filterFile?: string, depth: number = 1) { + const graph = { + nodes: [] as any[], + edges: [] as any[], + clusters: {}, + stats: { + totalNodes: 0, + totalEdges: 0, + avgDegree: 0, + maxDegree: 0, + circularDependencies: 0 + } + }; + + // Build nodes + for (const [filePath, fileData] of Object.entries(index.files) as any[]) { + if (filterFile && !filePath.includes(filterFile)) continue; + + graph.nodes.push({ + id: filePath, + path: filePath, + type: 'FILE', + language: fileData.language, + size: fileData.size || 0, + complexity: fileData.complexity || 0 + }); + } + + // Build edges + if (index.dependencyGraph) { + for (const [from, targets] of Object.entries(index.dependencyGraph) as any[]) { + if (filterFile && !from.includes(filterFile)) continue; + + for (const to of targets) { + graph.edges.push({ + from, + to, + type: 'import', + weight: 1 + }); + } + } + } + + // Calculate stats + graph.stats.totalNodes = graph.nodes.length; + graph.stats.totalEdges = graph.edges.length; + + if (graph.nodes.length > 0) { + const degrees = new Map(); + for (const edge of graph.edges) { + degrees.set(edge.from, (degrees.get(edge.from) || 0) + 1); + } + + const degreeValues = Array.from(degrees.values()); + if (degreeValues.length > 0) { + graph.stats.avgDegree = degreeValues.reduce((a, b) => a + b, 0) / degreeValues.length; + graph.stats.maxDegree = Math.max(...degreeValues); + } + + // Count circular dependencies + const circles = findCircularDeps(index.dependencyGraph || {}); + graph.stats.circularDependencies = circles.length; + } + + return graph; +} + +function findCircularDeps(depGraph: Record): string[][] { + const circles: string[][] = []; + const visited = new Set(); + const stack = new Set(); + + function dfs(node: string, path: string[]): void { + if (stack.has(node)) { + const cycleStart = path.indexOf(node); + if (cycleStart !== -1) { + circles.push(path.slice(cycleStart)); + } + return; + } + + if (visited.has(node)) return; + + visited.add(node); + stack.add(node); + path.push(node); + + const deps = depGraph[node] || []; + for (const dep of deps) { + dfs(dep, [...path]); + } + + stack.delete(node); + } + + for (const node of Object.keys(depGraph)) { + if (!visited.has(node)) { + dfs(node, []); + } + } + + return circles; } \ No newline at end of file diff --git a/src/api/graphql/schema.ts b/src/api/graphql/schema.ts index fc4d82b..3a356fa 100644 --- a/src/api/graphql/schema.ts +++ b/src/api/graphql/schema.ts @@ -1,326 +1,326 @@ -export const typeDefs = `#graphql - # Scalar types - scalar JSON - scalar DateTime - - # Input types - input IndexOptions { - incremental: Boolean - parallel: Boolean - workers: Int - force: Boolean - } - - input QueryOptions { - type: QueryType - fuzzy: Boolean - limit: Int - offset: Int - } - - input ExportOptions { - pretty: Boolean - includeStatistics: Boolean - sortBy: SortBy - groupByDirectory: Boolean - } - - # Enum types - enum QueryType { - ALL - FUNCTION - CLASS - IMPORT - EXPORT - } - - enum ExportFormat { - JSON - MARKDOWN - GRAPHVIZ - MERMAID - ASCII - } - - enum SortBy { - NAME - COMPLEXITY - SIZE - } - - enum FileType { - FILE - MODULE - PACKAGE - } - - # Object types - type FunctionInfo { - name: String! - parameters: [Parameter!]! - returnType: String - async: Boolean! - generator: Boolean! - startLine: Int! - endLine: Int! - decorators: [String!] - complexity: Int - file: String - } - - type Parameter { - name: String! - type: String! - default: String - } - - type ClassInfo { - name: String! - methods: [Method!]! - properties: [Property!]! - extends: String - implements: [String!]! - startLine: Int! - endLine: Int! - decorators: [String!] - metaclass: String - file: String - } - - type Method { - name: String! - visibility: String! - static: Boolean! - classmethod: Boolean - property: Boolean - async: Boolean - returnType: String - decorators: [String!] - } - - type Property { - name: String! - type: String! - visibility: String! - static: Boolean! - readonly: Boolean - value: String - } - - type ImportInfo { - source: String! - specifiers: [ImportSpecifier!]! - type: String! - } - - type ImportSpecifier { - name: String! - alias: String! - } - - type ExportInfo { - name: String! - type: String! - line: Int - } - - type FileIndex { - path: String! - imports: [ImportInfo!]! - exports: [ExportInfo!]! - functions: [FunctionInfo!]! - classes: [ClassInfo!]! - constants: [Constant!]! - dependencies: [String!]! - lastModified: DateTime! - language: String! - size: Int - complexity: Int - } - - type Constant { - name: String! - type: String! - value: String - line: Int - } - - type ProjectIndex { - version: String! - timestamp: DateTime! - projectRoot: String! - statistics: Statistics! - files: [FileIndex!]! - dependencyGraph: DependencyGraph! - } - - type Statistics { - totalFiles: Int! - totalFunctions: Int! - totalClasses: Int! - totalImports: Int! - totalExports: Int! - languages: JSON! - complexity: ComplexityStats! - indexingTime: Float! - memoryUsage: Float! - } - - type ComplexityStats { - average: Float! - max: Float! - distribution: JSON! - } - - type DependencyGraph { - nodes: [DependencyNode!]! - edges: [DependencyEdge!]! - clusters: JSON - stats: GraphStats! - } - - type DependencyNode { - id: String! - path: String! - type: FileType! - language: String! - size: Int! - complexity: Int! - } - - type DependencyEdge { - from: String! - to: String! - type: String! - weight: Float! - } - - type GraphStats { - totalNodes: Int! - totalEdges: Int! - avgDegree: Float! - maxDegree: Int! - circularDependencies: Int! - } - - type SearchResult { - path: String! - language: String! - functions: [String!]! - classes: [String!]! - matches: [Match!]! - score: Float! - } - - type Match { - line: Int! - column: Int! - text: String! - type: String! - } - - type IndexResult { - success: Boolean! - duration: Float! - filesIndexed: Int! - errors: [String!] - } - - type ExportResult { - success: Boolean! - format: ExportFormat! - content: String - path: String - size: Int - } - - type WatchStatus { - active: Boolean! - path: String - filesWatched: Int - lastUpdate: DateTime - } - - type HealthStatus { - status: String! - timestamp: DateTime! - uptime: Float! - memory: JSON! - components: JSON! - version: String! - } - - # Subscription types - type FileUpdate { - type: String! - path: String! - timestamp: DateTime! - changes: JSON - } - - type IndexUpdate { - filesAdded: Int! - filesModified: Int! - filesRemoved: Int! - timestamp: DateTime! - } - - # Query definitions - type Query { - # Index operations - getIndex: ProjectIndex! - getFile(path: String!): FileIndex - getFiles(language: String, limit: Int, offset: Int): [FileIndex!]! - - # Search operations - search(query: String!, options: QueryOptions): [SearchResult!]! - searchFunctions(name: String, file: String, limit: Int, offset: Int): [FunctionInfo!]! - searchClasses(name: String, file: String, limit: Int, offset: Int): [ClassInfo!]! - - # Analysis operations - getDependencies(file: String, depth: Int): DependencyGraph! - getStatistics: Statistics! - getComplexity(file: String): ComplexityStats! - findCircularDependencies: [[String!]!]! - findUnusedExports: [ExportInfo!]! - - # System operations - getHealth: HealthStatus! - getWatchStatus: WatchStatus! - getSupportedLanguages: [String!]! - getSupportedExtensions: [String!]! - } - - # Mutation definitions - type Mutation { - # Index operations - index(path: String, options: IndexOptions): IndexResult! - updateFile(path: String!): FileIndex! - clearCache: Boolean! - - # Export operations - export(format: ExportFormat!, options: ExportOptions): ExportResult! - - # Watch operations - startWatching(path: String!): WatchStatus! - stopWatching: WatchStatus! - - # Maintenance operations - optimize: Boolean! - validate: Boolean! - migrate(version: String!): Boolean! - } - - # Subscription definitions - type Subscription { - # Real-time updates - fileUpdated(path: String): FileUpdate! - indexUpdated: IndexUpdate! - - # Progress tracking - indexProgress: JSON! - - # Error notifications - errorOccurred: JSON! - } +export const typeDefs = `#graphql + # Scalar types + scalar JSON + scalar DateTime + + # Input types + input IndexOptions { + incremental: Boolean + parallel: Boolean + workers: Int + force: Boolean + } + + input QueryOptions { + type: QueryType + fuzzy: Boolean + limit: Int + offset: Int + } + + input ExportOptions { + pretty: Boolean + includeStatistics: Boolean + sortBy: SortBy + groupByDirectory: Boolean + } + + # Enum types + enum QueryType { + ALL + FUNCTION + CLASS + IMPORT + EXPORT + } + + enum ExportFormat { + JSON + MARKDOWN + GRAPHVIZ + MERMAID + ASCII + } + + enum SortBy { + NAME + COMPLEXITY + SIZE + } + + enum FileType { + FILE + MODULE + PACKAGE + } + + # Object types + type FunctionInfo { + name: String! + parameters: [Parameter!]! + returnType: String + async: Boolean! + generator: Boolean! + startLine: Int! + endLine: Int! + decorators: [String!] + complexity: Int + file: String + } + + type Parameter { + name: String! + type: String! + default: String + } + + type ClassInfo { + name: String! + methods: [Method!]! + properties: [Property!]! + extends: String + implements: [String!]! + startLine: Int! + endLine: Int! + decorators: [String!] + metaclass: String + file: String + } + + type Method { + name: String! + visibility: String! + static: Boolean! + classmethod: Boolean + property: Boolean + async: Boolean + returnType: String + decorators: [String!] + } + + type Property { + name: String! + type: String! + visibility: String! + static: Boolean! + readonly: Boolean + value: String + } + + type ImportInfo { + source: String! + specifiers: [ImportSpecifier!]! + type: String! + } + + type ImportSpecifier { + name: String! + alias: String! + } + + type ExportInfo { + name: String! + type: String! + line: Int + } + + type FileIndex { + path: String! + imports: [ImportInfo!]! + exports: [ExportInfo!]! + functions: [FunctionInfo!]! + classes: [ClassInfo!]! + constants: [Constant!]! + dependencies: [String!]! + lastModified: DateTime! + language: String! + size: Int + complexity: Int + } + + type Constant { + name: String! + type: String! + value: String + line: Int + } + + type ProjectIndex { + version: String! + timestamp: DateTime! + projectRoot: String! + statistics: Statistics! + files: [FileIndex!]! + dependencyGraph: DependencyGraph! + } + + type Statistics { + totalFiles: Int! + totalFunctions: Int! + totalClasses: Int! + totalImports: Int! + totalExports: Int! + languages: JSON! + complexity: ComplexityStats! + indexingTime: Float! + memoryUsage: Float! + } + + type ComplexityStats { + average: Float! + max: Float! + distribution: JSON! + } + + type DependencyGraph { + nodes: [DependencyNode!]! + edges: [DependencyEdge!]! + clusters: JSON + stats: GraphStats! + } + + type DependencyNode { + id: String! + path: String! + type: FileType! + language: String! + size: Int! + complexity: Int! + } + + type DependencyEdge { + from: String! + to: String! + type: String! + weight: Float! + } + + type GraphStats { + totalNodes: Int! + totalEdges: Int! + avgDegree: Float! + maxDegree: Int! + circularDependencies: Int! + } + + type SearchResult { + path: String! + language: String! + functions: [String!]! + classes: [String!]! + matches: [Match!]! + score: Float! + } + + type Match { + line: Int! + column: Int! + text: String! + type: String! + } + + type IndexResult { + success: Boolean! + duration: Float! + filesIndexed: Int! + errors: [String!] + } + + type ExportResult { + success: Boolean! + format: ExportFormat! + content: String + path: String + size: Int + } + + type WatchStatus { + active: Boolean! + path: String + filesWatched: Int + lastUpdate: DateTime + } + + type HealthStatus { + status: String! + timestamp: DateTime! + uptime: Float! + memory: JSON! + components: JSON! + version: String! + } + + # Subscription types + type FileUpdate { + type: String! + path: String! + timestamp: DateTime! + changes: JSON + } + + type IndexUpdate { + filesAdded: Int! + filesModified: Int! + filesRemoved: Int! + timestamp: DateTime! + } + + # Query definitions + type Query { + # Index operations + getIndex: ProjectIndex! + getFile(path: String!): FileIndex + getFiles(language: String, limit: Int, offset: Int): [FileIndex!]! + + # Search operations + search(query: String!, options: QueryOptions): [SearchResult!]! + searchFunctions(name: String, file: String, limit: Int, offset: Int): [FunctionInfo!]! + searchClasses(name: String, file: String, limit: Int, offset: Int): [ClassInfo!]! + + # Analysis operations + getDependencies(file: String, depth: Int): DependencyGraph! + getStatistics: Statistics! + getComplexity(file: String): ComplexityStats! + findCircularDependencies: [[String!]!]! + findUnusedExports: [ExportInfo!]! + + # System operations + getHealth: HealthStatus! + getWatchStatus: WatchStatus! + getSupportedLanguages: [String!]! + getSupportedExtensions: [String!]! + } + + # Mutation definitions + type Mutation { + # Index operations + index(path: String, options: IndexOptions): IndexResult! + updateFile(path: String!): FileIndex! + clearCache: Boolean! + + # Export operations + export(format: ExportFormat!, options: ExportOptions): ExportResult! + + # Watch operations + startWatching(path: String!): WatchStatus! + stopWatching: WatchStatus! + + # Maintenance operations + optimize: Boolean! + validate: Boolean! + migrate(version: String!): Boolean! + } + + # Subscription definitions + type Subscription { + # Real-time updates + fileUpdated(path: String): FileUpdate! + indexUpdated: IndexUpdate! + + # Progress tracking + indexProgress: JSON! + + # Error notifications + errorOccurred: JSON! + } `; \ No newline at end of file diff --git a/src/api/middleware/auth.ts b/src/api/middleware/auth.ts index c080916..1b6c210 100644 --- a/src/api/middleware/auth.ts +++ b/src/api/middleware/auth.ts @@ -1,260 +1,263 @@ -import { Request, Response, NextFunction } from 'express'; -import jwt from 'jsonwebtoken'; -import bcrypt from 'bcrypt'; -import { ApiUser } from '../types'; - -// Default secret (should be overridden in production) -const JWT_SECRET = process.env.JWT_SECRET || 'indexer-secret-key-change-in-production'; -const API_KEYS = process.env.API_KEYS ? process.env.API_KEYS.split(',') : ['demo-api-key']; - -// In-memory user store (replace with database in production) -const users: Map = new Map([ - ['admin', { - password: '$2b$10$xQX2Y6Y9JZpH3X9Y3pDRg.vPGvH2mJYWxr7ckL9lKVGN3LpC9Y7Ym', // 'admin123' - role: 'admin' - }], - ['demo', { - password: '$2b$10$9IQqO2RgqFqFxQb5Y5NqOejzB1wK6zY8JQFqO2RgqFqFxQb5Y5NqO', // 'demo123' - role: 'readonly', - apiKey: 'demo-api-key' - }] -]); - -export interface AuthRequest extends Request { - user?: ApiUser; -} - -/** - * Authentication middleware - * Supports JWT tokens and API keys - */ -export function authenticate(req: AuthRequest, res: Response, next: NextFunction) { - // Check for API key first - const apiKey = req.headers['x-api-key'] as string; - if (apiKey && API_KEYS.includes(apiKey)) { - req.user = getUserFromApiKey(apiKey); - return next(); - } - - // Check for JWT token - const authHeader = req.headers.authorization; - if (!authHeader) { - return res.status(401).json({ error: 'No authorization header' }); - } - - const token = authHeader.startsWith('Bearer ') - ? authHeader.slice(7) - : authHeader; - - try { - const decoded = jwt.verify(token, JWT_SECRET) as ApiUser; - req.user = decoded; - next(); - } catch (error) { - return res.status(401).json({ error: 'Invalid token' }); - } -} - -/** - * Optional authentication middleware - * Allows both authenticated and unauthenticated requests - */ -export function optionalAuth(req: AuthRequest, res: Response, next: NextFunction) { - // Check for API key - const apiKey = req.headers['x-api-key'] as string; - if (apiKey && API_KEYS.includes(apiKey)) { - req.user = getUserFromApiKey(apiKey); - } else { - // Check for JWT token - const authHeader = req.headers.authorization; - if (authHeader) { - const token = authHeader.startsWith('Bearer ') - ? authHeader.slice(7) - : authHeader; - - try { - const decoded = jwt.verify(token, JWT_SECRET) as ApiUser; - req.user = decoded; - } catch (error) { - // Ignore invalid tokens for optional auth - } - } - } - - next(); -} - -/** - * Role-based access control middleware - */ -export function requireRole(...roles: Array<'admin' | 'user' | 'readonly'>) { - return (req: AuthRequest, res: Response, next: NextFunction) => { - if (!req.user) { - return res.status(401).json({ error: 'Authentication required' }); - } - - if (!roles.includes(req.user.role)) { - return res.status(403).json({ error: 'Insufficient permissions' }); - } - - next(); - }; -} - -/** - * Login endpoint handler - */ -export async function login(req: Request, res: Response) { - const { username, password } = req.body; - - if (!username || !password) { - res.status(400).json({ error: 'Username and password required' }); - return; - } - - const user = users.get(username); - if (!user) { - res.status(401).json({ error: 'Invalid credentials' }); - return; - } - - const validPassword = await bcrypt.compare(password, user.password); - if (!validPassword) { - res.status(401).json({ error: 'Invalid credentials' }); - return; - } - - const token = generateToken({ - id: username, - username, - role: user.role - }); - - res.json({ - token, - user: { - username, - role: user.role - } - }); -} - -/** - * Register endpoint handler (admin only) - */ -export async function register(req: AuthRequest, res: Response) { - if (!req.user || req.user.role !== 'admin') { - res.status(403).json({ error: 'Admin access required' }); - return; - } - - const { username, password, role = 'user' } = req.body; - - if (!username || !password) { - res.status(400).json({ error: 'Username and password required' }); - return; - } - - if (users.has(username)) { - res.status(400).json({ error: 'User already exists' }); - return; - } - - const hashedPassword = await bcrypt.hash(password, 10); - users.set(username, { - password: hashedPassword, - role: role as 'admin' | 'user' | 'readonly' - }); - - res.json({ - message: 'User created successfully', - user: { - username, - role - } - }); -} - -/** - * Generate API key endpoint handler - */ -export async function generateApiKey(req: AuthRequest, res: Response) { - if (!req.user) { - res.status(401).json({ error: 'Authentication required' }); - return; - } - - const apiKey = `${req.user.username}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - - const user = users.get(req.user.username); - if (user) { - user.apiKey = apiKey; - API_KEYS.push(apiKey); - } - - res.json({ apiKey }); -} - -// Helper functions -function generateToken(user: ApiUser): string { - return jwt.sign(user, JWT_SECRET, { expiresIn: '7d' }); -} - -function getUserFromApiKey(apiKey: string): ApiUser { - // Check if it's a user-specific API key - for (const [username, user] of users.entries()) { - if (user.apiKey === apiKey) { - return { - id: username, - username, - role: user.role, - apiKey - }; - } - } - - // Default user for demo API key - return { - id: 'demo', - username: 'demo', - role: 'readonly', - apiKey - }; -} - -/** - * Rate limiting per user - */ -const rateLimits = new Map(); - -export function rateLimit(maxRequests: number = 100, windowMs: number = 60000) { - return (req: AuthRequest, res: Response, next: NextFunction) => { - const userId = req.user?.id || req.ip || 'anonymous'; - const now = Date.now(); - - let userLimit = rateLimits.get(userId); - - if (!userLimit || userLimit.resetTime < now) { - userLimit = { count: 0, resetTime: now + windowMs }; - rateLimits.set(userId, userLimit); - userLimit.count++; - return next(); - } - - if (userLimit.count >= maxRequests) { - const retryAfter = Math.ceil((userLimit.resetTime - now) / 1000); - res.set('Retry-After', String(retryAfter)); - return res.status(429).json({ - error: 'Too many requests', - retryAfter - }); - } - - userLimit.count++; - next(); - }; -} \ No newline at end of file +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import bcrypt from 'bcrypt'; +import { ApiUser } from '../types'; + +// Default secret (should be overridden in production) +const JWT_SECRET = process.env.JWT_SECRET || 'indexer-secret-key-change-in-production'; +const API_KEYS = process.env.API_KEYS ? process.env.API_KEYS.split(',') : ['demo-api-key']; + +// In-memory user store (replace with database in production) +const users: Map = new Map([ + ['admin', { + password: '$2b$10$xQX2Y6Y9JZpH3X9Y3pDRg.vPGvH2mJYWxr7ckL9lKVGN3LpC9Y7Ym', // 'admin123' + role: 'admin' + }], + ['demo', { + password: '$2b$10$9IQqO2RgqFqFxQb5Y5NqOejzB1wK6zY8JQFqO2RgqFqFxQb5Y5NqO', // 'demo123' + role: 'readonly', + apiKey: 'demo-api-key' + }] +]); + +export interface AuthRequest extends Request { + user?: ApiUser; +} + +/** + * Authentication middleware + * Supports JWT tokens and API keys + */ +export function authenticate(req: AuthRequest, res: Response, next: NextFunction) { + // Check for API key first + const apiKey = req.headers['x-api-key'] as string; + if (apiKey && API_KEYS.includes(apiKey)) { + req.user = getUserFromApiKey(apiKey); + return next(); + } + + // Check for JWT token + const authHeader = req.headers.authorization; + if (!authHeader) { + return res.status(401).json({ error: 'No authorization header' }); + } + + const token = authHeader.startsWith('Bearer ') + ? authHeader.slice(7) + : authHeader; + + try { + const decoded = jwt.verify(token, JWT_SECRET) as ApiUser; + req.user = decoded; + next(); + } catch (error) { + return res.status(401).json({ error: 'Invalid token' }); + } +} + +/** + * Optional authentication middleware + * Allows both authenticated and unauthenticated requests + */ +export function optionalAuth(req: AuthRequest, res: Response, next: NextFunction) { + // Check for API key + const apiKey = req.headers['x-api-key'] as string; + if (apiKey && API_KEYS.includes(apiKey)) { + req.user = getUserFromApiKey(apiKey); + } else { + // Check for JWT token + const authHeader = req.headers.authorization; + if (authHeader) { + const token = authHeader.startsWith('Bearer ') + ? authHeader.slice(7) + : authHeader; + + try { + const decoded = jwt.verify(token, JWT_SECRET) as ApiUser; + req.user = decoded; + } catch (error) { + // Ignore invalid tokens for optional auth + } + } + } + + next(); +} + +/** + * Role-based access control middleware + */ +export function requireRole(...roles: Array<'admin' | 'user' | 'readonly'>) { + return (req: AuthRequest, res: Response, next: NextFunction) => { + if (!req.user) { + return res.status(401).json({ error: 'Authentication required' }); + } + + if (!roles.includes(req.user.role)) { + return res.status(403).json({ error: 'Insufficient permissions' }); + } + + next(); + }; +} + +/** + * Login endpoint handler + */ +export async function login(req: Request, res: Response) { + const { username, password } = req.body; + + if (!username || !password) { + res.status(400).json({ error: 'Username and password required' }); + return; + } + + const user = users.get(username); + if (!user) { + res.status(401).json({ error: 'Invalid credentials' }); + return; + } + + const validPassword = await bcrypt.compare(password, user.password); + if (!validPassword) { + res.status(401).json({ error: 'Invalid credentials' }); + return; + } + + const token = generateToken({ + id: username, + username, + role: user.role + }); + + res.json({ + token, + user: { + username, + role: user.role + } + }); +} + +/** + * Register endpoint handler (admin only) + */ +export async function register(req: AuthRequest, res: Response) { + if (!req.user || req.user.role !== 'admin') { + res.status(403).json({ error: 'Admin access required' }); + return; + } + + const { username, password, role = 'user' } = req.body; + + if (!username || !password) { + res.status(400).json({ error: 'Username and password required' }); + return; + } + + if (users.has(username)) { + res.status(400).json({ error: 'User already exists' }); + return; + } + + const hashedPassword = await bcrypt.hash(password, 10); + users.set(username, { + password: hashedPassword, + role: role as 'admin' | 'user' | 'readonly' + }); + + res.json({ + message: 'User created successfully', + user: { + username, + role + } + }); +} + +/** + * Generate API key endpoint handler + */ +export async function generateApiKey(req: AuthRequest, res: Response) { + if (!req.user) { + res.status(401).json({ error: 'Authentication required' }); + return; + } + + const apiKey = `${req.user.username}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + + const user = users.get(req.user.username); + if (user) { + user.apiKey = apiKey; + API_KEYS.push(apiKey); + } + + res.json({ apiKey }); +} + +// Helper functions +function generateToken(user: ApiUser): string { + return jwt.sign(user, JWT_SECRET, { expiresIn: '7d' }); +} + +function getUserFromApiKey(apiKey: string): ApiUser { + // Check if it's a user-specific API key + for (const [username, user] of users.entries()) { + if (user.apiKey === apiKey) { + return { + id: username, + username, + role: user.role, + apiKey + }; + } + } + + // Default user for demo API key + return { + id: 'demo', + username: 'demo', + role: 'readonly', + apiKey + }; +} + +/** + * Rate limiting per user + */ +const rateLimits = new Map(); + +export function rateLimit(maxRequests: number = 100, windowMs: number = 60000) { + return (req: AuthRequest, res: Response, next: NextFunction) => { + const userId = req.user?.id || req.ip || 'anonymous'; + const now = Date.now(); + + let userLimit = rateLimits.get(userId); + + if (!userLimit || userLimit.resetTime < now) { + userLimit = { count: 0, resetTime: now + windowMs }; + rateLimits.set(userId, userLimit); + userLimit.count++; + return next(); + } + + if (userLimit.count >= maxRequests) { + const retryAfter = Math.ceil((userLimit.resetTime - now) / 1000); + res.set('Retry-After', String(retryAfter)); + return res.status(429).json({ + error: 'Too many requests', + retryAfter + }); + } + + userLimit.count++; + next(); + }; +} + +// Export alias for compatibility with server.ts +export const authMiddleware = authenticate; \ No newline at end of file diff --git a/src/api/rest/routes.ts b/src/api/rest/routes.ts index 72af91c..d89895e 100644 --- a/src/api/rest/routes.ts +++ b/src/api/rest/routes.ts @@ -1,65 +1,65 @@ -/** - * REST API Routes - * Configures all API endpoints using modular controllers - */ - -import { Router } from 'express'; -import { Indexer } from '../../core/indexer'; -import { - IndexerController, - QueryController, - FilesController, - ExportController, - HealthController, - AIController -} from '../controllers'; - -export function restRouter(indexer: Indexer): Router { - const router = Router(); - - // Initialize controllers - const indexerCtrl = new IndexerController(indexer); - const queryCtrl = new QueryController(indexer); - const filesCtrl = new FilesController(indexer); - const exportCtrl = new ExportController(indexer); - const healthCtrl = new HealthController(indexer); - const aiCtrl = new AIController(indexer); - - // Indexer routes - router.post('/index', indexerCtrl.buildIndex); - router.get('/index/status', indexerCtrl.getIndexStatus); - router.get('/index', indexerCtrl.getIndex); - router.patch('/index', indexerCtrl.updateIndex); - - // Query routes - router.post('/query', queryCtrl.query); - router.get('/functions', queryCtrl.getFunctions); - router.get('/classes', queryCtrl.getClasses); - router.get('/dependencies', queryCtrl.getDependencies); - - // File routes - router.get('/files', filesCtrl.getFiles); - router.get('/files/*', filesCtrl.getFile); - router.post('/watch', filesCtrl.watchFiles); - router.get('/stats', filesCtrl.getStats); - - // Export routes - router.post('/export', exportCtrl.exportIndex); - - // Health routes - router.get('/health', healthCtrl.getHealth); - router.get('/health/detailed', healthCtrl.getDetailedHealth); - - // AI routes - router.post('/ai/analyze', aiCtrl.analyze); - router.post('/ai/predict-bugs', aiCtrl.predictBugs); - router.post('/ai/detect-smells', aiCtrl.detectCodeSmells); - router.post('/ai/analyze-security', aiCtrl.analyzeSecurity); - router.post('/ai/suggest-refactoring', aiCtrl.suggestRefactoring); - router.post('/ai/generate-tests', aiCtrl.generateTests); - router.post('/ai/analyze-skills', aiCtrl.analyzeSkills); - router.post('/ai/analyze-python', aiCtrl.analyzePython); - router.get('/ai/language-stats', aiCtrl.getLanguageStats); - - return router; +/** + * REST API Routes + * Configures all API endpoints using modular controllers + */ + +import { Router } from 'express'; +import { Indexer } from '../../core/indexer'; +import { + IndexerController, + QueryController, + FilesController, + ExportController, + HealthController, + AIController +} from '../controllers'; + +export function restRouter(indexer: Indexer): Router { + const router = Router(); + + // Initialize controllers + const indexerCtrl = new IndexerController(indexer); + const queryCtrl = new QueryController(indexer); + const filesCtrl = new FilesController(indexer); + const exportCtrl = new ExportController(indexer); + const healthCtrl = new HealthController(indexer); + const aiCtrl = new AIController(indexer); + + // Indexer routes + router.post('/index', indexerCtrl.buildIndex); + router.get('/index/status', indexerCtrl.getIndexStatus); + router.get('/index', indexerCtrl.getIndex); + router.patch('/index', indexerCtrl.updateIndex); + + // Query routes + router.post('/query', queryCtrl.query); + router.get('/functions', queryCtrl.getFunctions); + router.get('/classes', queryCtrl.getClasses); + router.get('/dependencies', queryCtrl.getDependencies); + + // File routes + router.get('/files', filesCtrl.getFiles); + router.get('/files/*', filesCtrl.getFile); + router.post('/watch', filesCtrl.watchFiles); + router.get('/stats', filesCtrl.getStats); + + // Export routes + router.post('/export', exportCtrl.exportIndex); + + // Health routes + router.get('/health', healthCtrl.getHealth); + router.get('/health/detailed', healthCtrl.getDetailedHealth); + + // AI routes + router.post('/ai/analyze', aiCtrl.analyze); + router.post('/ai/predict-bugs', aiCtrl.predictBugs); + router.post('/ai/detect-smells', aiCtrl.detectCodeSmells); + router.post('/ai/analyze-security', aiCtrl.analyzeSecurity); + router.post('/ai/suggest-refactoring', aiCtrl.suggestRefactoring); + router.post('/ai/generate-tests', aiCtrl.generateTests); + router.post('/ai/analyze-skills', aiCtrl.analyzeSkills); + router.post('/ai/analyze-python', aiCtrl.analyzePython); + router.get('/ai/language-stats', aiCtrl.getLanguageStats); + + return router; } \ No newline at end of file diff --git a/src/api/server.ts b/src/api/server.ts index 2b0cbb4..f23779a 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -1,212 +1,195 @@ -import * as dotenv from 'dotenv'; -import logger from '../../utils/logger'; -// Load environment variables -dotenv.config({ path: '.env.local' }); -dotenv.config(); - -import express, { Express } from 'express'; -import { ApolloServer } from '@apollo/server'; -import { expressMiddleware } from '@apollo/server/express4'; -import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'; -import { makeExecutableSchema } from '@graphql-tools/schema'; -import { createServer } from 'http'; -import { WebSocketServer } from 'ws'; -import { useServer } from 'graphql-ws/lib/use/ws'; -import cors from 'cors'; -import helmet from 'helmet'; -import morgan from 'morgan'; -import rateLimit from 'express-rate-limit'; -import { json } from 'body-parser'; - -import { Indexer } from '../core/indexer'; -import { restRouter } from './rest/routes'; -import { typeDefs } from './graphql/schema'; -import { resolvers } from './graphql/resolvers'; -import { authMiddleware } from './middleware/auth'; -import { WebSocketHandler } from './websocket/handler'; -import { ApiConfig } from './types'; - -export class IndexerAPIServer { - private app: Express; - private httpServer: any; - private apolloServer: ApolloServer; - private wsServer: WebSocketServer; - private indexer: Indexer; - private config: ApiConfig; - private wsHandler: WebSocketHandler; - - constructor(indexer: Indexer, config: ApiConfig = {}) { - this.indexer = indexer; - this.config = { - port: config.port || 4000, - host: config.host || 'localhost', - enableAuth: config.enableAuth !== false, - rateLimitWindow: config.rateLimitWindow || 15 * 60 * 1000, // 15 minutes - rateLimitMax: config.rateLimitMax || 100, - corsOrigins: config.corsOrigins || '*', - ...config - }; - - this.app = express(); - this.httpServer = createServer(this.app); - this.wsHandler = new WebSocketHandler(this.indexer); - } - - async initialize(): Promise { - // Setup middleware - this.setupMiddleware(); - - // Setup REST routes - this.setupREST(); - - // Setup GraphQL - await this.setupGraphQL(); - - // Setup WebSocket - this.setupWebSocket(); - } - - private setupMiddleware(): void { - // Security headers - this.app.use(helmet({ - contentSecurityPolicy: false, // Disable for GraphQL playground - })); - - // CORS - this.app.use(cors({ - origin: this.config.corsOrigins, - credentials: true - })); - - // Logging - this.app.use(morgan('combined')); - - // Rate limiting - const limiter = rateLimit({ - windowMs: this.config.rateLimitWindow!, - max: this.config.rateLimitMax!, - message: 'Too many requests from this IP, please try again later.' - }); - this.app.use('/api/', limiter); - - // Body parsing - this.app.use(express.json({ limit: '10mb' })); - this.app.use(express.urlencoded({ extended: true, limit: '10mb' })); - } - - private setupREST(): void { - // Apply auth middleware if enabled - if (this.config.enableAuth) { - this.app.use('/api', authMiddleware); - } - - // Mount REST routes - this.app.use('/api', restRouter(this.indexer)); - - // Health check endpoint - this.app.get('/health', (req, res) => { - res.json({ - status: 'healthy', - timestamp: new Date().toISOString(), - uptime: process.uptime(), - memory: process.memoryUsage() - }); - }); - } - - private async setupGraphQL(): Promise { - const schema = makeExecutableSchema({ - typeDefs, - resolvers: resolvers(this.indexer) - }); - - this.apolloServer = new ApolloServer({ - schema, - plugins: [ - ApolloServerPluginDrainHttpServer({ httpServer: this.httpServer }), - { - async serverWillStart() { - return { - async drainServer() { - await serverCleanup.dispose(); - } - }; - } - } - ], - }); - - await this.apolloServer.start(); - - // Apply GraphQL middleware - this.app.use( - '/graphql', - cors(), - json(), - expressMiddleware(this.apolloServer, { - context: async ({ req }) => ({ - indexer: this.indexer, - user: req.user, // Added by auth middleware - req - }) - }) - ); - - // Store cleanup function - const serverCleanup = { dispose: async () => {} }; - } - - private setupWebSocket(): void { - this.wsServer = new WebSocketServer({ - server: this.httpServer, - path: '/ws' - }); - - // Handle WebSocket connections - this.wsServer.on('connection', (ws, req) => { - this.wsHandler.handleConnection(ws, req); - }); - - // Setup GraphQL subscriptions over WebSocket - const schema = makeExecutableSchema({ - typeDefs, - resolvers: resolvers(this.indexer) - }); - - useServer( - { - schema, - context: async (ctx, msg, args) => ({ - indexer: this.indexer - }) - }, - this.wsServer - ); - } - - async start(): Promise { - await this.initialize(); - - return new Promise((resolve) => { - this.httpServer.listen(this.config.port, this.config.host, () => { - logger.info(`🚀 Server ready at http://${this.config.host}:${this.config.port}`); - logger.info(`📡 REST API available at http://${this.config.host}:${this.config.port}/api`); - logger.info(`🎮 GraphQL endpoint at http://${this.config.host}:${this.config.port}/graphql`); - logger.info(`🔌 WebSocket endpoint at ws://${this.config.host}:${this.config.port}/ws`); - resolve(); - }); - }); - } - - async stop(): Promise { - await this.apolloServer.stop(); - this.wsServer.close(); - - return new Promise((resolve) => { - this.httpServer.close(() => { - logger.info('Server stopped'); - resolve(); - }); - }); - } +import * as dotenv from 'dotenv'; +import logger from '../utils/logger'; +// Load environment variables +dotenv.config({ path: '.env.local' }); +dotenv.config(); + +import express, { Express } from 'express'; +import { ApolloServer } from 'apollo-server-express'; +import { makeExecutableSchema } from '@graphql-tools/schema'; +import { createServer } from 'http'; +import { WebSocketServer } from 'ws'; +// import { useServer } from 'graphql-ws/lib/use/ws'; +import cors from 'cors'; +import helmet from 'helmet'; +import morgan from 'morgan'; +import rateLimit from 'express-rate-limit'; +import { json } from 'body-parser'; + +import { Indexer } from '../core/indexer'; +import { restRouter } from './rest/routes'; +import { typeDefs } from './graphql/schema'; +import { resolvers } from './graphql/resolvers'; +import { authMiddleware } from './middleware/auth'; +import { WebSocketHandler } from './websocket/handler'; +import { ApiConfig } from './types'; + +export class IndexerAPIServer { + private app: Express; + private httpServer: any; + private apolloServer!: ApolloServer; + private wsServer!: WebSocketServer; + private indexer: Indexer; + private config: ApiConfig; + private wsHandler: WebSocketHandler; + + constructor(indexer: Indexer, config: ApiConfig = {}) { + this.indexer = indexer; + this.config = { + port: config.port || 4000, + host: config.host || 'localhost', + enableAuth: config.enableAuth !== false, + rateLimitWindow: config.rateLimitWindow || 15 * 60 * 1000, // 15 minutes + rateLimitMax: config.rateLimitMax || 100, + corsOrigins: config.corsOrigins || '*', + ...config + }; + + this.app = express(); + this.httpServer = createServer(this.app); + this.wsHandler = new WebSocketHandler(this.indexer); + } + + async initialize(): Promise { + // Setup middleware + this.setupMiddleware(); + + // Setup REST routes + this.setupREST(); + + // Setup GraphQL + await this.setupGraphQL(); + + // Setup WebSocket + this.setupWebSocket(); + } + + private setupMiddleware(): void { + // Security headers + this.app.use(helmet({ + contentSecurityPolicy: false, // Disable for GraphQL playground + })); + + // CORS + this.app.use(cors({ + origin: this.config.corsOrigins, + credentials: true + })); + + // Logging + this.app.use(morgan('combined')); + + // Rate limiting + const limiter = rateLimit({ + windowMs: this.config.rateLimitWindow!, + max: this.config.rateLimitMax!, + message: 'Too many requests from this IP, please try again later.' + }); + this.app.use('/api/', limiter); + + // Body parsing + this.app.use(express.json({ limit: '10mb' })); + this.app.use(express.urlencoded({ extended: true, limit: '10mb' })); + } + + private setupREST(): void { + // Apply auth middleware if enabled + if (this.config.enableAuth) { + this.app.use('/api', authMiddleware); + } + + // Mount REST routes + this.app.use('/api', restRouter(this.indexer)); + + // Health check endpoint + this.app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + memory: process.memoryUsage() + }); + }); + } + + private async setupGraphQL(): Promise { + const schema = makeExecutableSchema({ + typeDefs, + resolvers: resolvers(this.indexer) + }); + + this.apolloServer = new ApolloServer({ + schema, + context: ({ req }: { req: any }) => ({ + indexer: this.indexer, + user: req.user, // Added by auth middleware + req + }), + introspection: true, + }); + + await this.apolloServer.start(); + + // Apply GraphQL middleware + this.apolloServer.applyMiddleware({ + app: this.app, + path: '/graphql', + cors: false, // We handle CORS above + }); + } + + private setupWebSocket(): void { + this.wsServer = new WebSocketServer({ + server: this.httpServer, + path: '/ws' + }); + + // Handle WebSocket connections + this.wsServer.on('connection', (ws: any, req: any) => { + this.wsHandler.handleConnection(ws, req); + }); + + // Setup GraphQL subscriptions over WebSocket + const schema = makeExecutableSchema({ + typeDefs, + resolvers: resolvers(this.indexer) + }); + + // GraphQL subscriptions over WebSocket (commented out due to missing dependency) + // useServer( + // { + // schema, + // context: async (ctx: any, msg: any, args: any) => ({ + // indexer: this.indexer + // }) + // }, + // this.wsServer + // ); + } + + async start(): Promise { + await this.initialize(); + + return new Promise((resolve) => { + this.httpServer.listen(this.config.port, this.config.host, () => { + logger.info(`🚀 Server ready at http://${this.config.host}:${this.config.port}`); + logger.info(`📡 REST API available at http://${this.config.host}:${this.config.port}/api`); + logger.info(`🎮 GraphQL endpoint at http://${this.config.host}:${this.config.port}/graphql`); + logger.info(`🔌 WebSocket endpoint at ws://${this.config.host}:${this.config.port}/ws`); + resolve(); + }); + }); + } + + async stop(): Promise { + await this.apolloServer.stop(); + this.wsServer.close(); + + return new Promise((resolve) => { + this.httpServer.close(() => { + logger.info('Server stopped'); + resolve(); + }); + }); + } } \ No newline at end of file diff --git a/src/api/streaming/sse-handler.ts b/src/api/streaming/sse-handler.ts index ce017b4..9682dc3 100644 --- a/src/api/streaming/sse-handler.ts +++ b/src/api/streaming/sse-handler.ts @@ -1,310 +1,322 @@ -/** - * Server-Sent Events (SSE) handler for streaming AI analysis - * Provides real-time updates during analysis - */ - -import { Request, Response } from 'express'; -import { ClaudeSDKAnalyzer } from '../../ai/sdk-analyzer'; -import { SecurityAgent } from '../../ai/agents/security-agent'; -import { Indexer } from '../../core/indexer'; -import logger from '../../../utils/logger'; - -/** - * SSE handler for streaming analysis results - */ -export class SSEHandler { - private analyzer: ClaudeSDKAnalyzer; - private securityAgent: SecurityAgent; - private indexer: Indexer; - private activeConnections: Map = new Map(); - - constructor(indexer: Indexer) { - this.indexer = indexer; - this.analyzer = new ClaudeSDKAnalyzer(); - this.securityAgent = new SecurityAgent(); - - // Set up event listeners - this.setupEventListeners(); - } - - /** - * Handle SSE connection for streaming analysis - */ - async handleStreamAnalysis(req: Request, res: Response): Promise { - const clientId = `client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - - // Set up SSE headers - res.writeHead(200, { - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', - 'Connection': 'keep-alive', - 'Access-Control-Allow-Origin': '*' - }); - - // Store connection - this.activeConnections.set(clientId, res); - - // Send initial connection event - this.sendSSE(res, { - event: 'connected', - data: { clientId, timestamp: new Date().toISOString() } - }); - - // Handle client disconnect - req.on('close', () => { - this.activeConnections.delete(clientId); - logger.info(`SSE client disconnected: ${clientId}`); - }); - - try { - // Get current index - const index = await this.indexer.getIndex(); - - if (!index || Object.keys(index.files).length === 0) { - this.sendSSE(res, { - event: 'error', - data: { message: 'No index available. Please build index first.' } - }); - return; - } - - // Start streaming analysis - const analysisGenerator = this.analyzer.analyzeCodebaseStreaming(index); - - for await (const update of analysisGenerator) { - // Send update to client - this.sendSSE(res, { - event: update.type, - data: update - }); - - // Check if client is still connected - if (!this.activeConnections.has(clientId)) { - break; - } - } - - // Send completion event - this.sendSSE(res, { - event: 'complete', - data: { - clientId, - timestamp: new Date().toISOString(), - message: 'Analysis complete' - } - }); - - } catch (error) { - this.sendSSE(res, { - event: 'error', - data: { - message: error instanceof Error ? error.message : 'Unknown error', - timestamp: new Date().toISOString() - } - }); - } - } - - /** - * Handle SSE connection for security analysis - */ - async handleStreamSecurityAnalysis(req: Request, res: Response): Promise { - const clientId = `security-${Date.now()}`; - - // Set up SSE headers - res.writeHead(200, { - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', - 'Connection': 'keep-alive' - }); - - this.activeConnections.set(clientId, res); - - // Send initial connection - this.sendSSE(res, { - event: 'connected', - data: { clientId, type: 'security' } - }); - - // Handle disconnect - req.on('close', () => { - this.activeConnections.delete(clientId); - this.securityAgent.cancelAnalysis(); - }); - - try { - const index = await this.indexer.getIndex(); - const { focus, includeSecrets, includeDependencies } = req.query; - - // Set up event forwarding from agent - this.securityAgent.on('update', (update) => { - this.sendSSE(res, { - event: 'security-update', - data: update - }); - }); - - // Run security analysis - const report = await this.securityAgent.analyze(index, { - focus: focus ? String(focus).split(',') : undefined, - includeSecrets: includeSecrets === 'true', - includeDependencies: includeDependencies === 'true' - }); - - // Send final report - this.sendSSE(res, { - event: 'security-complete', - data: report - }); - - } catch (error) { - this.sendSSE(res, { - event: 'error', - data: { - type: 'security', - message: error instanceof Error ? error.message : 'Security analysis failed' - } - }); - } finally { - // Clean up listeners - this.securityAgent.removeAllListeners('update'); - } - } - - /** - * Handle progress updates for long-running operations - */ - async handleProgressStream(req: Request, res: Response): Promise { - const { operationId } = req.params; - - res.writeHead(200, { - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', - 'Connection': 'keep-alive' - }); - - // Send progress updates - const interval = setInterval(() => { - const progress = this.getOperationProgress(operationId); - - if (progress) { - this.sendSSE(res, { - event: 'progress', - data: progress - }); - - if (progress.status === 'complete' || progress.status === 'error') { - clearInterval(interval); - res.end(); - } - } - }, 1000); - - req.on('close', () => { - clearInterval(interval); - }); - } - - /** - * Broadcast event to all connected clients - */ - broadcast(event: string, data: any): void { - for (const [clientId, res] of this.activeConnections) { - this.sendSSE(res, { event, data }); - } - } - - /** - * Send SSE message - */ - private sendSSE(res: Response, message: SSEMessage): void { - try { - if (message.id) { - res.write(`id: ${message.id}\n`); - } - - if (message.event) { - res.write(`event: ${message.event}\n`); - } - - if (message.retry) { - res.write(`retry: ${message.retry}\n`); - } - - res.write(`data: ${JSON.stringify(message.data)}\n\n`); - - // Flush the response - (res as any).flush?.(); - } catch (error) { - logger.error('Error sending SSE:', error); - } - } - - /** - * Set up event listeners for analyzer - */ - private setupEventListeners(): void { - // Forward analyzer events to SSE clients - this.analyzer.on('security-update', (data) => { - this.broadcast('security-update', data); - }); - - this.analyzer.on('performance-update', (data) => { - this.broadcast('performance-update', data); - }); - - this.analyzer.on('architecture-update', (data) => { - this.broadcast('architecture-update', data); - }); - - this.analyzer.on('testing-update', (data) => { - this.broadcast('testing-update', data); - }); - - this.analyzer.on('analysis-cancelled', (analysisId) => { - this.broadcast('analysis-cancelled', { analysisId }); - }); - } - - /** - * Get operation progress (mock implementation) - */ - private getOperationProgress(operationId: string): any { - // This would retrieve actual progress from a store - return { - operationId, - status: 'in-progress', - progress: Math.random() * 100, - message: 'Analyzing code...' - }; - } - - /** - * Clean up resources - */ - cleanup(): void { - // Close all connections - for (const [clientId, res] of this.activeConnections) { - res.end(); - } - this.activeConnections.clear(); - - // Remove listeners - this.analyzer.removeAllListeners(); - this.securityAgent.removeAllListeners(); - } -} - -/** - * Type definitions - */ -interface SSEMessage { - id?: string; - event?: string; - data: any; - retry?: number; -} - -/** - * Express middleware for SSE endpoints - */ +/** + * Server-Sent Events (SSE) handler for streaming AI analysis + * Provides real-time updates during analysis + */ + +import { Request, Response } from 'express'; +import { ClaudeSDKAnalyzer } from '../../ai/sdk-analyzer'; +import { SecurityAgent } from '../../ai/agents/security-agent'; +import { Indexer } from '../../core/indexer'; +import logger from '../../utils/logger'; + +/** + * SSE handler for streaming analysis results + */ +export class SSEHandler { + private analyzer: ClaudeSDKAnalyzer; + private securityAgent: SecurityAgent; + private indexer: Indexer; + private activeConnections: Map = new Map(); + + constructor(indexer: Indexer) { + this.indexer = indexer; + this.analyzer = new ClaudeSDKAnalyzer(); + this.securityAgent = new SecurityAgent(); + + // Set up event listeners + this.setupEventListeners(); + } + + /** + * Handle SSE connection for streaming analysis + */ + async handleStreamAnalysis(req: Request, res: Response): Promise { + const clientId = `client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + + // Set up SSE headers + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Access-Control-Allow-Origin': '*' + }); + + // Store connection + this.activeConnections.set(clientId, res); + + // Send initial connection event + this.sendSSE(res, { + event: 'connected', + data: { clientId, timestamp: new Date().toISOString() } + }); + + // Handle client disconnect + req.on('close', () => { + this.activeConnections.delete(clientId); + logger.info(`SSE client disconnected: ${clientId}`); + }); + + try { + // Get current index + const index = await this.indexer.getIndex(); + + if (!index || Object.keys(index.files).length === 0) { + this.sendSSE(res, { + event: 'error', + data: { message: 'No index available. Please build index first.' } + }); + return; + } + + // Start streaming analysis + const analysisGenerator = this.analyzer.analyzeCodebaseStreaming(index); + + for await (const update of analysisGenerator) { + // Send update to client + this.sendSSE(res, { + event: update.type, + data: update + }); + + // Check if client is still connected + if (!this.activeConnections.has(clientId)) { + break; + } + } + + // Send completion event + this.sendSSE(res, { + event: 'complete', + data: { + clientId, + timestamp: new Date().toISOString(), + message: 'Analysis complete' + } + }); + + } catch (error) { + this.sendSSE(res, { + event: 'error', + data: { + message: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + } + }); + } + } + + /** + * Handle SSE connection for security analysis + */ + async handleStreamSecurityAnalysis(req: Request, res: Response): Promise { + const clientId = `security-${Date.now()}`; + + // Set up SSE headers + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive' + }); + + this.activeConnections.set(clientId, res); + + // Send initial connection + this.sendSSE(res, { + event: 'connected', + data: { clientId, type: 'security' } + }); + + // Handle disconnect + req.on('close', () => { + this.activeConnections.delete(clientId); + this.securityAgent.cancelAnalysis(); + }); + + try { + const index = await this.indexer.getIndex(); + const { focus, includeSecrets, includeDependencies } = req.query; + + // Set up event forwarding from agent + this.securityAgent.on('update', (update) => { + this.sendSSE(res, { + event: 'security-update', + data: update + }); + }); + + // Run security analysis + if (!index) { + this.sendEvent(res, 'error', { error: 'No index found' }); + return; + } + + const report = await this.securityAgent.analyze(index, { + focus: focus ? String(focus).split(',') : undefined, + includeSecrets: includeSecrets === 'true', + includeDependencies: includeDependencies === 'true' + }); + + // Send final report + this.sendSSE(res, { + event: 'security-complete', + data: report + }); + + } catch (error) { + this.sendSSE(res, { + event: 'error', + data: { + type: 'security', + message: error instanceof Error ? error.message : 'Security analysis failed' + } + }); + } finally { + // Clean up listeners + this.securityAgent.removeAllListeners('update'); + } + } + + /** + * Handle progress updates for long-running operations + */ + async handleProgressStream(req: Request, res: Response): Promise { + const { operationId } = req.params; + + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive' + }); + + // Send progress updates + const interval = setInterval(() => { + const progress = this.getOperationProgress(operationId); + + if (progress) { + this.sendSSE(res, { + event: 'progress', + data: progress + }); + + if (progress.status === 'complete' || progress.status === 'error') { + clearInterval(interval); + res.end(); + } + } + }, 1000); + + req.on('close', () => { + clearInterval(interval); + }); + } + + /** + * Broadcast event to all connected clients + */ + broadcast(event: string, data: any): void { + for (const [clientId, res] of this.activeConnections) { + this.sendSSE(res, { event, data }); + } + } + + /** + * Send SSE message + */ + private sendSSE(res: Response, message: SSEMessage): void { + try { + if (message.id) { + res.write(`id: ${message.id}\n`); + } + + if (message.event) { + res.write(`event: ${message.event}\n`); + } + + if (message.retry) { + res.write(`retry: ${message.retry}\n`); + } + + res.write(`data: ${JSON.stringify(message.data)}\n\n`); + + // Flush the response + (res as any).flush?.(); + } catch (error) { + logger.error('Error sending SSE:', error); + } + } + + /** + * Convenience method to send event with data + */ + private sendEvent(res: Response, event: string, data: any): void { + this.sendSSE(res, { event, data }); + } + + /** + * Set up event listeners for analyzer + */ + private setupEventListeners(): void { + // Forward analyzer events to SSE clients + this.analyzer.on('security-update', (data) => { + this.broadcast('security-update', data); + }); + + this.analyzer.on('performance-update', (data) => { + this.broadcast('performance-update', data); + }); + + this.analyzer.on('architecture-update', (data) => { + this.broadcast('architecture-update', data); + }); + + this.analyzer.on('testing-update', (data) => { + this.broadcast('testing-update', data); + }); + + this.analyzer.on('analysis-cancelled', (analysisId) => { + this.broadcast('analysis-cancelled', { analysisId }); + }); + } + + /** + * Get operation progress (mock implementation) + */ + private getOperationProgress(operationId: string): any { + // This would retrieve actual progress from a store + return { + operationId, + status: 'in-progress', + progress: Math.random() * 100, + message: 'Analyzing code...' + }; + } + + /** + * Clean up resources + */ + cleanup(): void { + // Close all connections + for (const [clientId, res] of this.activeConnections) { + res.end(); + } + this.activeConnections.clear(); + + // Remove listeners + this.analyzer.removeAllListeners(); + this.securityAgent.removeAllListeners(); + } +} + +/** + * Type definitions + */ +interface SSEMessage { + id?: string; + event?: string; + data: any; + retry?: number; +} + +/** + * Express middleware for SSE endpoints + */ diff --git a/src/api/types.ts b/src/api/types.ts index 0d6ea4a..6d3d689 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1,135 +1,135 @@ -export interface ApiConfig { - port?: number; - host?: string; - enableAuth?: boolean; - rateLimitWindow?: number; - rateLimitMax?: number; - corsOrigins?: string | string[]; - jwtSecret?: string; - apiKeys?: string[]; -} - -export interface ApiUser { - id: string; - username: string; - email?: string; - role: 'admin' | 'user' | 'readonly'; - apiKey?: string; -} - -export interface ApiRequest extends Express.Request { - user?: ApiUser; -} - -export interface IndexRequest { - path?: string; - options?: { - incremental?: boolean; - parallel?: boolean; - workers?: number; - force?: boolean; - }; -} - -export interface QueryRequest { - query: string; - options?: { - type?: 'all' | 'function' | 'class' | 'import' | 'export'; - fuzzy?: boolean; - limit?: number; - offset?: number; - }; -} - -export interface ExportRequest { - format: 'json' | 'markdown' | 'graphviz' | 'mermaid' | 'ascii'; - options?: { - pretty?: boolean; - includeStatistics?: boolean; - sortBy?: 'name' | 'complexity' | 'size'; - groupByDirectory?: boolean; - }; -} - -export interface ApiResponse { - success: boolean; - data?: T; - error?: string; - timestamp: string; - duration?: number; -} - -export interface IndexStats { - totalFiles: number; - totalFunctions: number; - totalClasses: number; - totalImports: number; - totalExports: number; - languages: Record; - complexity: { - average: number; - max: number; - distribution: Record; - }; - indexingTime: number; - memoryUsage: number; -} - -export interface FileSearchResult { - path: string; - language: string; - functions: string[]; - classes: string[]; - matches: Array<{ - line: number; - column: number; - text: string; - type: 'function' | 'class' | 'import' | 'export' | 'constant'; - }>; - score: number; -} - -export interface DependencyGraphNode { - id: string; - path: string; - type: 'file' | 'module' | 'package'; - language: string; - size: number; - complexity: number; -} - -export interface DependencyGraphEdge { - from: string; - to: string; - type: 'import' | 'export' | 'dependency'; - weight: number; -} - -export interface DependencyGraph { - nodes: DependencyGraphNode[]; - edges: DependencyGraphEdge[]; - clusters?: Record; - stats: { - totalNodes: number; - totalEdges: number; - avgDegree: number; - maxDegree: number; - circularDependencies: number; - }; -} - -export interface WebSocketMessage { - type: 'subscribe' | 'unsubscribe' | 'index' | 'query' | 'update'; - id?: string; - topic?: string; - data?: any; -} - -export interface WebSocketResponse { - type: 'subscribed' | 'unsubscribed' | 'data' | 'error' | 'update'; - id?: string; - topic?: string; - data?: any; - error?: string; - timestamp: string; +export interface ApiConfig { + port?: number; + host?: string; + enableAuth?: boolean; + rateLimitWindow?: number; + rateLimitMax?: number; + corsOrigins?: string | string[]; + jwtSecret?: string; + apiKeys?: string[]; +} + +export interface ApiUser { + id: string; + username: string; + email?: string; + role: 'admin' | 'user' | 'readonly'; + apiKey?: string; +} + +export interface ApiRequest extends Express.Request { + user?: ApiUser; +} + +export interface IndexRequest { + path?: string; + options?: { + incremental?: boolean; + parallel?: boolean; + workers?: number; + force?: boolean; + }; +} + +export interface QueryRequest { + query: string; + options?: { + type?: 'all' | 'function' | 'class' | 'import' | 'export'; + fuzzy?: boolean; + limit?: number; + offset?: number; + }; +} + +export interface ExportRequest { + format: 'json' | 'markdown' | 'graphviz' | 'mermaid' | 'ascii'; + options?: { + pretty?: boolean; + includeStatistics?: boolean; + sortBy?: 'name' | 'complexity' | 'size'; + groupByDirectory?: boolean; + }; +} + +export interface ApiResponse { + success: boolean; + data?: T; + error?: string; + timestamp: string; + duration?: number; +} + +export interface IndexStats { + totalFiles: number; + totalFunctions: number; + totalClasses: number; + totalImports: number; + totalExports: number; + languages: Record; + complexity: { + average: number; + max: number; + distribution: Record; + }; + indexingTime: number; + memoryUsage: number; +} + +export interface FileSearchResult { + path: string; + language: string; + functions: string[]; + classes: string[]; + matches: Array<{ + line: number; + column: number; + text: string; + type: 'function' | 'class' | 'import' | 'export' | 'constant'; + }>; + score: number; +} + +export interface DependencyGraphNode { + id: string; + path: string; + type: 'file' | 'module' | 'package'; + language: string; + size: number; + complexity: number; +} + +export interface DependencyGraphEdge { + from: string; + to: string; + type: 'import' | 'export' | 'dependency'; + weight: number; +} + +export interface DependencyGraph { + nodes: DependencyGraphNode[]; + edges: DependencyGraphEdge[]; + clusters?: Record; + stats: { + totalNodes: number; + totalEdges: number; + avgDegree: number; + maxDegree: number; + circularDependencies: number; + }; +} + +export interface WebSocketMessage { + type: 'subscribe' | 'unsubscribe' | 'index' | 'query' | 'update'; + id?: string; + topic?: string; + data?: any; +} + +export interface WebSocketResponse { + type: 'connected' | 'subscribed' | 'unsubscribed' | 'data' | 'error' | 'update'; + id?: string; + topic?: string; + data?: any; + error?: string; + timestamp: string; } \ No newline at end of file diff --git a/src/api/web-chat.ts b/src/api/web-chat.ts index c83b284..a0fd485 100644 --- a/src/api/web-chat.ts +++ b/src/api/web-chat.ts @@ -1,419 +1,425 @@ -/** - * Web-based chat interface for browser access - */ - -import express from 'express'; -import { WebSocketServer } from 'ws'; -import { ClaudeChat } from './claude-chat'; -import * as path from 'path'; -import * as fs from 'fs'; -import logger from '../../utils/logger'; - -export class WebChatServer { - private app: express.Application; - private wss: WebSocketServer; - private chat: ClaudeChat; - private port: number; - - constructor(port: number = 3000) { - this.port = port; - this.app = express(); - this.wss = new WebSocketServer({ noServer: true }); - this.chat = new ClaudeChat(); - - this.setupRoutes(); - this.setupWebSocket(); - } - - /** - * Set up Express routes - */ - private setupRoutes(): void { - // Serve static files - this.app.use(express.static(path.join(__dirname, '../../public'))); - - // Serve chat interface - this.app.get('/', (req, res) => { - res.send(this.getChatHTML()); - }); - - // Health check - this.app.get('/health', (req, res) => { - res.json({ status: 'healthy', mode: 'chat' }); - }); - } - - /** - * Set up WebSocket handling - */ - private setupWebSocket(): void { - // Handle upgrade requests - this.app.server?.on('upgrade', (request, socket, head) => { - this.wss.handleUpgrade(request, socket, head, (ws) => { - this.wss.emit('connection', ws, request); - }); - }); - - // Handle WebSocket connections - this.wss.on('connection', (ws) => { - logger.info('New chat client connected'); - - ws.send(JSON.stringify({ - type: 'connected', - message: 'Connected to Claude Chat' - })); - - ws.on('message', async (data) => { - const message = JSON.parse(data.toString()); - - if (message.type === 'query') { - // Stream response back - const response = await this.chat.processQuery(message.content); - - ws.send(JSON.stringify({ - type: 'response', - content: response - })); - } - }); - - ws.on('close', () => { - logger.info('Chat client disconnected'); - }); - }); - } - - /** - * Start the server - */ - async start(): Promise { - const server = this.app.listen(this.port, () => { - logger.info(`Web chat interface running at http://localhost:${this.port}`); - }); - - // Store server reference for WebSocket upgrade - (this.app as any).server = server; - } - - /** - * Get the HTML for the chat interface - */ - private getChatHTML(): string { - return ` - - - - - Claude Code Chat - - - -
-
- - Claude Code Chat - Interactive Assistant -
- -
- Commands: /refresh (rebuild index) | /clear (clear chat) | /stats (project stats) | /help (show help) -
- -
-
-
- 👋 Hi! I'm Claude, your AI programming assistant. I have access to your entire codebase and can help with: -

- • Explaining code and architecture
- • Finding bugs and security issues
- • Generating tests and documentation
- • Refactoring suggestions
- • Answering any questions about your project

- What would you like to know? -
-
-
- -
- - -
-
- - - -`; - } -} +/** + * Web-based chat interface for browser access + */ + +import express from 'express'; +import { WebSocketServer } from 'ws'; +import { Server } from 'http'; +// import { ClaudeChat } from './claude-chat'; // Commented out - missing file +import * as path from 'path'; +import * as fs from 'fs'; +import logger from '../utils/logger'; + +interface ExtendedApplication extends express.Application { + server?: Server; +} + +export class WebChatServer { + private app: ExtendedApplication; + private wss: WebSocketServer; + // private chat: ClaudeChat; // Commented out - missing file + private port: number; + + constructor(port: number = 3000) { + this.port = port; + this.app = express(); + this.wss = new WebSocketServer({ noServer: true }); + // this.chat = new ClaudeChat(); // Commented out - missing file + + this.setupRoutes(); + this.setupWebSocket(); + } + + /** + * Set up Express routes + */ + private setupRoutes(): void { + // Serve static files + this.app.use(express.static(path.join(__dirname, '../../public'))); + + // Serve chat interface + this.app.get('/', (req, res) => { + res.send(this.getChatHTML()); + }); + + // Health check + this.app.get('/health', (req, res) => { + res.json({ status: 'healthy', mode: 'chat' }); + }); + } + + /** + * Set up WebSocket handling + */ + private setupWebSocket(): void { + // Handle upgrade requests + this.app.server?.on('upgrade', (request, socket, head) => { + this.wss.handleUpgrade(request, socket, head, (ws) => { + this.wss.emit('connection', ws, request); + }); + }); + + // Handle WebSocket connections + this.wss.on('connection', (ws) => { + logger.info('New chat client connected'); + + ws.send(JSON.stringify({ + type: 'connected', + message: 'Connected to Claude Chat' + })); + + ws.on('message', async (data) => { + const message = JSON.parse(data.toString()); + + if (message.type === 'query') { + // Stream response back + // const response = await this.chat.processQuery(message.content); // Commented out - missing chat + const response = 'Chat functionality not available - missing ClaudeChat implementation'; + + ws.send(JSON.stringify({ + type: 'response', + content: response + })); + } + }); + + ws.on('close', () => { + logger.info('Chat client disconnected'); + }); + }); + } + + /** + * Start the server + */ + async start(): Promise { + const server = this.app.listen(this.port, () => { + logger.info(`Web chat interface running at http://localhost:${this.port}`); + }); + + // Store server reference for WebSocket upgrade + this.app.server = server; + } + + /** + * Get the HTML for the chat interface + */ + private getChatHTML(): string { + return ` + + + + + Claude Code Chat + + + +
+
+ + Claude Code Chat - Interactive Assistant +
+ +
+ Commands: /refresh (rebuild index) | /clear (clear chat) | /stats (project stats) | /help (show help) +
+ +
+
+
+ 👋 Hi! I'm Claude, your AI programming assistant. I have access to your entire codebase and can help with: +

+ • Explaining code and architecture
+ • Finding bugs and security issues
+ • Generating tests and documentation
+ • Refactoring suggestions
+ • Answering any questions about your project

+ What would you like to know? +
+
+
+ +
+ + +
+
+ + + +`; + } +} diff --git a/src/api/websocket/handler.ts b/src/api/websocket/handler.ts index e0466f3..d7ea6bd 100644 --- a/src/api/websocket/handler.ts +++ b/src/api/websocket/handler.ts @@ -1,259 +1,234 @@ -import { WebSocket } from 'ws'; -import { IncomingMessage } from 'http'; -import { Indexer } from '../../core/indexer'; -import { WebSocketMessage, WebSocketResponse } from '../types'; -import { EventEmitter } from 'events'; -import logger from '../../../utils/logger'; - -export class WebSocketHandler extends EventEmitter { - private indexer: Indexer; - private clients: Map; - private subscriptions: Map>; - - constructor(indexer: Indexer) { - super(); - this.indexer = indexer; - this.clients = new Map(); - this.subscriptions = new Map(); - - // Subscribe to indexer events - this.setupIndexerListeners(); - } - - handleConnection(ws: WebSocket, req: IncomingMessage): void { - const clientId = this.generateClientId(); - this.clients.set(clientId, ws); - this.subscriptions.set(clientId, new Set()); - - logger.info(`WebSocket client connected: ${clientId}`); - - // Send welcome message - this.sendToClient(clientId, { - type: 'connected', - data: { clientId, timestamp: new Date().toISOString() }, - timestamp: new Date().toISOString() - }); - - // Handle messages - ws.on('message', (data: Buffer) => { - this.handleMessage(clientId, data); - }); - - // Handle close - ws.on('close', () => { - this.handleDisconnect(clientId); - }); - - // Handle errors - ws.on('error', (error) => { - logger.error(`WebSocket error for client ${clientId}:`, error); - this.handleDisconnect(clientId); - }); - - // Ping/pong for keep-alive - const pingInterval = setInterval(() => { - if (ws.readyState === WebSocket.OPEN) { - ws.ping(); - } else { - clearInterval(pingInterval); - } - }, 30000); - } - - private handleMessage(clientId: string, data: Buffer): void { - try { - const message: WebSocketMessage = JSON.parse(data.toString()); - - switch (message.type) { - case 'subscribe': - this.handleSubscribe(clientId, message); - break; - case 'unsubscribe': - this.handleUnsubscribe(clientId, message); - break; - case 'index': - this.handleIndexRequest(clientId, message); - break; - case 'query': - this.handleQueryRequest(clientId, message); - break; - default: - this.sendError(clientId, `Unknown message type: ${message.type}`); - } - } catch (error: any) { - this.sendError(clientId, `Invalid message: ${error.message}`); - } - } - - private handleSubscribe(clientId: string, message: WebSocketMessage): void { - const topic = message.topic; - if (!topic) { - this.sendError(clientId, 'Topic is required for subscription'); - return; - } - - const clientSubs = this.subscriptions.get(clientId); - if (clientSubs) { - clientSubs.add(topic); - } - - this.sendToClient(clientId, { - type: 'subscribed', - topic, - data: { message: `Subscribed to ${topic}` }, - timestamp: new Date().toISOString() - }); - - logger.info(`Client ${clientId} subscribed to ${topic}`); - } - - private handleUnsubscribe(clientId: string, message: WebSocketMessage): void { - const topic = message.topic; - if (!topic) { - this.sendError(clientId, 'Topic is required for unsubscription'); - return; - } - - const clientSubs = this.subscriptions.get(clientId); - if (clientSubs) { - clientSubs.delete(topic); - } - - this.sendToClient(clientId, { - type: 'unsubscribed', - topic, - data: { message: `Unsubscribed from ${topic}` }, - timestamp: new Date().toISOString() - }); - - logger.info(`Client ${clientId} unsubscribed from ${topic}`); - } - - private async handleIndexRequest(clientId: string, message: WebSocketMessage): Promise { - try { - const { path, options } = message.data || {}; - - // Send progress updates - const progressInterval = setInterval(() => { - this.sendToClient(clientId, { - type: 'update', - topic: 'index-progress', - data: { status: 'indexing', progress: Math.random() * 100 }, - timestamp: new Date().toISOString() - }); - }, 1000); - - const result = await this.indexer.buildIndex(path || process.cwd(), options); - - clearInterval(progressInterval); - - this.sendToClient(clientId, { - type: 'data', - topic: 'index-result', - data: { - success: true, - filesIndexed: Object.keys(result.files).length, - statistics: result.statistics - }, - timestamp: new Date().toISOString() - }); - } catch (error: any) { - this.sendError(clientId, `Index failed: ${error.message}`); - } - } - - private async handleQueryRequest(clientId: string, message: WebSocketMessage): Promise { - try { - const { query, options } = message.data || {}; - - if (!query) { - this.sendError(clientId, 'Query is required'); - return; - } - - const results = await this.indexer.query(query, options); - - this.sendToClient(clientId, { - type: 'data', - topic: 'query-result', - data: results, - timestamp: new Date().toISOString() - }); - } catch (error: any) { - this.sendError(clientId, `Query failed: ${error.message}`); - } - } - - private handleDisconnect(clientId: string): void { - this.clients.delete(clientId); - this.subscriptions.delete(clientId); - logger.info(`WebSocket client disconnected: ${clientId}`); - } - - private setupIndexerListeners(): void { - // Listen for file changes - this.indexer.on('file-changed', (data) => { - this.broadcast('file-updates', { - type: 'update', - topic: 'file-updates', - data, - timestamp: new Date().toISOString() - }); - }); - - // Listen for index updates - this.indexer.on('index-updated', (data) => { - this.broadcast('index-updates', { - type: 'update', - topic: 'index-updates', - data, - timestamp: new Date().toISOString() - }); - }); - - // Listen for errors - this.indexer.on('error', (error) => { - this.broadcast('errors', { - type: 'error', - topic: 'errors', - error: error.message, - timestamp: new Date().toISOString() - }); - }); - } - - private broadcast(topic: string, message: WebSocketResponse): void { - for (const [clientId, subscriptions] of this.subscriptions.entries()) { - if (subscriptions.has(topic)) { - this.sendToClient(clientId, message); - } - } - } - - private sendToClient(clientId: string, message: WebSocketResponse): void { - const client = this.clients.get(clientId); - if (client && client.readyState === WebSocket.OPEN) { - client.send(JSON.stringify(message)); - } - } - - private sendError(clientId: string, error: string): void { - this.sendToClient(clientId, { - type: 'error', - error, - timestamp: new Date().toISOString() - }); - } - - private generateClientId(): string { - return `client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - } - - public getConnectedClients(): number { - return this.clients.size; - } - - public getSubscriptions(): Map> { - return this.subscriptions; - } +import { WebSocket } from 'ws'; +import { IncomingMessage } from 'http'; +import { Indexer } from '../../core/indexer'; +import { WebSocketMessage, WebSocketResponse } from '../types'; +import { EventEmitter } from 'events'; +import logger from '../../utils/logger'; + +export class WebSocketHandler extends EventEmitter { + private indexer: Indexer; + private clients: Map; + private subscriptions: Map>; + + constructor(indexer: Indexer) { + super(); + this.indexer = indexer; + this.clients = new Map(); + this.subscriptions = new Map(); + + // Subscribe to indexer events + this.setupIndexerListeners(); + } + + handleConnection(ws: WebSocket, req: IncomingMessage): void { + const clientId = this.generateClientId(); + this.clients.set(clientId, ws); + this.subscriptions.set(clientId, new Set()); + + logger.info(`WebSocket client connected: ${clientId}`); + + // Send welcome message + this.sendToClient(clientId, { + type: 'connected', + data: { clientId, timestamp: new Date().toISOString() }, + timestamp: new Date().toISOString() + }); + + // Handle messages + ws.on('message', (data: Buffer) => { + this.handleMessage(clientId, data); + }); + + // Handle close + ws.on('close', () => { + this.handleDisconnect(clientId); + }); + + // Handle errors + ws.on('error', (error) => { + logger.error(`WebSocket error for client ${clientId}:`, error); + this.handleDisconnect(clientId); + }); + + // Ping/pong for keep-alive + const pingInterval = setInterval(() => { + if (ws.readyState === WebSocket.OPEN) { + ws.ping(); + } else { + clearInterval(pingInterval); + } + }, 30000); + } + + private handleMessage(clientId: string, data: Buffer): void { + try { + const message: WebSocketMessage = JSON.parse(data.toString()); + + switch (message.type) { + case 'subscribe': + this.handleSubscribe(clientId, message); + break; + case 'unsubscribe': + this.handleUnsubscribe(clientId, message); + break; + case 'index': + this.handleIndexRequest(clientId, message); + break; + case 'query': + this.handleQueryRequest(clientId, message); + break; + default: + this.sendError(clientId, `Unknown message type: ${message.type}`); + } + } catch (error: any) { + this.sendError(clientId, `Invalid message: ${error.message}`); + } + } + + private handleSubscribe(clientId: string, message: WebSocketMessage): void { + const topic = message.topic; + if (!topic) { + this.sendError(clientId, 'Topic is required for subscription'); + return; + } + + const clientSubs = this.subscriptions.get(clientId); + if (clientSubs) { + clientSubs.add(topic); + } + + this.sendToClient(clientId, { + type: 'subscribed', + topic, + data: { message: `Subscribed to ${topic}` }, + timestamp: new Date().toISOString() + }); + + logger.info(`Client ${clientId} subscribed to ${topic}`); + } + + private handleUnsubscribe(clientId: string, message: WebSocketMessage): void { + const topic = message.topic; + if (!topic) { + this.sendError(clientId, 'Topic is required for unsubscription'); + return; + } + + const clientSubs = this.subscriptions.get(clientId); + if (clientSubs) { + clientSubs.delete(topic); + } + + this.sendToClient(clientId, { + type: 'unsubscribed', + topic, + data: { message: `Unsubscribed from ${topic}` }, + timestamp: new Date().toISOString() + }); + + logger.info(`Client ${clientId} unsubscribed from ${topic}`); + } + + private async handleIndexRequest(clientId: string, message: WebSocketMessage): Promise { + try { + const { path, options } = message.data || {}; + + // Send progress updates + const progressInterval = setInterval(() => { + this.sendToClient(clientId, { + type: 'update', + topic: 'index-progress', + data: { status: 'indexing', progress: Math.random() * 100 }, + timestamp: new Date().toISOString() + }); + }, 1000); + + const result = await this.indexer.buildIndex(path || process.cwd(), options); + + clearInterval(progressInterval); + + this.sendToClient(clientId, { + type: 'data', + topic: 'index-result', + data: { + success: true, + filesIndexed: Object.keys(result.files).length, + statistics: result.statistics + }, + timestamp: new Date().toISOString() + }); + } catch (error: any) { + this.sendError(clientId, `Index failed: ${error.message}`); + } + } + + private async handleQueryRequest(clientId: string, message: WebSocketMessage): Promise { + try { + const { query, options } = message.data || {}; + + if (!query) { + this.sendError(clientId, 'Query is required'); + return; + } + + const results = await this.indexer.query(query, options); + + this.sendToClient(clientId, { + type: 'data', + topic: 'query-result', + data: results, + timestamp: new Date().toISOString() + }); + } catch (error: any) { + this.sendError(clientId, `Query failed: ${error.message}`); + } + } + + private handleDisconnect(clientId: string): void { + this.clients.delete(clientId); + this.subscriptions.delete(clientId); + logger.info(`WebSocket client disconnected: ${clientId}`); + } + + private setupIndexerListeners(): void { + // Note: Indexer doesn't extend EventEmitter + // Event handling would need to be implemented differently + // For now, this is a placeholder for future event handling + logger.info('WebSocket indexer listeners setup (events not implemented)'); + } + + private broadcast(topic: string, message: WebSocketResponse): void { + for (const [clientId, subscriptions] of this.subscriptions.entries()) { + if (subscriptions.has(topic)) { + this.sendToClient(clientId, message); + } + } + } + + private sendToClient(clientId: string, message: WebSocketResponse): void { + const client = this.clients.get(clientId); + if (client && client.readyState === WebSocket.OPEN) { + client.send(JSON.stringify(message)); + } + } + + private sendError(clientId: string, error: string): void { + this.sendToClient(clientId, { + type: 'error', + error, + timestamp: new Date().toISOString() + }); + } + + private generateClientId(): string { + return `client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + } + + public getConnectedClients(): number { + return this.clients.size; + } + + public getSubscriptions(): Map> { + return this.subscriptions; + } } \ No newline at end of file diff --git a/src/cli/chat-commands.ts b/src/cli/chat-commands.ts index da2e5bc..e18c08f 100644 --- a/src/cli/chat-commands.ts +++ b/src/cli/chat-commands.ts @@ -1,199 +1,201 @@ -#!/usr/bin/env node -/** - * Enhanced CLI with Claude Code Chat Integration - * Adds interactive natural language chat capabilities - */ - -import { program } from 'commander'; -import * as path from 'path'; -import * as fs from 'fs'; -import logger from '../../utils/logger'; - -// Add chat command -program - .command('chat') - .description('Start interactive Claude Code chat session') - .option('--mode ', 'Chat mode: interactive|cli|web', 'interactive') - .option('--context', 'Pre-load project context', true) - .option('--stream', 'Enable response streaming', true) - .action(async (options: { mode: string; context: boolean; stream: boolean }) => { - try { - const { ClaudeChat, ClaudeCodeCLIIntegration } = await import('../cli/claude-chat'); - - switch (options.mode) { - case 'interactive': - // Built-in interactive chat - const chat = new ClaudeChat(); - await chat.start(); - break; - - case 'cli': - // Launch official Claude Code CLI with context - await ClaudeCodeCLIIntegration.launchWithContext(process.cwd()); - break; - - case 'web': - // Start web-based chat interface - logger.info('Starting web chat interface...'); - const { WebChatServer } = await import('../api/web-chat'); - const server = new WebChatServer(); - await server.start(); - logger.info('Chat interface available at: http://localhost:3000'); - break; - - default: - logger.error('Invalid mode. Choose: interactive, cli, or web'); - } - } catch (error) { - logger.error('Error starting chat:', error); - process.exit(1); - } - }); - -// Add Claude command as alias -program - .command('claude') - .description('Interact with Claude Code (alias for chat)') - .option('--query ', 'One-shot query without entering chat mode') - .option('--file ', 'Ask about a specific file') - .option('--function ', 'Ask about a specific function') - .action(async (options: { query?: string; file?: string; function?: string }) => { - try { - if (options.query || options.file || options.function) { - // One-shot query mode - const { ClaudeQueryRunner } = await import('../cli/claude-query'); - const runner = new ClaudeQueryRunner(); - - let query = options.query || ''; - if (options.file) { - query = `Explain the file ${options.file}. ${query}`; - } - if (options.function) { - query = `Explain the function ${options.function}. ${query}`; - } - - const response = await runner.runQuery(query); - logger.info(response); - } else { - // Interactive chat mode - const { ClaudeChat } = await import('../cli/claude-chat'); - const chat = new ClaudeChat(); - await chat.start(); - } - } catch (error) { - logger.error('Error with Claude interaction:', error); - process.exit(1); - } - }); - -// Add AI command for non-interactive analysis -program - .command('ai ') - .description('AI-powered code analysis (bugs|security|refactor|tests)') - .option('--files ', 'Specific files to analyze') - .option('--stream', 'Stream results in real-time', false) - .option('--format ', 'Output format: json|markdown|html', 'json') - .action(async (action: string, options: any) => { - try { - const { AIRunner } = await import('../cli/ai-runner'); - const runner = new AIRunner(); - - const validActions = ['bugs', 'security', 'refactor', 'tests', 'analyze']; - if (!validActions.includes(action)) { - logger.error(`Invalid action. Choose: ${validActions.join(', ')}`); - process.exit(1); - } - - // Start API server if not running - const apiUrl = 'http://localhost:4000'; - - // Check if API is running - try { - await fetch(`${apiUrl}/api/health`); - } catch { - logger.info('Starting API server...'); - const { spawn } = await import('child_process'); - spawn('node', [path.join(__dirname, '../cli/index.js'), 'api'], { - detached: true, - stdio: 'ignore' - }).unref(); - - // Wait for server to start - await new Promise(resolve => setTimeout(resolve, 3000)); - } - - // Run analysis - const endpoint = `/api/ai/${action === 'bugs' ? 'predict-bugs' : - action === 'security' ? 'analyze-security' : - action === 'refactor' ? 'suggest-refactoring' : - action === 'tests' ? 'generate-tests' : 'analyze'}`; - - if (options.stream) { - // Use SSE for streaming - const EventSource = await import('eventsource').then(m => m.default); - const eventSource = new EventSource(`${apiUrl}/api/stream/analyze`); - - eventSource.onmessage = (event: any) => { - const data = JSON.parse(event.data); - logger.info(data); - }; - - eventSource.onerror = (error: any) => { - logger.error('Stream error:', error); - eventSource.close(); - }; - } else { - // Regular HTTP request - const response = await fetch(`${apiUrl}${endpoint}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - files: options.files?.split(',') - }) - }); - - const result = await response.json(); - - if (options.format === 'markdown') { - const { formatAsMarkdown } = await import('../utils/formatter'); - logger.info(formatAsMarkdown(result)); - } else if (options.format === 'html') { - const { formatAsHTML } = await import('../utils/formatter'); - const html = formatAsHTML(result); - const htmlFile = `analysis-${Date.now()}.html`; - fs.writeFileSync(htmlFile, html); - logger.info(`Results saved to ${htmlFile}`); - } else { - logger.info(JSON.stringify(result, null, 2)); - } - } - - } catch (error) { - logger.error('Error with AI analysis:', error); - process.exit(1); - } - }); - -// Add ask command for quick questions -program - .command('ask ') - .description('Ask Claude a quick question about your code') - .option('--context', 'Include full project context', true) - .action(async (question: string[], options: { context: boolean }) => { - try { - const { ClaudeQueryRunner } = await import('../cli/claude-query'); - const runner = new ClaudeQueryRunner(); - - const query = question.join(' '); - logger.info(`\nAsking Claude: "${query}"\n`); - - const response = await runner.runQuery(query, { - includeContext: options.context - }); - - logger.info(response); - } catch (error) { - logger.error('Error:', error); - process.exit(1); - } - }); +#!/usr/bin/env node +/** + * Enhanced CLI with Claude Code Chat Integration + * Adds interactive natural language chat capabilities + */ + +import { program } from 'commander'; +import * as path from 'path'; +import * as fs from 'fs'; +import logger from '../utils/logger'; + +// Add chat command +program + .command('chat') + .description('Start interactive Claude Code chat session') + .option('--mode ', 'Chat mode: interactive|cli|web', 'interactive') + .option('--context', 'Pre-load project context', true) + .option('--stream', 'Enable response streaming', true) + .action(async (options: { mode: string; context: boolean; stream: boolean }) => { + try { + const { ClaudeChat, ClaudeCodeCLIIntegration } = await import('../cli/claude-chat'); + + switch (options.mode) { + case 'interactive': + // Built-in interactive chat + const chat = new ClaudeChat(); + await chat.start(); + break; + + case 'cli': + // Launch official Claude Code CLI with context + await ClaudeCodeCLIIntegration.launchWithContext(process.cwd()); + break; + + case 'web': + // Start web-based chat interface + logger.info('Starting web chat interface...'); + const { WebChatServer } = await import('../api/web-chat'); + const server = new WebChatServer(); + await server.start(); + logger.info('Chat interface available at: http://localhost:3000'); + break; + + default: + logger.error('Invalid mode. Choose: interactive, cli, or web'); + } + } catch (error) { + logger.error('Error starting chat:', error); + process.exit(1); + } + }); + +// Add Claude command as alias +program + .command('claude') + .description('Interact with Claude Code (alias for chat)') + .option('--query ', 'One-shot query without entering chat mode') + .option('--file ', 'Ask about a specific file') + .option('--function ', 'Ask about a specific function') + .action(async (options: { query?: string; file?: string; function?: string }) => { + try { + if (options.query || options.file || options.function) { + // One-shot query mode + const { ClaudeQueryRunner } = await import('../cli/claude-query'); + const runner = new ClaudeQueryRunner(); + + let query = options.query || ''; + if (options.file) { + query = `Explain the file ${options.file}. ${query}`; + } + if (options.function) { + query = `Explain the function ${options.function}. ${query}`; + } + + const response = await runner.runQuery(query); + logger.info(response); + } else { + // Interactive chat mode + const { ClaudeChat } = await import('../cli/claude-chat'); + const chat = new ClaudeChat(); + await chat.start(); + } + } catch (error) { + logger.error('Error with Claude interaction:', error); + process.exit(1); + } + }); + +// Add AI command for non-interactive analysis +program + .command('ai ') + .description('AI-powered code analysis (bugs|security|refactor|tests)') + .option('--files ', 'Specific files to analyze') + .option('--stream', 'Stream results in real-time', false) + .option('--format ', 'Output format: json|markdown|html', 'json') + .action(async (action: string, options: any) => { + try { + // const { AIRunner } = await import('../cli/ai-runner'); // Commented out - missing file + // const runner = new AIRunner(); // Commented out - missing class + + const validActions = ['bugs', 'security', 'refactor', 'tests', 'analyze']; + if (!validActions.includes(action)) { + logger.error(`Invalid action. Choose: ${validActions.join(', ')}`); + process.exit(1); + } + + // Start API server if not running + const apiUrl = 'http://localhost:4000'; + + // Check if API is running + try { + await fetch(`${apiUrl}/api/health`); + } catch { + logger.info('Starting API server...'); + const { spawn } = await import('child_process'); + spawn('node', [path.join(__dirname, '../cli/index.js'), 'api'], { + detached: true, + stdio: 'ignore' + }).unref(); + + // Wait for server to start + await new Promise(resolve => setTimeout(resolve, 3000)); + } + + // Run analysis + const endpoint = `/api/ai/${action === 'bugs' ? 'predict-bugs' : + action === 'security' ? 'analyze-security' : + action === 'refactor' ? 'suggest-refactoring' : + action === 'tests' ? 'generate-tests' : 'analyze'}`; + + if (options.stream) { + // Use SSE for streaming + // const EventSource = await import('eventsource').then(m => m.default); // Commented out - missing dependency + // const eventSource = new EventSource(`${apiUrl}/api/stream/analyze`); // Commented out - missing EventSource + + // eventSource.onmessage = (event: any) => { + // const data = JSON.parse(event.data); + // logger.info(data); + // }; + + // eventSource.onerror = (error: any) => { + // logger.error('Stream error:', error); + // eventSource.close(); + // }; + + logger.warn('Streaming not available - missing EventSource dependency'); + } else { + // Regular HTTP request + const response = await fetch(`${apiUrl}${endpoint}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + files: options.files?.split(',') + }) + }); + + const result = await response.json(); + + if (options.format === 'markdown') { + // const { formatAsMarkdown } = await import('../utils/formatter'); // Missing function + logger.info(JSON.stringify(result, null, 2)); // Fallback to JSON + } else if (options.format === 'html') { + // const { formatAsHTML } = await import('../utils/formatter'); // Missing function + const html = JSON.stringify(result, null, 2); // Fallback to JSON + const htmlFile = `analysis-${Date.now()}.html`; + fs.writeFileSync(htmlFile, html); + logger.info(`Results saved to ${htmlFile}`); + } else { + logger.info(JSON.stringify(result, null, 2)); + } + } + + } catch (error) { + logger.error('Error with AI analysis:', error); + process.exit(1); + } + }); + +// Add ask command for quick questions +program + .command('ask ') + .description('Ask Claude a quick question about your code') + .option('--context', 'Include full project context', true) + .action(async (question: string[], options: { context: boolean }) => { + try { + const { ClaudeQueryRunner } = await import('../cli/claude-query'); + const runner = new ClaudeQueryRunner(); + + const query = question.join(' '); + logger.info(`\nAsking Claude: "${query}"\n`); + + const response = await runner.runQuery(query, { + includeContext: options.context + }); + + logger.info(response); + } catch (error) { + logger.error('Error:', error); + process.exit(1); + } + }); diff --git a/src/cli/claude-chat.ts b/src/cli/claude-chat.ts index b94895f..1bff36e 100644 --- a/src/cli/claude-chat.ts +++ b/src/cli/claude-chat.ts @@ -1,508 +1,508 @@ -/** - * Interactive Claude Code Chat Interface - * Provides natural language conversation with Claude about your codebase - */ - -import * as readline from 'readline'; -import * as chalk from 'chalk'; -import { query } from '@anthropic-ai/claude-code'; -import { ProjectIndex } from '../types'; -import { Indexer } from '../core/indexer'; -import { ConfigLoader } from '../core/config'; -import * as fs from 'fs'; -import * as path from 'path'; -import { execSync } from 'child_process'; -import logger from '../../utils/logger'; - -export class ClaudeChat { - private rl: readline.Interface; - private sessionId?: string; - private conversationHistory: Array<{ role: string; content: string }> = []; - private projectIndex: ProjectIndex | null = null; - private indexer: Indexer; - private config: ConfigLoader; - private apiKey: string; - private model: string; - private contextFile: string; - private isFirstMessage: boolean = true; - - constructor() { - this.config = new ConfigLoader(); - this.indexer = new Indexer(this.config); - this.apiKey = process.env.ANTHROPIC_API_KEY || ''; - this.model = process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805'; - this.contextFile = path.join(process.cwd(), '.indexer-output/current/index.json'); - - this.rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - prompt: chalk.cyan('You: ') - }); - - // Check API key - if (!this.apiKey) { - logger.error(chalk.red('❌ ANTHROPIC_API_KEY not set. Please configure it in .env.local')); - process.exit(1); - } - } - - /** - * Start the interactive chat session - */ - async start(): Promise { - // Load or build project index - await this.loadProjectContext(); - - // Show welcome message - this.showWelcome(); - - // Start conversation loop - this.rl.prompt(); - - this.rl.on('line', async (input) => { - const trimmedInput = input.trim(); - - // Handle special commands - if (trimmedInput.startsWith('/')) { - await this.handleCommand(trimmedInput); - this.rl.prompt(); - return; - } - - // Exit conditions - if (['exit', 'quit', 'bye'].includes(trimmedInput.toLowerCase())) { - this.shutdown(); - return; - } - - // Process with Claude - await this.processWithClaude(trimmedInput); - this.rl.prompt(); - }); - - this.rl.on('close', () => { - this.shutdown(); - }); - } - - /** - * Load project context from index - */ - private async loadProjectContext(): Promise { - logger.info(chalk.gray('Loading project context...')); - - try { - // Try to load existing index - if (fs.existsSync(this.contextFile)) { - const indexContent = fs.readFileSync(this.contextFile, 'utf-8'); - this.projectIndex = JSON.parse(indexContent); - logger.info(chalk.green('✓ Loaded existing project index')); - } else { - // Build new index - logger.info(chalk.yellow('Building project index...')); - this.projectIndex = await this.indexer.buildIndex(process.cwd()); - - // Save for future use - fs.mkdirSync(path.dirname(this.contextFile), { recursive: true }); - fs.writeFileSync(this.contextFile, JSON.stringify(this.projectIndex, null, 2)); - logger.info(chalk.green('✓ Project indexed successfully')); - } - - // Display project stats - if (this.projectIndex?.statistics) { - const stats = this.projectIndex.statistics; - logger.info(chalk.gray(` Files: ${stats.totalFiles} | Functions: ${stats.totalFunctions} | Classes: ${stats.totalClasses}`)); - } - } catch (error) { - logger.error(chalk.red('Failed to load project context:'), error); - // Continue without index - } - } - - /** - * Show welcome message - */ - private showWelcome(): void { - console.clear(); - logger.info(chalk.bold.cyan('\n╔════════════════════════════════════════╗')); - logger.info(chalk.bold.cyan('║ Claude Code Chat - Interactive ║')); - logger.info(chalk.bold.cyan('╚════════════════════════════════════════╝\n')); - - logger.info(chalk.gray('I have access to your entire codebase and can help with:')); - logger.info(chalk.gray(' • Code analysis and explanation')); - logger.info(chalk.gray(' • Bug detection and fixes')); - logger.info(chalk.gray(' • Refactoring suggestions')); - logger.info(chalk.gray(' • Test generation')); - logger.info(chalk.gray(' • Architecture questions\n')); - - logger.info(chalk.yellow('Commands:')); - logger.info(chalk.gray(' /refresh - Rebuild project index')); - logger.info(chalk.gray(' /clear - Clear conversation history')); - logger.info(chalk.gray(' /save - Save conversation to file')); - logger.info(chalk.gray(' /mode - Switch between chat/code/debug modes')); - logger.info(chalk.gray(' /help - Show all commands')); - logger.info(chalk.gray(' exit - Quit the chat\n')); - - logger.info(chalk.green('💡 Ask me anything about your code!\n')); - } - - /** - * Process user input with Claude - */ - private async processWithClaude(userInput: string): Promise { - // Show thinking indicator - const thinkingIndicator = this.startThinkingAnimation(); - - try { - // Build context-aware prompt - const prompt = this.buildContextualPrompt(userInput); - - // Add to conversation history - this.conversationHistory.push({ role: 'user', content: userInput }); - - // Query Claude with streaming - let response = ''; - let firstChunk = true; - - for await (const message of query({ - prompt, - options: { - model: this.model, - systemPrompt: this.buildSystemPrompt(), - resumeSessionId: this.sessionId, - maxTurns: 1, - permissionMode: 'plan', - allowedTools: ['Read', 'Grep', 'Write', 'Execute'], - streaming: true - } - })) { - // Clear thinking indicator on first response - if (firstChunk) { - this.stopThinkingAnimation(thinkingIndicator); - logger.info(chalk.bold.green('\nClaude: ')); - firstChunk = false; - } - - if (message.type === 'assistant' && message.content) { - // Stream response to console - process.stdout.write(chalk.white(message.content)); - response += message.content; - } - - if (message.type === 'result') { - if (message.session_id) { - this.sessionId = message.session_id; - } - response = message.result || response; - } - } - - logger.info('\n'); - - // Add response to history - this.conversationHistory.push({ role: 'assistant', content: response }); - - } catch (error) { - this.stopThinkingAnimation(thinkingIndicator); - logger.error(chalk.red('\n❌ Error:'), error instanceof Error ? error.message : error); - } - } - - /** - * Build contextual prompt with project information - */ - private buildContextualPrompt(userInput: string): string { - let prompt = ''; - - // Include project context on first message - if (this.isFirstMessage && this.projectIndex) { - prompt += `Project Context:\n`; - prompt += `- Total Files: ${this.projectIndex.statistics.totalFiles}\n`; - prompt += `- Languages: ${Object.keys(this.projectIndex.statistics.languages || {}).join(', ')}\n`; - prompt += `- Functions: ${this.projectIndex.statistics.totalFunctions}\n`; - prompt += `- Classes: ${this.projectIndex.statistics.totalClasses}\n\n`; - - // Include relevant file information based on query - const relevantFiles = this.findRelevantFiles(userInput); - if (relevantFiles.length > 0) { - prompt += `Relevant files for this query:\n`; - relevantFiles.slice(0, 10).forEach(file => { - prompt += `- ${file}\n`; - }); - prompt += '\n'; - } - - this.isFirstMessage = false; - } - - // Include conversation history - if (this.conversationHistory.length > 0) { - prompt += 'Previous conversation:\n'; - this.conversationHistory.slice(-4).forEach(msg => { - prompt += `${msg.role}: ${msg.content.substring(0, 200)}...\n`; - }); - prompt += '\n'; - } - - prompt += `User: ${userInput}`; - - return prompt; - } - - /** - * Build system prompt for Claude - */ - private buildSystemPrompt(): string { - return `You are Claude Code, an expert programming assistant with access to the user's entire codebase. - -You have been provided with a comprehensive index of the project including: -- All functions, classes, and their relationships -- Import/export dependencies -- File structure and organization -- Code complexity metrics - -Your capabilities: -1. Answer questions about the codebase architecture -2. Find and explain specific code implementations -3. Identify bugs and suggest fixes -4. Generate tests for functions -5. Suggest refactoring improvements -6. Explain complex code sections -7. Help with debugging issues - -Always be specific and reference actual files and line numbers when possible. -Provide code examples and be practical in your suggestions. -If you need to see specific file contents, you can read them directly.`; - } - - /** - * Find files relevant to the user's query - */ - private findRelevantFiles(query: string): string[] { - if (!this.projectIndex) return []; - - const relevant: string[] = []; - const queryLower = query.toLowerCase(); - const keywords = queryLower.split(/\s+/).filter(w => w.length > 3); - - for (const [filePath, fileData] of Object.entries(this.projectIndex.files)) { - // Check if file name matches - if (keywords.some(kw => filePath.toLowerCase().includes(kw))) { - relevant.push(filePath); - continue; - } - - // Check functions - for (const func of fileData.functions) { - const funcName = typeof func === 'string' ? func : func.name; - if (keywords.some(kw => funcName.toLowerCase().includes(kw))) { - relevant.push(filePath); - break; - } - } - - // Check classes - for (const cls of fileData.classes) { - const className = typeof cls === 'string' ? cls : cls.name; - if (keywords.some(kw => className.toLowerCase().includes(kw))) { - relevant.push(filePath); - break; - } - } - } - - return [...new Set(relevant)]; - } - - /** - * Handle special commands - */ - private async handleCommand(command: string): Promise { - const cmd = command.toLowerCase(); - - switch (cmd) { - case '/refresh': - logger.info(chalk.yellow('Rebuilding project index...')); - this.projectIndex = await this.indexer.buildIndex(process.cwd()); - fs.writeFileSync(this.contextFile, JSON.stringify(this.projectIndex, null, 2)); - logger.info(chalk.green('✓ Index refreshed')); - break; - - case '/clear': - this.conversationHistory = []; - this.sessionId = undefined; - console.clear(); - this.showWelcome(); - break; - - case '/save': - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const conversationFile = `conversation-${timestamp}.md`; - let content = '# Claude Code Chat Conversation\n\n'; - - this.conversationHistory.forEach(msg => { - content += `## ${msg.role === 'user' ? 'You' : 'Claude'}\n\n`; - content += `${msg.content}\n\n`; - }); - - fs.writeFileSync(conversationFile, content); - logger.info(chalk.green(`✓ Conversation saved to ${conversationFile}`)); - break; - - case '/mode': - logger.info(chalk.yellow('Available modes:')); - logger.info(' • chat (default) - Natural conversation'); - logger.info(' • code - Code-focused responses'); - logger.info(' • debug - Debugging assistance'); - break; - - case '/stats': - if (this.projectIndex?.statistics) { - const stats = this.projectIndex.statistics; - logger.info(chalk.cyan('\nProject Statistics:')); - logger.info(` Files: ${stats.totalFiles}`); - logger.info(` Functions: ${stats.totalFunctions}`); - logger.info(` Classes: ${stats.totalClasses}`); - logger.info(` Languages: ${JSON.stringify(stats.languages, null, 2)}`); - } - break; - - case '/help': - logger.info(chalk.cyan('\nAvailable Commands:')); - logger.info(' /refresh - Rebuild project index'); - logger.info(' /clear - Clear conversation history'); - logger.info(' /save - Save conversation to file'); - logger.info(' /stats - Show project statistics'); - logger.info(' /mode - Switch conversation mode'); - logger.info(' /search - Search codebase'); - logger.info(' /explain - Explain a specific file'); - logger.info(' /help - Show this help message'); - break; - - default: - if (cmd.startsWith('/search ')) { - const searchTerm = command.substring(8); - const results = this.findRelevantFiles(searchTerm); - logger.info(chalk.cyan(`\nSearch results for "${searchTerm}":`)); - results.slice(0, 10).forEach(file => { - logger.info(` • ${file}`); - }); - if (results.length > 10) { - logger.info(chalk.gray(` ... and ${results.length - 10} more`)); - } - } else if (cmd.startsWith('/explain ')) { - const filePath = command.substring(9); - await this.processWithClaude(`Explain the purpose and key functions in ${filePath}`); - } else { - logger.info(chalk.red(`Unknown command: ${command}`)); - } - } - } - - /** - * Start thinking animation - */ - private startThinkingAnimation(): NodeJS.Timer { - const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; - let i = 0; - - return setInterval(() => { - process.stdout.write(`\r${chalk.cyan(frames[i])} ${chalk.gray('Claude is thinking...')}`); - i = (i + 1) % frames.length; - }, 100); - } - - /** - * Stop thinking animation - */ - private stopThinkingAnimation(timer: NodeJS.Timer): void { - clearInterval(timer); - process.stdout.write('\r' + ' '.repeat(30) + '\r'); // Clear the line - } - - /** - * Shutdown the chat interface - */ - private shutdown(): void { - logger.info(chalk.cyan('\n\nGoodbye! 👋\n')); - this.rl.close(); - process.exit(0); - } -} - -/** - * Alternative: Use the official Claude Code CLI directly - */ -export class ClaudeCodeCLIIntegration { - /** - * Launch Claude Code with project context - */ - static async launchWithContext(projectPath: string = process.cwd()): Promise { - try { - // Ensure index exists - const indexPath = path.join(projectPath, '.indexer-output/current/index.json'); - if (!fs.existsSync(indexPath)) { - logger.info(chalk.yellow('Building project index first...')); - const config = new ConfigLoader(); - const indexer = new Indexer(config); - const index = await indexer.buildIndex(projectPath); - - fs.mkdirSync(path.dirname(indexPath), { recursive: true }); - fs.writeFileSync(indexPath, JSON.stringify(index, null, 2)); - } - - // Set environment for Claude Code - process.env.CLAUDE_PROJECT_ROOT = projectPath; - process.env.CLAUDE_PROJECT_INDEX = indexPath; - - // Launch Claude Code CLI - logger.info(chalk.cyan('Launching Claude Code with project context...\n')); - - execSync('claude chat', { - stdio: 'inherit', - env: { - ...process.env, - ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY, - CLAUDE_PROJECT_ROOT: projectPath, - CLAUDE_PROJECT_INDEX: indexPath - } - }); - - } catch (error) { - if (error instanceof Error && error.message.includes('command not found')) { - logger.error(chalk.red('Claude Code CLI not found. Install it first:')); - logger.info(chalk.yellow(' npm install -g claude-code')); - } else { - logger.error(chalk.red('Error launching Claude Code:'), error); - } - } - } - - /** - * Create a custom Claude Code configuration - */ - static createConfiguration(projectPath: string): void { - const config = { - projectRoot: projectPath, - indexPath: path.join(projectPath, '.indexer-output/current/index.json'), - tools: { - read: true, - write: true, - execute: true, - search: true - }, - context: { - autoLoad: true, - includeIndex: true, - maxFiles: 100 - }, - model: process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805', - streaming: true - }; - - const configPath = path.join(projectPath, '.claude-code.json'); - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - - logger.info(chalk.green(`✓ Created Claude Code configuration at ${configPath}`)); - } -} +/** + * Interactive Claude Code Chat Interface + * Provides natural language conversation with Claude about your codebase + */ + +import * as readline from 'readline'; +import * as chalk from 'chalk'; +import { query } from '@anthropic-ai/claude-code'; +import { ProjectIndex } from '../types'; +import { Indexer } from '../core/indexer'; +import { ConfigLoader } from '../core/config'; +import * as fs from 'fs'; +import * as path from 'path'; +import { execSync } from 'child_process'; +import logger from '../utils/logger'; + +export class ClaudeChat { + private rl: readline.Interface; + private sessionId?: string; + private conversationHistory: Array<{ role: string; content: string }> = []; + private projectIndex: ProjectIndex | null = null; + private indexer: Indexer; + private config: ConfigLoader; + private apiKey: string; + private model: string; + private contextFile: string; + private isFirstMessage: boolean = true; + + constructor() { + this.config = new ConfigLoader(); + this.indexer = new Indexer(this.config); + this.apiKey = process.env.ANTHROPIC_API_KEY || ''; + this.model = process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805'; + this.contextFile = path.join(process.cwd(), '.indexer-output/current/index.json'); + + this.rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + prompt: chalk.cyan('You: ') + }); + + // Check API key + if (!this.apiKey) { + logger.error(chalk.red('❌ ANTHROPIC_API_KEY not set. Please configure it in .env.local')); + process.exit(1); + } + } + + /** + * Start the interactive chat session + */ + async start(): Promise { + // Load or build project index + await this.loadProjectContext(); + + // Show welcome message + this.showWelcome(); + + // Start conversation loop + this.rl.prompt(); + + this.rl.on('line', async (input) => { + const trimmedInput = input.trim(); + + // Handle special commands + if (trimmedInput.startsWith('/')) { + await this.handleCommand(trimmedInput); + this.rl.prompt(); + return; + } + + // Exit conditions + if (['exit', 'quit', 'bye'].includes(trimmedInput.toLowerCase())) { + this.shutdown(); + return; + } + + // Process with Claude + await this.processWithClaude(trimmedInput); + this.rl.prompt(); + }); + + this.rl.on('close', () => { + this.shutdown(); + }); + } + + /** + * Load project context from index + */ + private async loadProjectContext(): Promise { + logger.info(chalk.gray('Loading project context...')); + + try { + // Try to load existing index + if (fs.existsSync(this.contextFile)) { + const indexContent = fs.readFileSync(this.contextFile, 'utf-8'); + this.projectIndex = JSON.parse(indexContent); + logger.info(chalk.green('✓ Loaded existing project index')); + } else { + // Build new index + logger.info(chalk.yellow('Building project index...')); + this.projectIndex = await this.indexer.buildIndex(process.cwd()); + + // Save for future use + fs.mkdirSync(path.dirname(this.contextFile), { recursive: true }); + fs.writeFileSync(this.contextFile, JSON.stringify(this.projectIndex, null, 2)); + logger.info(chalk.green('✓ Project indexed successfully')); + } + + // Display project stats + if (this.projectIndex?.statistics) { + const stats = this.projectIndex.statistics; + logger.info(chalk.gray(` Files: ${stats.totalFiles} | Functions: ${stats.totalFunctions} | Classes: ${stats.totalClasses}`)); + } + } catch (error) { + logger.error(chalk.red('Failed to load project context:'), error); + // Continue without index + } + } + + /** + * Show welcome message + */ + private showWelcome(): void { + console.clear(); + logger.info(chalk.bold.cyan('\n╔════════════════════════════════════════╗')); + logger.info(chalk.bold.cyan('║ Claude Code Chat - Interactive ║')); + logger.info(chalk.bold.cyan('╚════════════════════════════════════════╝\n')); + + logger.info(chalk.gray('I have access to your entire codebase and can help with:')); + logger.info(chalk.gray(' • Code analysis and explanation')); + logger.info(chalk.gray(' • Bug detection and fixes')); + logger.info(chalk.gray(' • Refactoring suggestions')); + logger.info(chalk.gray(' • Test generation')); + logger.info(chalk.gray(' • Architecture questions\n')); + + logger.info(chalk.yellow('Commands:')); + logger.info(chalk.gray(' /refresh - Rebuild project index')); + logger.info(chalk.gray(' /clear - Clear conversation history')); + logger.info(chalk.gray(' /save - Save conversation to file')); + logger.info(chalk.gray(' /mode - Switch between chat/code/debug modes')); + logger.info(chalk.gray(' /help - Show all commands')); + logger.info(chalk.gray(' exit - Quit the chat\n')); + + logger.info(chalk.green('💡 Ask me anything about your code!\n')); + } + + /** + * Process user input with Claude + */ + private async processWithClaude(userInput: string): Promise { + // Show thinking indicator + const thinkingIndicator = this.startThinkingAnimation(); + + try { + // Build context-aware prompt + const prompt = this.buildContextualPrompt(userInput); + + // Add to conversation history + this.conversationHistory.push({ role: 'user', content: userInput }); + + // Query Claude with streaming + let response = ''; + let firstChunk = true; + + for await (const message of query({ + prompt, + options: { + model: this.model, + // systemPrompt: this.buildSystemPrompt(), // Not available in this version + // resumeSessionId: this.sessionId, // Not available in this version + maxTurns: 1, + permissionMode: 'plan', + allowedTools: ['Read', 'Grep', 'Write', 'Execute'] + // streaming: true // Not available in this version + } + })) { + // Clear thinking indicator on first response + if (firstChunk) { + this.stopThinkingAnimation(thinkingIndicator); + logger.info(chalk.bold.green('\nClaude: ')); + firstChunk = false; + } + + if (message.type === 'assistant' && (message as any).content) { + // Stream response to console + process.stdout.write(chalk.white((message as any).content)); + response += (message as any).content; + } + + if (message.type === 'result') { + if (message.session_id) { + this.sessionId = message.session_id; + } + response = (message as any).result || response; + } + } + + logger.info('\n'); + + // Add response to history + this.conversationHistory.push({ role: 'assistant', content: response }); + + } catch (error) { + this.stopThinkingAnimation(thinkingIndicator); + logger.error(chalk.red('\n❌ Error:'), error instanceof Error ? error.message : error); + } + } + + /** + * Build contextual prompt with project information + */ + private buildContextualPrompt(userInput: string): string { + let prompt = ''; + + // Include project context on first message + if (this.isFirstMessage && this.projectIndex) { + prompt += `Project Context:\n`; + prompt += `- Total Files: ${this.projectIndex.statistics.totalFiles}\n`; + prompt += `- Languages: ${Object.keys(this.projectIndex.statistics.languages || {}).join(', ')}\n`; + prompt += `- Functions: ${this.projectIndex.statistics.totalFunctions}\n`; + prompt += `- Classes: ${this.projectIndex.statistics.totalClasses}\n\n`; + + // Include relevant file information based on query + const relevantFiles = this.findRelevantFiles(userInput); + if (relevantFiles.length > 0) { + prompt += `Relevant files for this query:\n`; + relevantFiles.slice(0, 10).forEach(file => { + prompt += `- ${file}\n`; + }); + prompt += '\n'; + } + + this.isFirstMessage = false; + } + + // Include conversation history + if (this.conversationHistory.length > 0) { + prompt += 'Previous conversation:\n'; + this.conversationHistory.slice(-4).forEach(msg => { + prompt += `${msg.role}: ${msg.content.substring(0, 200)}...\n`; + }); + prompt += '\n'; + } + + prompt += `User: ${userInput}`; + + return prompt; + } + + /** + * Build system prompt for Claude + */ + private buildSystemPrompt(): string { + return `You are Claude Code, an expert programming assistant with access to the user's entire codebase. + +You have been provided with a comprehensive index of the project including: +- All functions, classes, and their relationships +- Import/export dependencies +- File structure and organization +- Code complexity metrics + +Your capabilities: +1. Answer questions about the codebase architecture +2. Find and explain specific code implementations +3. Identify bugs and suggest fixes +4. Generate tests for functions +5. Suggest refactoring improvements +6. Explain complex code sections +7. Help with debugging issues + +Always be specific and reference actual files and line numbers when possible. +Provide code examples and be practical in your suggestions. +If you need to see specific file contents, you can read them directly.`; + } + + /** + * Find files relevant to the user's query + */ + private findRelevantFiles(query: string): string[] { + if (!this.projectIndex) return []; + + const relevant: string[] = []; + const queryLower = query.toLowerCase(); + const keywords = queryLower.split(/\s+/).filter(w => w.length > 3); + + for (const [filePath, fileData] of Object.entries(this.projectIndex.files)) { + // Check if file name matches + if (keywords.some(kw => filePath.toLowerCase().includes(kw))) { + relevant.push(filePath); + continue; + } + + // Check functions + for (const func of fileData.functions) { + const funcName = typeof func === 'string' ? func : func.name; + if (keywords.some(kw => funcName.toLowerCase().includes(kw))) { + relevant.push(filePath); + break; + } + } + + // Check classes + for (const cls of fileData.classes) { + const className = typeof cls === 'string' ? cls : cls.name; + if (keywords.some(kw => className.toLowerCase().includes(kw))) { + relevant.push(filePath); + break; + } + } + } + + return [...new Set(relevant)]; + } + + /** + * Handle special commands + */ + private async handleCommand(command: string): Promise { + const cmd = command.toLowerCase(); + + switch (cmd) { + case '/refresh': + logger.info(chalk.yellow('Rebuilding project index...')); + this.projectIndex = await this.indexer.buildIndex(process.cwd()); + fs.writeFileSync(this.contextFile, JSON.stringify(this.projectIndex, null, 2)); + logger.info(chalk.green('✓ Index refreshed')); + break; + + case '/clear': + this.conversationHistory = []; + this.sessionId = undefined; + console.clear(); + this.showWelcome(); + break; + + case '/save': + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const conversationFile = `conversation-${timestamp}.md`; + let content = '# Claude Code Chat Conversation\n\n'; + + this.conversationHistory.forEach(msg => { + content += `## ${msg.role === 'user' ? 'You' : 'Claude'}\n\n`; + content += `${msg.content}\n\n`; + }); + + fs.writeFileSync(conversationFile, content); + logger.info(chalk.green(`✓ Conversation saved to ${conversationFile}`)); + break; + + case '/mode': + logger.info(chalk.yellow('Available modes:')); + logger.info(' • chat (default) - Natural conversation'); + logger.info(' • code - Code-focused responses'); + logger.info(' • debug - Debugging assistance'); + break; + + case '/stats': + if (this.projectIndex?.statistics) { + const stats = this.projectIndex.statistics; + logger.info(chalk.cyan('\nProject Statistics:')); + logger.info(` Files: ${stats.totalFiles}`); + logger.info(` Functions: ${stats.totalFunctions}`); + logger.info(` Classes: ${stats.totalClasses}`); + logger.info(` Languages: ${JSON.stringify(stats.languages, null, 2)}`); + } + break; + + case '/help': + logger.info(chalk.cyan('\nAvailable Commands:')); + logger.info(' /refresh - Rebuild project index'); + logger.info(' /clear - Clear conversation history'); + logger.info(' /save - Save conversation to file'); + logger.info(' /stats - Show project statistics'); + logger.info(' /mode - Switch conversation mode'); + logger.info(' /search - Search codebase'); + logger.info(' /explain - Explain a specific file'); + logger.info(' /help - Show this help message'); + break; + + default: + if (cmd.startsWith('/search ')) { + const searchTerm = command.substring(8); + const results = this.findRelevantFiles(searchTerm); + logger.info(chalk.cyan(`\nSearch results for "${searchTerm}":`)); + results.slice(0, 10).forEach(file => { + logger.info(` • ${file}`); + }); + if (results.length > 10) { + logger.info(chalk.gray(` ... and ${results.length - 10} more`)); + } + } else if (cmd.startsWith('/explain ')) { + const filePath = command.substring(9); + await this.processWithClaude(`Explain the purpose and key functions in ${filePath}`); + } else { + logger.info(chalk.red(`Unknown command: ${command}`)); + } + } + } + + /** + * Start thinking animation + */ + private startThinkingAnimation(): NodeJS.Timer { + const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; + let i = 0; + + return setInterval(() => { + process.stdout.write(`\r${chalk.cyan(frames[i])} ${chalk.gray('Claude is thinking...')}`); + i = (i + 1) % frames.length; + }, 100); + } + + /** + * Stop thinking animation + */ + private stopThinkingAnimation(timer: NodeJS.Timer): void { + clearInterval(timer as any); + process.stdout.write('\r' + ' '.repeat(30) + '\r'); // Clear the line + } + + /** + * Shutdown the chat interface + */ + private shutdown(): void { + logger.info(chalk.cyan('\n\nGoodbye! 👋\n')); + this.rl.close(); + process.exit(0); + } +} + +/** + * Alternative: Use the official Claude Code CLI directly + */ +export class ClaudeCodeCLIIntegration { + /** + * Launch Claude Code with project context + */ + static async launchWithContext(projectPath: string = process.cwd()): Promise { + try { + // Ensure index exists + const indexPath = path.join(projectPath, '.indexer-output/current/index.json'); + if (!fs.existsSync(indexPath)) { + logger.info(chalk.yellow('Building project index first...')); + const config = new ConfigLoader(); + const indexer = new Indexer(config); + const index = await indexer.buildIndex(projectPath); + + fs.mkdirSync(path.dirname(indexPath), { recursive: true }); + fs.writeFileSync(indexPath, JSON.stringify(index, null, 2)); + } + + // Set environment for Claude Code + process.env.CLAUDE_PROJECT_ROOT = projectPath; + process.env.CLAUDE_PROJECT_INDEX = indexPath; + + // Launch Claude Code CLI + logger.info(chalk.cyan('Launching Claude Code with project context...\n')); + + execSync('claude chat', { + stdio: 'inherit', + env: { + ...process.env, + ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY, + CLAUDE_PROJECT_ROOT: projectPath, + CLAUDE_PROJECT_INDEX: indexPath + } + }); + + } catch (error) { + if (error instanceof Error && error.message.includes('command not found')) { + logger.error(chalk.red('Claude Code CLI not found. Install it first:')); + logger.info(chalk.yellow(' npm install -g claude-code')); + } else { + logger.error(chalk.red('Error launching Claude Code:'), error); + } + } + } + + /** + * Create a custom Claude Code configuration + */ + static createConfiguration(projectPath: string): void { + const config = { + projectRoot: projectPath, + indexPath: path.join(projectPath, '.indexer-output/current/index.json'), + tools: { + read: true, + write: true, + execute: true, + search: true + }, + context: { + autoLoad: true, + includeIndex: true, + maxFiles: 100 + }, + model: process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805', + streaming: true + }; + + const configPath = path.join(projectPath, '.claude-code.json'); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + + logger.info(chalk.green(`✓ Created Claude Code configuration at ${configPath}`)); + } +} diff --git a/src/cli/claude-query.ts b/src/cli/claude-query.ts index 4e67b9f..44c5fc6 100644 --- a/src/cli/claude-query.ts +++ b/src/cli/claude-query.ts @@ -1,126 +1,127 @@ -/** - * Quick query runner for one-shot Claude questions - * Used by 'indexer ask' and 'indexer claude --query' commands - */ - -import { query } from '@anthropic-ai/claude-code'; -import { ProjectIndex } from '../types'; -import { Indexer } from '../core/indexer'; -import { ConfigLoader } from '../core/config'; -import * as fs from 'fs'; -import * as path from 'path'; - -export class ClaudeQueryRunner { - private indexer: Indexer; - private config: ConfigLoader; - private apiKey: string; - private model: string; - private projectIndex: ProjectIndex | null = null; - - constructor() { - this.config = new ConfigLoader(); - this.indexer = new Indexer(this.config); - this.apiKey = process.env.ANTHROPIC_API_KEY || ''; - this.model = process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805'; - } - - /** - * Run a single query and return the response - */ - async runQuery(userQuery: string, options: QueryOptions = {}): Promise { - // Load project context if requested - if (options.includeContext !== false) { - await this.loadProjectContext(); - } - - // Build the prompt - const prompt = this.buildPrompt(userQuery, options); - - // Query Claude - let response = ''; - - for await (const message of query({ - prompt, - options: { - model: this.model, - customSystemPrompt: this.getSystemPrompt(), - maxTurns: 1, - permissionMode: 'plan', - allowedTools: options.allowTools ? ['Read', 'Grep', 'Write'] : ['Read', 'Grep'] - } - })) { - if (message.type === 'assistant') { - response += (message as any).message?.content || ''; - } - - if (message.type === 'tool_use') { - // Handle tool usage if needed - } - } - - return response; - } - - /** - * Load project index for context - */ - private async loadProjectContext(): Promise { - const indexPath = path.join(process.cwd(), '.indexer-output/current/index.json'); - - if (fs.existsSync(indexPath)) { - const content = fs.readFileSync(indexPath, 'utf-8'); - this.projectIndex = JSON.parse(content); - } else { - // Build index if it doesn't exist - this.projectIndex = await this.indexer.buildIndex(process.cwd()); - - // Save for future use - fs.mkdirSync(path.dirname(indexPath), { recursive: true }); - fs.writeFileSync(indexPath, JSON.stringify(this.projectIndex, null, 2)); - } - } - - /** - * Build prompt with optional context - */ - private buildPrompt(userQuery: string, options: QueryOptions): string { - let prompt = ''; - - if (this.projectIndex && options.includeContext !== false) { - const stats = this.projectIndex.statistics; - prompt += `Project Context:\n`; - prompt += `- Files: ${stats.totalFiles}\n`; - prompt += `- Functions: ${stats.totalFunctions}\n`; - prompt += `- Classes: ${stats.totalClasses}\n`; - prompt += `- Languages: ${Object.keys(stats.languages || {}).join(', ')}\n\n`; - } - - if (options.file) { - prompt += `Focus on file: ${options.file}\n\n`; - } - - if (options.function) { - prompt += `Focus on function: ${options.function}\n\n`; - } - - prompt += `Question: ${userQuery}`; - - return prompt; - } - - /** - * Get system prompt for Claude - */ - private getSystemPrompt(): string { - return `You are a helpful programming assistant with access to the user's codebase. -Be concise and specific in your responses. Reference actual files and functions when relevant. -Provide code examples when appropriate.`; - } -} - -interface QueryOptions { - includeContext?: boolean; - allowTools?: boolean; - file?: string; - function?: string; -} +/** + * Quick query runner for one-shot Claude questions + * Used by 'indexer ask' and 'indexer claude --query' commands + */ + +import { query } from '@anthropic-ai/claude-code'; +import { ProjectIndex } from '../types'; +import { Indexer } from '../core/indexer'; +import { ConfigLoader } from '../core/config'; +import * as fs from 'fs'; +import * as path from 'path'; + +export class ClaudeQueryRunner { + private indexer: Indexer; + private config: ConfigLoader; + private apiKey: string; + private model: string; + private projectIndex: ProjectIndex | null = null; + + constructor() { + this.config = new ConfigLoader(); + this.indexer = new Indexer(this.config); + this.apiKey = process.env.ANTHROPIC_API_KEY || ''; + this.model = process.env.CLAUDE_MODEL || 'claude-opus-4-1-20250805'; + } + + /** + * Run a single query and return the response + */ + async runQuery(userQuery: string, options: QueryOptions = {}): Promise { + // Load project context if requested + if (options.includeContext !== false) { + await this.loadProjectContext(); + } + + // Build the prompt + const prompt = this.buildPrompt(userQuery, options); + + // Query Claude + let response = ''; + + for await (const message of query({ + prompt, + options: { + model: this.model, + customSystemPrompt: this.getSystemPrompt(), + maxTurns: 1, + permissionMode: 'plan', + allowedTools: options.allowTools ? ['Read', 'Grep', 'Write'] : ['Read', 'Grep'] + } + })) { + if (message.type === 'assistant') { + response += (message as any).message?.content || ''; + } + + // Note: tool_use type is not available in this context + // if (message.type === 'tool_use') { + // // Handle tool usage if needed + // } + } + + return response; + } + + /** + * Load project index for context + */ + private async loadProjectContext(): Promise { + const indexPath = path.join(process.cwd(), '.indexer-output/current/index.json'); + + if (fs.existsSync(indexPath)) { + const content = fs.readFileSync(indexPath, 'utf-8'); + this.projectIndex = JSON.parse(content); + } else { + // Build index if it doesn't exist + this.projectIndex = await this.indexer.buildIndex(process.cwd()); + + // Save for future use + fs.mkdirSync(path.dirname(indexPath), { recursive: true }); + fs.writeFileSync(indexPath, JSON.stringify(this.projectIndex, null, 2)); + } + } + + /** + * Build prompt with optional context + */ + private buildPrompt(userQuery: string, options: QueryOptions): string { + let prompt = ''; + + if (this.projectIndex && options.includeContext !== false) { + const stats = this.projectIndex.statistics; + prompt += `Project Context:\n`; + prompt += `- Files: ${stats.totalFiles}\n`; + prompt += `- Functions: ${stats.totalFunctions}\n`; + prompt += `- Classes: ${stats.totalClasses}\n`; + prompt += `- Languages: ${Object.keys(stats.languages || {}).join(', ')}\n\n`; + } + + if (options.file) { + prompt += `Focus on file: ${options.file}\n\n`; + } + + if (options.function) { + prompt += `Focus on function: ${options.function}\n\n`; + } + + prompt += `Question: ${userQuery}`; + + return prompt; + } + + /** + * Get system prompt for Claude + */ + private getSystemPrompt(): string { + return `You are a helpful programming assistant with access to the user's codebase. +Be concise and specific in your responses. Reference actual files and functions when relevant. +Provide code examples when appropriate.`; + } +} + +interface QueryOptions { + includeContext?: boolean; + allowTools?: boolean; + file?: string; + function?: string; +} diff --git a/src/cli/commands/api.ts b/src/cli/commands/api.ts index 757c613..3ea9475 100644 --- a/src/cli/commands/api.ts +++ b/src/cli/commands/api.ts @@ -1,59 +1,59 @@ -import { Command } from 'commander'; -import logger from '../../utils/logger'; - -export function registerApiCommand(program: Command): void { - program - .command('api') - .description('Start REST/GraphQL/WebSocket API server') - .option('--port ', 'Server port', '4000') - .option('--host ', 'Server host', 'localhost') - .option('--enable-auth', 'Enable authentication', false) - .option('--enable-cors', 'Enable CORS', true) - .option('--enable-helmet', 'Enable security headers', true) - .option('--enable-rate-limit', 'Enable rate limiting', true) - .option('--config ', 'Configuration file path') - .action(async (options: any) => { - try { - logger.info('Starting API server...'); - - // Import server dynamically to reduce CLI startup time - const { IndexerAPIServer } = await import('../../api/server'); - const { ConfigLoader } = await import('../../core/config'); - const { Indexer } = await import('../../core/indexer'); - - // Load configuration - const configLoader = new ConfigLoader(options.config); - const indexer = new Indexer(configLoader); - - // Create and configure server - const server = new IndexerAPIServer(indexer, { - port: parseInt(options.port), - host: options.host, - enableAuth: options.enableAuth, - corsOrigins: options.enableCors ? '*' : undefined, - rateLimitMax: options.enableRateLimit ? 100 : undefined, - rateLimitWindow: options.enableRateLimit ? 15 * 60 * 1000 : undefined // 15 minutes - }); - - // Start the server - await server.start(); - - // Handle graceful shutdown - process.on('SIGINT', async () => { - logger.info('\nShutting down API server...'); - await server.stop(); - process.exit(0); - }); - - process.on('SIGTERM', async () => { - logger.info('\nShutting down API server...'); - await server.stop(); - process.exit(0); - }); - - } catch (error) { - logger.error('Error starting API server:', error); - process.exit(1); - } - }); +import { Command } from 'commander'; +import logger from '../../utils/logger'; + +export function registerApiCommand(program: Command): void { + program + .command('api') + .description('Start REST/GraphQL/WebSocket API server') + .option('--port ', 'Server port', '4000') + .option('--host ', 'Server host', 'localhost') + .option('--enable-auth', 'Enable authentication', false) + .option('--enable-cors', 'Enable CORS', true) + .option('--enable-helmet', 'Enable security headers', true) + .option('--enable-rate-limit', 'Enable rate limiting', true) + .option('--config ', 'Configuration file path') + .action(async (options: any) => { + try { + logger.info('Starting API server...'); + + // Import server dynamically to reduce CLI startup time + const { IndexerAPIServer } = await import('../../api/server'); + const { ConfigLoader } = await import('../../core/config'); + const { Indexer } = await import('../../core/indexer'); + + // Load configuration + const configLoader = new ConfigLoader(options.config); + const indexer = new Indexer(configLoader); + + // Create and configure server + const server = new IndexerAPIServer(indexer, { + port: parseInt(options.port), + host: options.host, + enableAuth: options.enableAuth, + corsOrigins: options.enableCors ? '*' : undefined, + rateLimitMax: options.enableRateLimit ? 100 : undefined, + rateLimitWindow: options.enableRateLimit ? 15 * 60 * 1000 : undefined // 15 minutes + }); + + // Start the server + await server.start(); + + // Handle graceful shutdown + process.on('SIGINT', async () => { + logger.info('\nShutting down API server...'); + await server.stop(); + process.exit(0); + }); + + process.on('SIGTERM', async () => { + logger.info('\nShutting down API server...'); + await server.stop(); + process.exit(0); + }); + + } catch (error) { + logger.error('Error starting API server:', error); + process.exit(1); + } + }); } \ No newline at end of file diff --git a/src/cli/commands/clean.ts b/src/cli/commands/clean.ts index 11a5748..7de78c6 100644 --- a/src/cli/commands/clean.ts +++ b/src/cli/commands/clean.ts @@ -1,60 +1,60 @@ -import { Command } from 'commander'; -import * as fs from 'fs'; -import * as path from 'path'; -import { ConfigLoader } from '../../core/config'; -import logger from '../../utils/logger'; - -export function registerCleanCommand(program: Command): void { - program - .command('clean') - .description('Clean cache and output directories') - .option('--cache', 'Clean only cache directory') - .option('--output', 'Clean only output directory') - .option('--all', 'Clean everything (default)', true) - .action(async (options: { cache?: boolean; output?: boolean; all?: boolean }) => { - try { - const config = new ConfigLoader(); - const cacheDir = path.resolve(config.getCacheDir()); - const outputDir = path.resolve(config.getOutputDirectory()); - - logger.info('🧹 Cleaning indexer directories...\n'); - - // Determine what to clean - const cleanCache = options.cache || options.all; - const cleanOutput = options.output || options.all; - - // Clean cache directory - if (cleanCache && fs.existsSync(cacheDir)) { - logger.info(` Removing cache: ${cacheDir}`); - fs.rmSync(cacheDir, { recursive: true, force: true }); - logger.info(' ✓ Cache cleaned'); - } - - // Clean output directory - if (cleanOutput && fs.existsSync(outputDir)) { - logger.info(` Removing output: ${outputDir}`); - fs.rmSync(outputDir, { recursive: true, force: true }); - logger.info(' ✓ Output cleaned'); - } - - // Clean any PROJECT_INDEX.json files (legacy) - const legacyFiles = ['PROJECT_INDEX.json', 'INDEX.json', '.indexer-output']; - for (const file of legacyFiles) { - const filePath = path.resolve(file); - if (fs.existsSync(filePath)) { - logger.info(` Removing: ${file}`); - if (fs.statSync(filePath).isDirectory()) { - fs.rmSync(filePath, { recursive: true, force: true }); - } else { - fs.rmSync(filePath); - } - } - } - - logger.info('\n✅ Cleanup complete!'); - } catch (error) { - logger.error('Error during cleanup:', error); - process.exit(1); - } - }); +import { Command } from 'commander'; +import * as fs from 'fs'; +import * as path from 'path'; +import { ConfigLoader } from '../../core/config'; +import logger from '../../utils/logger'; + +export function registerCleanCommand(program: Command): void { + program + .command('clean') + .description('Clean cache and output directories') + .option('--cache', 'Clean only cache directory') + .option('--output', 'Clean only output directory') + .option('--all', 'Clean everything (default)', true) + .action(async (options: { cache?: boolean; output?: boolean; all?: boolean }) => { + try { + const config = new ConfigLoader(); + const cacheDir = path.resolve(config.getCacheDir()); + const outputDir = path.resolve(config.getOutputDirectory()); + + logger.info('🧹 Cleaning indexer directories...\n'); + + // Determine what to clean + const cleanCache = options.cache || options.all; + const cleanOutput = options.output || options.all; + + // Clean cache directory + if (cleanCache && fs.existsSync(cacheDir)) { + logger.info(` Removing cache: ${cacheDir}`); + fs.rmSync(cacheDir, { recursive: true, force: true }); + logger.info(' ✓ Cache cleaned'); + } + + // Clean output directory + if (cleanOutput && fs.existsSync(outputDir)) { + logger.info(` Removing output: ${outputDir}`); + fs.rmSync(outputDir, { recursive: true, force: true }); + logger.info(' ✓ Output cleaned'); + } + + // Clean any PROJECT_INDEX.json files (legacy) + const legacyFiles = ['PROJECT_INDEX.json', 'INDEX.json', '.indexer-output']; + for (const file of legacyFiles) { + const filePath = path.resolve(file); + if (fs.existsSync(filePath)) { + logger.info(` Removing: ${file}`); + if (fs.statSync(filePath).isDirectory()) { + fs.rmSync(filePath, { recursive: true, force: true }); + } else { + fs.rmSync(filePath); + } + } + } + + logger.info('\n✅ Cleanup complete!'); + } catch (error) { + logger.error('Error during cleanup:', error); + process.exit(1); + } + }); } \ No newline at end of file diff --git a/src/cli/commands/config.ts b/src/cli/commands/config.ts index def3cb8..a7fffe8 100644 --- a/src/cli/commands/config.ts +++ b/src/cli/commands/config.ts @@ -1,105 +1,105 @@ -import { Command } from 'commander'; -import * as fs from 'fs'; -import * as path from 'path'; -import { ConfigLoader } from '../../core/config'; -import logger from '../../utils/logger'; - -export function registerConfigCommand(program: Command): void { - program - .command('config') - .description('Show or create indexer configuration') - .option('--init', 'Create a new configuration file') - .option('--show', 'Show current configuration (default)', true) - .option('--format ', 'Config format: json|yaml', 'yaml') - .action(async (options: { init?: boolean; show?: boolean; format?: string }) => { - try { - const config = new ConfigLoader(); - - if (options.init) { - // Create a new config file - const configPath = options.format === 'json' ? '.indexerconfig.json' : '.indexer.yml'; - - if (fs.existsSync(configPath)) { - logger.warn(`⚠️ Configuration file ${configPath} already exists`); - return; - } - - const defaultConfig = `# Indexer Configuration -version: 2 -name: ${path.basename(process.cwd())} - -# File patterns to include -include: - - "**/*.{js,jsx,ts,tsx,py,go,java,cpp,c,h,hpp,cs,rb,php,swift,kt,rs,sql,graphql,gql,yaml,yml,astro}" - -# File patterns to ignore -ignore: - - "**/node_modules/**" - - "**/dist/**" - - "**/build/**" - - "**/.git/**" - - "**/coverage/**" - - "**/*.min.js" - - "**/*.test.{js,jsx,ts,tsx}" - -# Performance settings -performance: - parallel: true - workers: 4 - cache: true - maxFileSize: 2MB - -# Export settings -export: - outputDirectory: .indexer-output - formats: - json: - path: indexes/INDEX.json - markdown: - path: docs/CODE_INDEX.md - graphviz: - path: visualizations/dependencies.dot - mermaid: - path: visualizations/dependencies.mmd - ascii: - path: docs/project-tree.txt - -# AI settings (optional) -ai: - model: claude-opus-4-1-20250805 - maxConcurrentAgents: 4 - -# Monorepo detection (auto-detected if not specified) -monorepo: auto -`; - - if (options.format === 'json') { - // Convert to JSON - const yaml = await import('js-yaml'); - const configObj = yaml.load(defaultConfig); - fs.writeFileSync(configPath, JSON.stringify(configObj, null, 2)); - } else { - fs.writeFileSync(configPath, defaultConfig); - } - - logger.success(`✅ Created configuration file: ${configPath}`); - logger.info('\nYou can now customize the configuration to fit your project.'); - } else { - // Show current configuration - const currentConfig = config.getConfig(); - - logger.info('📋 Current Indexer Configuration\n'); - logger.info('Configuration file:', config['configPath'] || '(using defaults)'); - logger.info('\nSettings:'); - logger.info(JSON.stringify(currentConfig, null, 2)); - - if (!config['configPath']) { - logger.info('\n💡 Tip: Run "indexer config --init" to create a configuration file'); - } - } - } catch (error) { - logger.error('Error handling configuration:', error); - process.exit(1); - } - }); +import { Command } from 'commander'; +import * as fs from 'fs'; +import * as path from 'path'; +import { ConfigLoader } from '../../core/config'; +import logger from '../../utils/logger'; + +export function registerConfigCommand(program: Command): void { + program + .command('config') + .description('Show or create indexer configuration') + .option('--init', 'Create a new configuration file') + .option('--show', 'Show current configuration (default)', true) + .option('--format ', 'Config format: json|yaml', 'yaml') + .action(async (options: { init?: boolean; show?: boolean; format?: string }) => { + try { + const config = new ConfigLoader(); + + if (options.init) { + // Create a new config file + const configPath = options.format === 'json' ? '.indexerconfig.json' : '.indexer.yml'; + + if (fs.existsSync(configPath)) { + logger.warn(`⚠️ Configuration file ${configPath} already exists`); + return; + } + + const defaultConfig = `# Indexer Configuration +version: 2 +name: ${path.basename(process.cwd())} + +# File patterns to include +include: + - "**/*.{js,jsx,ts,tsx,py,go,java,cpp,c,h,hpp,cs,rb,php,swift,kt,rs,sql,graphql,gql,yaml,yml,astro}" + +# File patterns to ignore +ignore: + - "**/node_modules/**" + - "**/dist/**" + - "**/build/**" + - "**/.git/**" + - "**/coverage/**" + - "**/*.min.js" + - "**/*.test.{js,jsx,ts,tsx}" + +# Performance settings +performance: + parallel: true + workers: 4 + cache: true + maxFileSize: 2MB + +# Export settings +export: + outputDirectory: .indexer-output + formats: + json: + path: indexes/INDEX.json + markdown: + path: docs/CODE_INDEX.md + graphviz: + path: visualizations/dependencies.dot + mermaid: + path: visualizations/dependencies.mmd + ascii: + path: docs/project-tree.txt + +# AI settings (optional) +ai: + model: claude-opus-4-1-20250805 + maxConcurrentAgents: 4 + +# Monorepo detection (auto-detected if not specified) +monorepo: auto +`; + + if (options.format === 'json') { + // Convert to JSON + const yaml = await import('js-yaml'); + const configObj = yaml.load(defaultConfig); + fs.writeFileSync(configPath, JSON.stringify(configObj, null, 2)); + } else { + fs.writeFileSync(configPath, defaultConfig); + } + + logger.success(`✅ Created configuration file: ${configPath}`); + logger.info('\nYou can now customize the configuration to fit your project.'); + } else { + // Show current configuration + const currentConfig = config.getConfig(); + + logger.info('📋 Current Indexer Configuration\n'); + logger.info('Configuration file:', config['configPath'] || '(using defaults)'); + logger.info('\nSettings:'); + logger.info(JSON.stringify(currentConfig, null, 2)); + + if (!config['configPath']) { + logger.info('\n💡 Tip: Run "indexer config --init" to create a configuration file'); + } + } + } catch (error) { + logger.error('Error handling configuration:', error); + process.exit(1); + } + }); } \ No newline at end of file diff --git a/src/cli/commands/export.ts b/src/cli/commands/export.ts index 1d2bb5a..0de286a 100644 --- a/src/cli/commands/export.ts +++ b/src/cli/commands/export.ts @@ -1,170 +1,170 @@ -import { Command } from 'commander'; -import * as fs from 'fs'; -import * as path from 'path'; -import { ConfigLoader } from '../../core/config'; -import { JSONExporter } from '../../exporters/json'; -import { GraphVizExporter } from '../../exporters/graphviz'; -import { MarkdownExporter } from '../../exporters/markdown'; -import { MermaidExporter } from '../../exporters/mermaid'; -import { ASCIIExporter } from '../../exporters/ascii'; -import { ensureDirectoryExists } from '../../utils/file-utils'; -import logger from '../../utils/logger'; - -export function registerExportCommand(program: Command): void { - program - .command('export ') - .description('Export index to different formats (json|compressed|graphviz|markdown|mermaid|ascii)') - .option('--output ', 'Output file path') - .option('--pretty', 'Pretty print JSON output', true) - .option('--statistics', 'Include statistics in output', true) - .option('--token-limit ', 'Token limit for compressed format (e.g., 50000)') - .option('--rankdir ', 'GraphViz/Mermaid direction (TB|BT|LR|RL)', 'LR') - .option('--group-by-dir', 'Group files by directory in GraphViz', true) - .option('--sort-by ', 'Sort files in Markdown (name|type|size|complexity)', 'name') - .option('--diagram-type ', 'Mermaid diagram type (flowchart|graph|classDiagram|erDiagram)', 'flowchart') - .option('--render-html', 'Generate HTML with Mermaid renderer', false) - .option('--theme ', 'Mermaid theme (default|dark|forest|neutral)', 'default') - .option('--max-nodes ', 'Maximum nodes in Mermaid diagram', '50') - .option('--filter-pattern ', 'Filter files by regex pattern') - .option('--service-filter ', 'Filter by service (frontend|backend|skills|data-ops|marketing)') - .option('--ascii-style - - -
-
-

🚀 Code Intelligence Dashboard

-
Complete analysis of ${path.basename(this.projectRoot)}
-
- -
- ${this.generateStatsCards()} -
- -
- ${this.generateFeatureCards()} -
- -
-

Quick Actions

-
-
-
👁️ Start File Watcher
- indexer watch -
-
-
🌐 Start API Server
- indexer api -
-
-
🔍 Query Codebase
- indexer query "pattern" -
-
-
💚 Check Health
- indexer health -
-
-
-
- - - -`; - - fs.writeFileSync(dashboardPath, html); - this.generatedArtifacts.set('dashboard', dashboardPath); - } - - /** - * Show available services (don't actually start them) - */ - private async startServices(): Promise { - // Services are started via separate commands, just show what's available - logger.info('\n📡 Available Services:'); - - // File watcher available - logger.info(' • File watcher: Run "indexer watch" to monitor changes'); - - // API server available - logger.info(' • API server: Run "indexer api" to start REST/GraphQL/WebSocket server'); - - // Slack monitoring if configured - if (process.env.SLACK_BOT_TOKEN && process.env.LINEAR_API_KEY) { - logger.info(' • Slack bot: Run "indexer slack --start" to enable monitoring'); - } - - // Claude hooks - logger.info(' • Claude hooks: Run "indexer hook --install" for AI integration'); - } - - /** - * Show comprehensive summary - */ - private showCompleteSummary(): void { - logger.info('\n' + '='.repeat(70)); - logger.info('✨ INDEXING COMPLETE - EVERYTHING IS READY!'); - logger.info('='.repeat(70)); - - logger.info('\n📁 Generated Artifacts:'); - for (const [name, artifactPath] of Array.from(this.generatedArtifacts)) { - const relPath = artifactPath.replace(this.projectRoot + '/', ''); - logger.info(` • ${name}: ${relPath}`); - } - - logger.info('\n🎯 Quick Access:'); - const dashboardPath = this.generatedArtifacts.get('dashboard'); - if (dashboardPath) { - logger.info(` 📊 Dashboard: file://${dashboardPath}`); - } - - if (this.generatedArtifacts.has('multi-repo-html')) { - logger.info(` 🗺️ Multi-Repo Graph: file://${this.generatedArtifacts.get('multi-repo-html')}`); - } - - logger.info('\n💡 Next Steps:'); - logger.info(' 1. Open the dashboard in your browser (link above)'); - logger.info(' 2. For VS Code/Cursor: Open any .md file and press Cmd+Shift+V'); - logger.info(' 3. Start API server: indexer api'); - logger.info(' 4. Query your code: indexer query "function-name"'); - logger.info(' 5. Watch for changes: indexer watch'); - - logger.info('\n🚀 All features are automatically configured and ready to use!'); - logger.info(' No additional setup needed - enjoy your code intelligence! 🎉\n'); - } - - // Helper methods - - private async findRepositories(): Promise> { - const repos: Array = []; - const searchPaths = [ - 'frontend', 'backend', 'skills', 'data-ops', 'marketing', - 'packages/*', 'services/*', 'apps/*', 'libs/*' - ]; - - for (const searchPath of searchPaths) { - const fullPath = path.join(this.projectRoot, searchPath); - - if (searchPath.includes('*')) { - const dir = path.dirname(fullPath); - if (fs.existsSync(dir)) { - const subdirs = fs.readdirSync(dir, { withFileTypes: true }) - .filter(d => d.isDirectory()) - .map(d => ({ - name: d.name, - path: path.join(dir, d.name), - type: this.detectRepoType(d.name) - })); - repos.push(...subdirs); - } - } else if (fs.existsSync(fullPath)) { - repos.push({ - name: path.basename(fullPath), - path: fullPath, - type: this.detectRepoType(searchPath) - }); - } - } - - return repos; - } - - private detectRepoType(name: string): 'frontend' | 'backend' | 'skills' | 'data-ops' | 'marketing' | 'library' { - const n = name.toLowerCase(); - if (n.includes('frontend') || n.includes('ui') || n.includes('web')) return 'frontend'; - if (n.includes('backend') || n.includes('api') || n.includes('server')) return 'backend'; - if (n.includes('skill')) return 'skills'; - if (n.includes('data') || n.includes('etl')) return 'data-ops'; - if (n.includes('marketing') || n.includes('site')) return 'marketing'; - return 'library'; - } - - private async hasGraphQL(): Promise { - const indicators = [ - '**/*.graphql', - '**/*.gql', - '**/schema.graphql', - '**/graphql/**/*' - ]; - - // Check for GraphQL files - for (const pattern of indicators) { - // Simple check - could be enhanced with glob - if (fs.existsSync(path.join(this.projectRoot, pattern.replace('**/', '')))) { - return true; - } - } - - return false; - } - - private isInVSCode(): boolean { - return process.env.TERM_PROGRAM === 'vscode' || - fs.existsSync(path.join(this.projectRoot, '.vscode')); - } - - private isInCursor(): boolean { - return process.env.CURSOR_IDE === 'true' || - process.env.TERM_PROGRAM === 'cursor' || - fs.existsSync(path.join(this.projectRoot, '.cursor')); - } - - private async generateVSCodeSettings(): Promise { - const vscodeDir = path.join(this.projectRoot, '.vscode'); - ensureDirectoryExists(vscodeDir); - - const settings = { - "indexer.enabled": true, - "indexer.outputPath": ".indexer-output", - "indexer.features": Array.from(this.detectedFeatures), - "indexer.dashboard": `file://${this.generatedArtifacts.get('dashboard')}`, - "markdown.preview.mermaid.enabled": true - }; - - const settingsPath = path.join(vscodeDir, 'indexer.json'); - fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2)); - } - - private async setupGitHooks(): Promise { - const hooksDir = path.join(this.projectRoot, '.git', 'hooks'); - if (!fs.existsSync(hooksDir)) return; - - const preCommitHook = `#!/bin/sh -# Auto-update index before commit -if command -v indexer &> /dev/null; then - indexer scan --incremental -fi -`; - - const hookPath = path.join(hooksDir, 'pre-commit'); - fs.writeFileSync(hookPath, preCommitHook); - fs.chmodSync(hookPath, '755'); - } - - private generateStatsCards(): string { - // Would read from actual index statistics - return ` -
-
${this.detectedFeatures.size}
-
Features Detected
-
-
-
${this.generatedArtifacts.size}
-
Artifacts Generated
-
-
-
100%
-
Coverage
-
- `; - } - - private generateFeatureCards(): string { - const cards: string[] = []; - - if (this.generatedArtifacts.has('main-index')) { - cards.push(` -
-
📊
-
Code Index
-
Complete searchable index of all code with functions, classes, and dependencies.
- View Index -
- `); - } - - if (this.generatedArtifacts.has('multi-repo-html')) { - cards.push(` -
-
🗺️
-
Multi-Repo Graph
-
Interactive visualization of cross-repository dependencies and connections.
- Open Graph -
- `); - } - - if (this.generatedArtifacts.has('mermaid')) { - cards.push(` -
-
🎨
-
Mermaid Diagrams
-
Beautiful diagrams showing code structure and relationships.
- View Diagrams -
- `); - } - - if (this.detectedFeatures.has('monorepo')) { - cards.push(` -
-
🏗️
-
Monorepo Analysis
-
Service boundaries, circular dependencies, and health metrics.
- View Analysis -
- `); - } - - cards.push(` -
-
🔍
-
Code Search
-
Lightning-fast search across all files, functions, and classes.
- Try Search -
- `); - - cards.push(` -
-
🌐
-
API Server
-
REST, GraphQL, and WebSocket APIs for programmatic access.
- Start Server -
- `); - - return cards.join('\n'); - } -} - -/** - * Convenience function to run SmartIndexer - */ -export async function runSmartIndexer(projectRoot?: string, configPath?: string): Promise { - const indexer = new SmartIndexer(projectRoot, configPath); - await indexer.runComplete(); +import * as fs from 'fs'; +import * as path from 'path'; +import { Indexer } from './indexer'; +import { ConfigLoader } from './config'; +import { MonorepoAnalyzer } from './monorepo'; +import { MultiRepoKnowledgeGraph } from './multi-repo-knowledge-graph'; +; +import { ProjectIndex, IndexerOptions } from '../types'; +; +import { MarkdownExporter } from '../exporters/markdown'; +import { MermaidExporter, MultiRepoMermaidExporter } from '../exporters/mermaid'; +import { GraphVizExporter } from '../exporters/graphviz'; +import { ASCIIExporter } from '../exporters/ascii'; +import { CursorMultiRepoIntegration } from '../cursor/multi-repo-integration'; +import { ensureDirectoryExists } from '../utils/file-utils'; +import logger from '../utils/logger'; + +/** + * SmartIndexer - Unified orchestrator that automatically provides ALL features + * No user decisions needed - it detects and does everything intelligently + */ +export class SmartIndexer { + private config: ConfigLoader; + private indexer: Indexer; + private projectRoot: string; + private outputDir: string; + private detectedFeatures: Set = new Set(); + private generatedArtifacts: Map = new Map(); + + constructor(projectRoot: string = process.cwd(), configPath?: string) { + this.projectRoot = projectRoot; + this.config = new ConfigLoader(configPath); + this.indexer = new Indexer(this.config); + this.outputDir = path.join(projectRoot, '.indexer-output', 'current'); + } + + /** + * Main entry point - does EVERYTHING automatically + */ + async runComplete(): Promise { + logger.info('🚀 SmartIndexer starting - analyzing your project...\n'); + + // Archive previous outputs if they exist + await this.archivePreviousOutputs(); + + // Step 1: Detect project structure and features + await this.detectProjectStructure(); + + // Step 2: Build comprehensive index + logger.info('📊 Building comprehensive code index...'); + const index = await this.buildCompleteIndex(); + + // Step 3: Generate all visualizations and exports + logger.info('🎨 Generating visualizations and exports...'); + await this.generateAllExports(index); + + // Step 4: Setup integrations + logger.info('🔌 Setting up IDE integrations...'); + await this.setupIntegrations(index); + + // Step 5: Create unified dashboard + logger.info('📈 Creating unified dashboard...'); + await this.createUnifiedDashboard(); + + // Step 6: Start services if configured + logger.info('🌐 Starting services...'); + await this.startServices(); + + // Step 7: Show summary and guide + this.showCompleteSummary(); + } + + /** + * Archive previous outputs if they exist + */ + private async archivePreviousOutputs(): Promise { + const outputBase = path.join(this.projectRoot, '.indexer-output'); + const currentDir = path.join(outputBase, 'current'); + const archiveBase = path.join(outputBase, 'archive'); + + // Check if there's existing content to archive + // First check if .indexer-output exists with files directly in it (old structure) + if (fs.existsSync(outputBase) && !fs.existsSync(currentDir)) { + // Old structure detected - move everything to current first + logger.info('📋 Migrating to new output structure...'); + + // Create current directory + ensureDirectoryExists(currentDir); + + // Move all files from root to current (except archive if it exists) + const files = fs.readdirSync(outputBase); + for (const file of files) { + if (file !== 'archive' && file !== 'current') { + const oldPath = path.join(outputBase, file); + const newPath = path.join(currentDir, file); + fs.renameSync(oldPath, newPath); + } + } + } + + // Now check if current directory has content to archive + if (!fs.existsSync(currentDir) || fs.readdirSync(currentDir).length === 0) { + // Nothing to archive, just ensure current exists + ensureDirectoryExists(currentDir); + return; + } + + // Create archive directory if needed + ensureDirectoryExists(archiveBase); + + // Generate timestamp for archive folder + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + const archiveDir = path.join(archiveBase, timestamp); + + // Create the archive directory + ensureDirectoryExists(archiveDir); + + // Move all files from current to archive + try { + const files = fs.readdirSync(currentDir); + for (const file of files) { + const srcPath = path.join(currentDir, file); + const destPath = path.join(archiveDir, file); + fs.renameSync(srcPath, destPath); + } + logger.info(`📦 Archived previous outputs to: .indexer-output/archive/${timestamp}/`); + } catch (error) { + logger.warn('️ Could not archive some outputs:', error instanceof Error ? error.message : String(error)); + } + + // Ensure current directory exists and is empty for new outputs + ensureDirectoryExists(currentDir); + } + + /** + * Detect project structure and available features + */ + private async detectProjectStructure(): Promise { + logger.info('🔍 Detecting project structure...'); + + // Check for monorepo + const monorepoIndicators = [ + 'packages', 'services', 'apps', 'libs', + 'frontend', 'backend', 'skills', 'data-ops', 'marketing' + ]; + + for (const indicator of monorepoIndicators) { + const indicatorPath = path.join(this.projectRoot, indicator); + if (fs.existsSync(indicatorPath)) { + this.detectedFeatures.add('monorepo'); + logger.info(' ✅ Monorepo structure detected'); + break; + } + } + + // Check for multiple repos + const repos = await this.findRepositories(); + if (repos.length > 1) { + this.detectedFeatures.add('multi-repo'); + logger.info(` ✅ Multi-repository setup detected (${repos.length} repos)`); + } + + // Check for GraphQL + if (await this.hasGraphQL()) { + this.detectedFeatures.add('graphql'); + logger.info(' ✅ GraphQL API detected'); + } + + // Check for React/Vue/Angular + const packageJsonPath = path.join(this.projectRoot, 'package.json'); + if (fs.existsSync(packageJsonPath)) { + const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + if (pkg.dependencies?.react || pkg.devDependencies?.react) { + this.detectedFeatures.add('react'); + logger.info(' ✅ React framework detected'); + } + if (pkg.dependencies?.vue || pkg.devDependencies?.vue) { + this.detectedFeatures.add('vue'); + logger.info(' ✅ Vue framework detected'); + } + if (pkg.dependencies?.['@angular/core']) { + this.detectedFeatures.add('angular'); + logger.info(' ✅ Angular framework detected'); + } + } + + // Check for Python + if (fs.existsSync(path.join(this.projectRoot, 'requirements.txt')) || + fs.existsSync(path.join(this.projectRoot, 'setup.py')) || + fs.existsSync(path.join(this.projectRoot, 'pyproject.toml'))) { + this.detectedFeatures.add('python'); + logger.info(' ✅ Python project detected'); + } + + // Check for Go + if (fs.existsSync(path.join(this.projectRoot, 'go.mod'))) { + this.detectedFeatures.add('go'); + logger.info(' ✅ Go project detected'); + } + + // Check for Docker + if (fs.existsSync(path.join(this.projectRoot, 'docker-compose.yml')) || + fs.existsSync(path.join(this.projectRoot, 'Dockerfile'))) { + this.detectedFeatures.add('docker'); + logger.info(' ✅ Docker configuration detected'); + } + + logger.info(''); + } + + /** + * Build complete index with all features + */ + private async buildCompleteIndex(): Promise { + const options: IndexerOptions = { + mode: 'deep', // Always use deep mode for comprehensive analysis + parallel: 4, + includeTests: true, + includeComments: true, + followSymlinks: true + } as any; + + const index = await this.indexer.buildIndex(this.projectRoot, options); + + // Save main index to current directory + const indexPath = path.join(this.outputDir, 'index.json'); + ensureDirectoryExists(this.outputDir); + fs.writeFileSync(indexPath, JSON.stringify(index, null, 2)); + this.generatedArtifacts.set('main-index', indexPath); + + // If monorepo, analyze structure + if (this.detectedFeatures.has('monorepo')) { + const analyzer = new MonorepoAnalyzer(); + const analysis = analyzer.analyzeMonorepo(index); + + const monorepoPath = path.join(this.outputDir, 'service-graph.json'); + fs.writeFileSync(monorepoPath, JSON.stringify(analysis, null, 2)); + this.generatedArtifacts.set('service-graph', monorepoPath); + } + + return index; + } + + /** + * Generate ALL export formats automatically + */ + private async generateAllExports(index: ProjectIndex): Promise { + const exporters = [ + { name: 'markdown', exporter: new MarkdownExporter(), ext: '.md' }, + { name: 'graphviz', exporter: new GraphVizExporter(), ext: '.dot' }, + { name: 'mermaid', exporter: new MermaidExporter(), ext: '.mmd' }, + { name: 'ascii', exporter: new ASCIIExporter(), ext: '.txt' } + ]; + + for (const { name, exporter, ext } of exporters) { + const outputPath = path.join(this.outputDir, `visualization${ext}`); + + const options: any = { + includeStatistics: true + }; + + if (name === 'mermaid') { + options.renderHtml = true; + } + + await exporter.export(index, outputPath, options); + + this.generatedArtifacts.set(name, outputPath); + } + + // Generate multi-repo visualizations if applicable + if (this.detectedFeatures.has('multi-repo')) { + await this.generateMultiRepoVisualizations(); + } + } + + /** + * Generate multi-repository visualizations + */ + private async generateMultiRepoVisualizations(): Promise { + const graph = new MultiRepoKnowledgeGraph(); + const repos = await this.findRepositories(); + + for (const repo of repos) { + await graph.addRepository(repo); + } + + await graph.analyzeConnections(); + + // Export API mappings (cross-repo/cross-service connections) + const graphPath = path.join(this.outputDir, 'api-mappings.json'); + graph.exportJSON(graphPath); + this.generatedArtifacts.set('api-mappings', graphPath); + + // Generate Mermaid diagrams + // Convert the graph to the expected format + const graphData = graph.getGraph(); + const convertedGraph: any = { + repositories: Array.from(graphData.repos.values()).map(repo => ({ + name: repo.name, + path: repo.path, + type: repo.type + })), + connections: graphData.connections, + timestamp: new Date().toISOString() + }; + + const exporter = new MultiRepoMermaidExporter(convertedGraph); + + const mdPath = path.join(this.outputDir, 'service-dependencies.md'); + await exporter.exportForVSCode(mdPath); + this.generatedArtifacts.set('service-dependencies-md', mdPath); + + // Interactive viewer functionality not yet implemented + // const htmlPath = path.join(this.outputDir, 'service-viewer.html'); + // await exporter.generateInteractiveViewer(htmlPath); + // this.generatedArtifacts.set('service-viewer', htmlPath); + } + + /** + * Setup IDE integrations automatically + */ + private async setupIntegrations(index: ProjectIndex): Promise { + // VS Code/Cursor integration + if (this.isInVSCode() || this.isInCursor()) { + const cursorIntegration = new CursorMultiRepoIntegration(this.projectRoot); + + if (this.detectedFeatures.has('multi-repo')) { + await cursorIntegration.initialize(); + const files = await cursorIntegration.generateCursorViews(); + files.forEach(f => this.generatedArtifacts.set(`cursor-${path.basename(f)}`, f)); + } + + // Generate VS Code settings + await this.generateVSCodeSettings(); + } + + // Generate Git hooks if in Git repo + if (fs.existsSync(path.join(this.projectRoot, '.git'))) { + await this.setupGitHooks(); + } + } + + /** + * Create unified dashboard showing ALL features + */ + private async createUnifiedDashboard(): Promise { + const dashboardPath = path.join(this.outputDir, 'dashboard.html'); + + const html = ` + + + + + Code Intelligence Dashboard - ${path.basename(this.projectRoot)} + + + +
+
+

🚀 Code Intelligence Dashboard

+
Complete analysis of ${path.basename(this.projectRoot)}
+
+ +
+ ${this.generateStatsCards()} +
+ +
+ ${this.generateFeatureCards()} +
+ +
+

Quick Actions

+
+
+
👁️ Start File Watcher
+ indexer watch +
+
+
🌐 Start API Server
+ indexer api +
+
+
🔍 Query Codebase
+ indexer query "pattern" +
+
+
💚 Check Health
+ indexer health +
+
+
+
+ + + +`; + + fs.writeFileSync(dashboardPath, html); + this.generatedArtifacts.set('dashboard', dashboardPath); + } + + /** + * Show available services (don't actually start them) + */ + private async startServices(): Promise { + // Services are started via separate commands, just show what's available + logger.info('\n📡 Available Services:'); + + // File watcher available + logger.info(' • File watcher: Run "indexer watch" to monitor changes'); + + // API server available + logger.info(' • API server: Run "indexer api" to start REST/GraphQL/WebSocket server'); + + // Slack monitoring if configured + if (process.env.SLACK_BOT_TOKEN && process.env.LINEAR_API_KEY) { + logger.info(' • Slack bot: Run "indexer slack --start" to enable monitoring'); + } + + // Claude hooks + logger.info(' • Claude hooks: Run "indexer hook --install" for AI integration'); + } + + /** + * Show comprehensive summary + */ + private showCompleteSummary(): void { + logger.info('\n' + '='.repeat(70)); + logger.info('✨ INDEXING COMPLETE - EVERYTHING IS READY!'); + logger.info('='.repeat(70)); + + logger.info('\n📁 Generated Artifacts:'); + for (const [name, artifactPath] of Array.from(this.generatedArtifacts)) { + const relPath = artifactPath.replace(this.projectRoot + '/', ''); + logger.info(` • ${name}: ${relPath}`); + } + + logger.info('\n🎯 Quick Access:'); + const dashboardPath = this.generatedArtifacts.get('dashboard'); + if (dashboardPath) { + logger.info(` 📊 Dashboard: file://${dashboardPath}`); + } + + if (this.generatedArtifacts.has('multi-repo-html')) { + logger.info(` 🗺️ Multi-Repo Graph: file://${this.generatedArtifacts.get('multi-repo-html')}`); + } + + logger.info('\n💡 Next Steps:'); + logger.info(' 1. Open the dashboard in your browser (link above)'); + logger.info(' 2. For VS Code/Cursor: Open any .md file and press Cmd+Shift+V'); + logger.info(' 3. Start API server: indexer api'); + logger.info(' 4. Query your code: indexer query "function-name"'); + logger.info(' 5. Watch for changes: indexer watch'); + + logger.info('\n🚀 All features are automatically configured and ready to use!'); + logger.info(' No additional setup needed - enjoy your code intelligence! 🎉\n'); + } + + // Helper methods + + private async findRepositories(): Promise> { + const repos: Array = []; + const searchPaths = [ + 'frontend', 'backend', 'skills', 'data-ops', 'marketing', + 'packages/*', 'services/*', 'apps/*', 'libs/*' + ]; + + for (const searchPath of searchPaths) { + const fullPath = path.join(this.projectRoot, searchPath); + + if (searchPath.includes('*')) { + const dir = path.dirname(fullPath); + if (fs.existsSync(dir)) { + const subdirs = fs.readdirSync(dir, { withFileTypes: true }) + .filter(d => d.isDirectory()) + .map(d => ({ + name: d.name, + path: path.join(dir, d.name), + type: this.detectRepoType(d.name) + })); + repos.push(...subdirs); + } + } else if (fs.existsSync(fullPath)) { + repos.push({ + name: path.basename(fullPath), + path: fullPath, + type: this.detectRepoType(searchPath) + }); + } + } + + return repos; + } + + private detectRepoType(name: string): 'frontend' | 'backend' | 'skills' | 'data-ops' | 'marketing' | 'library' { + const n = name.toLowerCase(); + if (n.includes('frontend') || n.includes('ui') || n.includes('web')) return 'frontend'; + if (n.includes('backend') || n.includes('api') || n.includes('server')) return 'backend'; + if (n.includes('skill')) return 'skills'; + if (n.includes('data') || n.includes('etl')) return 'data-ops'; + if (n.includes('marketing') || n.includes('site')) return 'marketing'; + return 'library'; + } + + private async hasGraphQL(): Promise { + const indicators = [ + '**/*.graphql', + '**/*.gql', + '**/schema.graphql', + '**/graphql/**/*' + ]; + + // Check for GraphQL files + for (const pattern of indicators) { + // Simple check - could be enhanced with glob + if (fs.existsSync(path.join(this.projectRoot, pattern.replace('**/', '')))) { + return true; + } + } + + return false; + } + + private isInVSCode(): boolean { + return process.env.TERM_PROGRAM === 'vscode' || + fs.existsSync(path.join(this.projectRoot, '.vscode')); + } + + private isInCursor(): boolean { + return process.env.CURSOR_IDE === 'true' || + process.env.TERM_PROGRAM === 'cursor' || + fs.existsSync(path.join(this.projectRoot, '.cursor')); + } + + private async generateVSCodeSettings(): Promise { + const vscodeDir = path.join(this.projectRoot, '.vscode'); + ensureDirectoryExists(vscodeDir); + + const settings = { + "indexer.enabled": true, + "indexer.outputPath": ".indexer-output", + "indexer.features": Array.from(this.detectedFeatures), + "indexer.dashboard": `file://${this.generatedArtifacts.get('dashboard')}`, + "markdown.preview.mermaid.enabled": true + }; + + const settingsPath = path.join(vscodeDir, 'indexer.json'); + fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2)); + } + + private async setupGitHooks(): Promise { + const hooksDir = path.join(this.projectRoot, '.git', 'hooks'); + if (!fs.existsSync(hooksDir)) return; + + const preCommitHook = `#!/bin/sh +# Auto-update index before commit +if command -v indexer &> /dev/null; then + indexer scan --incremental +fi +`; + + const hookPath = path.join(hooksDir, 'pre-commit'); + fs.writeFileSync(hookPath, preCommitHook); + fs.chmodSync(hookPath, '755'); + } + + private generateStatsCards(): string { + // Would read from actual index statistics + return ` +
+
${this.detectedFeatures.size}
+
Features Detected
+
+
+
${this.generatedArtifacts.size}
+
Artifacts Generated
+
+
+
100%
+
Coverage
+
+ `; + } + + private generateFeatureCards(): string { + const cards: string[] = []; + + if (this.generatedArtifacts.has('main-index')) { + cards.push(` +
+
📊
+
Code Index
+
Complete searchable index of all code with functions, classes, and dependencies.
+ View Index +
+ `); + } + + if (this.generatedArtifacts.has('multi-repo-html')) { + cards.push(` +
+
🗺️
+
Multi-Repo Graph
+
Interactive visualization of cross-repository dependencies and connections.
+ Open Graph +
+ `); + } + + if (this.generatedArtifacts.has('mermaid')) { + cards.push(` +
+
🎨
+
Mermaid Diagrams
+
Beautiful diagrams showing code structure and relationships.
+ View Diagrams +
+ `); + } + + if (this.detectedFeatures.has('monorepo')) { + cards.push(` +
+
🏗️
+
Monorepo Analysis
+
Service boundaries, circular dependencies, and health metrics.
+ View Analysis +
+ `); + } + + cards.push(` +
+
🔍
+
Code Search
+
Lightning-fast search across all files, functions, and classes.
+ Try Search +
+ `); + + cards.push(` +
+
🌐
+
API Server
+
REST, GraphQL, and WebSocket APIs for programmatic access.
+ Start Server +
+ `); + + return cards.join('\n'); + } +} + +/** + * Convenience function to run SmartIndexer + */ +export async function runSmartIndexer(projectRoot?: string, configPath?: string): Promise { + const indexer = new SmartIndexer(projectRoot, configPath); + await indexer.runComplete(); } \ No newline at end of file diff --git a/src/core/watcher.ts b/src/core/watcher.ts index 3268535..fb7e837 100644 --- a/src/core/watcher.ts +++ b/src/core/watcher.ts @@ -1,202 +1,203 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { watch } from 'chokidar'; -import { Indexer } from './indexer'; -import { ProjectIndex } from '../types'; -import logger from '../../utils/logger'; - -interface WatcherOptions { - debounce?: number; - quiet?: boolean; - ignore?: string[]; -} - -export class FileWatcher { - private indexer: Indexer; - private outputPath: string; - private options: WatcherOptions; - private watcher: any; - private pendingChanges: Set = new Set(); - private debounceTimer: NodeJS.Timeout | null = null; - private currentIndex: ProjectIndex | null = null; - - constructor(indexer: Indexer, outputPath: string, options: WatcherOptions = {}) { - this.indexer = indexer; - this.outputPath = outputPath; - this.options = { - debounce: 300, - quiet: false, - ...options - }; - - // Load current index if it exists - if (fs.existsSync(outputPath)) { - try { - this.currentIndex = JSON.parse(fs.readFileSync(outputPath, 'utf-8')); - } catch (error) { - logger.error('Failed to load existing index:', error); - } - } - } - - public start(): void { - const watchPath = process.cwd(); - - this.watcher = watch(watchPath, { - ignored: [ - /(^|[\/\\])\../, // Hidden files - '**/node_modules/**', - '**/dist/**', - '**/build/**', - '**/.git/**', - this.outputPath, // Don't watch the index file itself - ...(this.options.ignore || []) - ], - persistent: true, - ignoreInitial: true, - awaitWriteFinish: { - stabilityThreshold: 100, - pollInterval: 100 - } - }); - - this.watcher - .on('add', (filePath: string) => this.handleFileChange(filePath, 'added')) - .on('change', (filePath: string) => this.handleFileChange(filePath, 'changed')) - .on('unlink', (filePath: string) => this.handleFileChange(filePath, 'deleted')) - .on('error', (error: Error) => logger.error('Watcher error:', error)); - - if (!this.options.quiet) { - logger.info('File watcher started...'); - } - } - - public stop(): void { - if (this.watcher) { - this.watcher.close(); - this.watcher = null; - } - - if (this.debounceTimer) { - clearTimeout(this.debounceTimer); - this.debounceTimer = null; - } - } - - private handleFileChange(filePath: string, changeType: string): void { - const relativePath = path.relative(process.cwd(), filePath); - - if (!this.options.quiet) { - logger.info(`File ${changeType}: ${relativePath}`); - } - - this.pendingChanges.add(filePath); - - // Debounce the update - if (this.debounceTimer) { - clearTimeout(this.debounceTimer); - } - - this.debounceTimer = setTimeout(() => { - this.processPendingChanges(); - }, this.options.debounce); - } - - private async processPendingChanges(): Promise { - if (this.pendingChanges.size === 0) { - return; - } - - // Take a snapshot of current changes and create new Set for incoming changes - const changesToProcess = this.pendingChanges; - this.pendingChanges = new Set(); - - const changes = Array.from(changesToProcess); - - if (!this.options.quiet) { - logger.info(`Processing ${changes.length} file changes...`); - } - - try { - // Update the index incrementally - if (this.currentIndex) { - for (const filePath of changes) { - const relativePath = path.relative(process.cwd(), filePath); - - try { - await fsPromises.access(filePath); - // File added or changed - const fileIndex = await this.indexer.updateFile(filePath, process.cwd()); - if (fileIndex) { - this.currentIndex.files[relativePath] = fileIndex; - } - } catch { - // File deleted - delete this.currentIndex.files[relativePath]; - this.indexer.removeFile(filePath); - } - } - - // Recalculate statistics - this.updateStatistics(); - - // Update timestamp - this.currentIndex.timestamp = new Date().toISOString(); - } else { - // No current index, rebuild from scratch - this.currentIndex = await this.indexer.buildIndex(process.cwd(), { incremental: true }); - } - - // Save the updated index - await this.saveIndex(); - - if (!this.options.quiet) { - logger.info('Index updated successfully'); - } - } catch (error) { - logger.error('Failed to update index:', error); - } - } - - private updateStatistics(): void { - if (!this.currentIndex) return; - - let totalFunctions = 0; - let totalClasses = 0; - let totalConstants = 0; - const languages: Record = {}; - - for (const file of Object.values(this.currentIndex.files)) { - totalFunctions += file.functions?.length || 0; - totalClasses += file.classes?.length || 0; - totalConstants += file.constants?.length || 0; - - if (file.language) { - languages[file.language] = (languages[file.language] || 0) + 1; - } - } - - this.currentIndex.statistics = { - totalFiles: Object.keys(this.currentIndex.files).length, - totalFunctions, - totalClasses, - totalConstants, - languages - }; - } - - private saveIndex(): void { - if (!this.currentIndex) return; - - try { - const content = JSON.stringify(this.currentIndex, null, 2); - fs.writeFileSync(this.outputPath, content); - } catch (error) { - logger.error('Failed to save index:', error); - } - } - - public getIndex(): ProjectIndex | null { - return this.currentIndex; - } +import * as fs from 'fs'; +import { promises as fsPromises } from 'fs'; +import * as path from 'path'; +import { watch } from 'chokidar'; +import { Indexer } from './indexer'; +import { ProjectIndex } from '../types'; +import logger from '../utils/logger'; + +interface WatcherOptions { + debounce?: number; + quiet?: boolean; + ignore?: string[]; +} + +export class FileWatcher { + private indexer: Indexer; + private outputPath: string; + private options: WatcherOptions; + private watcher: any; + private pendingChanges: Set = new Set(); + private debounceTimer: NodeJS.Timeout | null = null; + private currentIndex: ProjectIndex | null = null; + + constructor(indexer: Indexer, outputPath: string, options: WatcherOptions = {}) { + this.indexer = indexer; + this.outputPath = outputPath; + this.options = { + debounce: 300, + quiet: false, + ...options + }; + + // Load current index if it exists + if (fs.existsSync(outputPath)) { + try { + this.currentIndex = JSON.parse(fs.readFileSync(outputPath, 'utf-8')); + } catch (error) { + logger.error('Failed to load existing index:', error); + } + } + } + + public start(): void { + const watchPath = process.cwd(); + + this.watcher = watch(watchPath, { + ignored: [ + /(^|[\/\\])\../, // Hidden files + '**/node_modules/**', + '**/dist/**', + '**/build/**', + '**/.git/**', + this.outputPath, // Don't watch the index file itself + ...(this.options.ignore || []) + ], + persistent: true, + ignoreInitial: true, + awaitWriteFinish: { + stabilityThreshold: 100, + pollInterval: 100 + } + }); + + this.watcher + .on('add', (filePath: string) => this.handleFileChange(filePath, 'added')) + .on('change', (filePath: string) => this.handleFileChange(filePath, 'changed')) + .on('unlink', (filePath: string) => this.handleFileChange(filePath, 'deleted')) + .on('error', (error: Error) => logger.error('Watcher error:', error)); + + if (!this.options.quiet) { + logger.info('File watcher started...'); + } + } + + public stop(): void { + if (this.watcher) { + this.watcher.close(); + this.watcher = null; + } + + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + this.debounceTimer = null; + } + } + + private handleFileChange(filePath: string, changeType: string): void { + const relativePath = path.relative(process.cwd(), filePath); + + if (!this.options.quiet) { + logger.info(`File ${changeType}: ${relativePath}`); + } + + this.pendingChanges.add(filePath); + + // Debounce the update + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + } + + this.debounceTimer = setTimeout(() => { + this.processPendingChanges(); + }, this.options.debounce); + } + + private async processPendingChanges(): Promise { + if (this.pendingChanges.size === 0) { + return; + } + + // Take a snapshot of current changes and create new Set for incoming changes + const changesToProcess = this.pendingChanges; + this.pendingChanges = new Set(); + + const changes = Array.from(changesToProcess); + + if (!this.options.quiet) { + logger.info(`Processing ${changes.length} file changes...`); + } + + try { + // Update the index incrementally + if (this.currentIndex) { + for (const filePath of changes) { + const relativePath = path.relative(process.cwd(), filePath); + + try { + await fsPromises.access(filePath); + // File added or changed + const fileIndex = await this.indexer.updateFile(filePath, process.cwd()); + if (fileIndex) { + this.currentIndex.files[relativePath] = fileIndex; + } + } catch { + // File deleted + delete this.currentIndex.files[relativePath]; + this.indexer.removeFile(filePath); + } + } + + // Recalculate statistics + this.updateStatistics(); + + // Update timestamp + this.currentIndex.timestamp = new Date().toISOString(); + } else { + // No current index, rebuild from scratch + this.currentIndex = await this.indexer.buildIndex(process.cwd(), { incremental: true }); + } + + // Save the updated index + await this.saveIndex(); + + if (!this.options.quiet) { + logger.info('Index updated successfully'); + } + } catch (error) { + logger.error('Failed to update index:', error); + } + } + + private updateStatistics(): void { + if (!this.currentIndex) return; + + let totalFunctions = 0; + let totalClasses = 0; + let totalConstants = 0; + const languages: Record = {}; + + for (const file of Object.values(this.currentIndex.files)) { + totalFunctions += file.functions?.length || 0; + totalClasses += file.classes?.length || 0; + totalConstants += file.constants?.length || 0; + + if (file.language) { + languages[file.language] = (languages[file.language] || 0) + 1; + } + } + + this.currentIndex.statistics = { + totalFiles: Object.keys(this.currentIndex.files).length, + totalFunctions, + totalClasses, + totalConstants, + languages + }; + } + + private saveIndex(): void { + if (!this.currentIndex) return; + + try { + const content = JSON.stringify(this.currentIndex, null, 2); + fs.writeFileSync(this.outputPath, content); + } catch (error) { + logger.error('Failed to save index:', error); + } + } + + public getIndex(): ProjectIndex | null { + return this.currentIndex; + } } \ No newline at end of file diff --git a/src/cursor/multi-repo-integration.ts b/src/cursor/multi-repo-integration.ts index bf5dcdd..25db9fe 100644 --- a/src/cursor/multi-repo-integration.ts +++ b/src/cursor/multi-repo-integration.ts @@ -1,418 +1,420 @@ -import * as path from 'path'; -import * as fs from 'fs'; -import { MultiRepoKnowledgeGraph } from '../core/multi-repo-knowledge-graph'; -import { MultiRepoMermaidExporter } from '../exporters/mermaid'; -import logger from '../utils/logger'; - -/** - * Cursor IDE Integration for Multi-Repository Knowledge Graph - * Provides seamless Mermaid diagram viewing within Cursor - */ -export class CursorMultiRepoIntegration { - private graph: MultiRepoKnowledgeGraph | null = null; - private outputDir: string; - - constructor(workspacePath?: string) { - // Default output directory for Cursor - this.outputDir = path.join( - workspacePath || process.cwd(), - '.indexer-output', - 'visualizations' - ); - - // Ensure output directory exists - if (!fs.existsSync(this.outputDir)) { - fs.mkdirSync(this.outputDir, { recursive: true }); - } - } - - /** - * Initialize and analyze repositories - */ - async initialize(repos?: Array<{ name: string; path: string; type: string }>): Promise { - this.graph = new MultiRepoKnowledgeGraph(); - - if (repos) { - for (const repo of repos) { - await this.graph.addRepository(repo as any); - } - } else { - // Auto-detect repositories - const detected = await this.autoDetectRepositories(); - for (const repo of detected) { - await this.graph.addRepository(repo); - } - } - - await this.graph.analyzeConnections(); - } - - /** - * Generate Cursor-compatible Markdown with Mermaid diagrams - * Cursor has excellent built-in Mermaid support in Markdown preview - */ - async generateCursorViews(): Promise { - if (!this.graph || !this.graph.hasRepositories()) { - throw new Error('No repositories analyzed. Call initialize() first.'); - } - - const exporter = new MultiRepoMermaidExporter(this.graph.getGraph()); - const generatedFiles: string[] = []; - - // Generate main overview file - const overviewPath = path.join(this.outputDir, 'multi-repo-overview.md'); - await exporter.exportForVSCode(overviewPath); - generatedFiles.push(overviewPath); - - // Generate interactive HTML (can be opened in Cursor's preview) - const htmlPath = path.join(this.outputDir, 'multi-repo-interactive.html'); - await exporter.generateInteractiveViewer(htmlPath); - generatedFiles.push(htmlPath); - - // Generate individual diagram files for better Cursor integration - const diagrams = await this.generateIndividualDiagrams(exporter); - generatedFiles.push(...diagrams); - - // Generate README with links to all diagrams - const readmePath = await this.generateCursorReadme(generatedFiles); - generatedFiles.push(readmePath); - - return generatedFiles; - } - - /** - * Generate individual diagram files for focused viewing - */ - private async generateIndividualDiagrams(exporter: MultiRepoMermaidExporter): Promise { - const files: string[] = []; - - // Overview diagram - const overviewPath = path.join(this.outputDir, 'diagram-overview.md'); - const overviewContent = await exporter.generateMultiRepoDiagram(); - fs.writeFileSync(overviewPath, `# Repository Overview\n\n${overviewContent}`); - files.push(overviewPath); - - // API Flow diagram - const apiFlowPath = path.join(this.outputDir, 'diagram-api-flow.md'); - const apiFlowContent = await exporter.generateAPIFlowDiagram(); - fs.writeFileSync(apiFlowPath, `# API Flow Diagram\n\n${apiFlowContent}`); - files.push(apiFlowPath); - - // Service Matrix diagram - const matrixPath = path.join(this.outputDir, 'diagram-service-matrix.md'); - const matrixContent = await exporter.generateServiceMatrix(); - fs.writeFileSync(matrixPath, `# Service Interaction Matrix\n\n${matrixContent}`); - files.push(matrixPath); - - return files; - } - - /** - * Generate Cursor-specific README with navigation - */ - private async generateCursorReadme(generatedFiles: string[]): Promise { - const readmePath = path.join(this.outputDir, 'README.md'); - - const content = `# Multi-Repository Knowledge Graph - Cursor View - -## 🎯 Quick Navigation - -### Interactive Views -- [📊 Full Interactive Dashboard](./multi-repo-interactive.html) - Open in Cursor's preview -- [📝 Complete Analysis Report](./multi-repo-overview.md) - Best viewed in Cursor's Markdown preview - -### Individual Diagrams -- [🗺️ Repository Overview](./diagram-overview.md) - Overall repository structure -- [🔄 API Flow Diagram](./diagram-api-flow.md) - Frontend to backend connections -- [📈 Service Interaction Matrix](./diagram-service-matrix.md) - Connection density visualization - -## 💡 Viewing Tips for Cursor - -### Markdown Preview with Mermaid -1. Open any \`.md\` file from above -2. Press \`Cmd+Shift+V\` (Mac) or \`Ctrl+Shift+V\` (Windows/Linux) -3. Cursor will automatically render Mermaid diagrams - -### Split View -1. Open a diagram file -2. Press \`Cmd+\\\` (Mac) or \`Ctrl+\\\` (Windows/Linux) to split editor -3. Open preview in one pane, keep code in the other - -### Interactive HTML -1. Right-click on \`multi-repo-interactive.html\` -2. Select "Open with Live Server" or "Preview" -3. Interact with tabs and zoom controls - -## 📊 Current Statistics - -- **Repositories Analyzed**: ${this.graph?.getGraph().repositories.length || 0} -- **Total Connections**: ${this.graph?.getGraph().connections.length || 0} -- **Connection Types**: ${this.getConnectionTypes()} - -## 🔄 Refresh Data - -Run the following in Cursor's terminal: -\`\`\`bash -# Re-analyze all repositories -npx indexer multi-repo --analyze - -# Export fresh diagrams -npx indexer multi-repo --export cursor -\`\`\` - -## 🎨 Diagram Legend - -### Connection Types -- **Solid Arrow (→)**: API calls -- **Dashed Arrow (⇢)**: GraphQL operations -- **Thick Arrow (⇒)**: Event-based connections -- **Dotted Line (⋯)**: Import dependencies - -### Repository Colors -- 🟦 **Frontend**: Light blue (#61dafb) -- 🟩 **Backend**: Green (#68a063) -- 🟡 **Skills**: Yellow (#f1e05a) -- 🟥 **Data Ops**: Red (#f97583) -- 🟧 **Marketing**: Orange (#ff6b6b) - -## 🚀 Cursor-Specific Features - -### AI-Powered Analysis -Ask Cursor's AI about your architecture: -- "What are the main API dependencies?" -- "Which services have circular dependencies?" -- "How can I optimize the service connections?" - -### Quick Actions -- \`Cmd+P\` → Type "multi-repo" to find all generated files -- \`Cmd+Shift+F\` → Search across all diagram files -- Use Cursor's outline view to navigate large diagrams - ---- -Generated: ${new Date().toISOString()} -`; - - fs.writeFileSync(readmePath, content); - return readmePath; - } - - /** - * Get connection types summary - */ - private getConnectionTypes(): string { - if (!this.graph) return 'N/A'; - - const connections = this.graph.getGraph().connections; - const types = new Set(connections.map(c => c.type)); - return Array.from(types).join(', '); - } - - /** - * Auto-detect repositories in workspace - */ - private async autoDetectRepositories(): Promise> { - const repos: Array = []; - const workspaceRoot = process.cwd(); - - // Common monorepo patterns - const patterns = [ - 'frontend', 'backend', 'skills', 'data-ops', 'marketing', - 'packages/*', 'services/*', 'apps/*', 'libs/*' - ]; - - for (const pattern of patterns) { - const searchPath = path.join(workspaceRoot, pattern); - - if (pattern.includes('*')) { - const dir = path.dirname(searchPath); - if (fs.existsSync(dir)) { - const subdirs = fs.readdirSync(dir, { withFileTypes: true }) - .filter(d => d.isDirectory()) - .map(d => path.join(dir, d.name)); - - for (const subdir of subdirs) { - repos.push({ - name: path.basename(subdir), - path: subdir, - type: this.detectRepoType(subdir) - }); - } - } - } else if (fs.existsSync(searchPath)) { - repos.push({ - name: path.basename(searchPath), - path: searchPath, - type: this.detectRepoType(searchPath) - }); - } - } - - return repos; - } - - /** - * Detect repository type - */ - private detectRepoType(repoPath: string): 'frontend' | 'backend' | 'skills' | 'data-ops' | 'marketing' | 'library' { - const name = path.basename(repoPath).toLowerCase(); - - if (name.includes('frontend') || name.includes('ui')) return 'frontend'; - if (name.includes('backend') || name.includes('api')) return 'backend'; - if (name.includes('skill')) return 'skills'; - if (name.includes('data')) return 'data-ops'; - if (name.includes('marketing')) return 'marketing'; - - // Check package.json - const pkgPath = path.join(repoPath, 'package.json'); - if (fs.existsSync(pkgPath)) { - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); - if (pkg.dependencies?.react || pkg.dependencies?.vue) return 'frontend'; - if (pkg.dependencies?.express || pkg.dependencies?.fastify) return 'backend'; - } - - return 'backend'; - } - - /** - * Generate Cursor command palette commands - */ - generateCursorCommands(): string { - return `{ - "commands": [ - { - "command": "multi-repo.analyze", - "title": "Multi-Repo: Analyze Workspace", - "category": "Multi-Repo" - }, - { - "command": "multi-repo.view-overview", - "title": "Multi-Repo: View Overview Diagram", - "category": "Multi-Repo" - }, - { - "command": "multi-repo.view-api-flow", - "title": "Multi-Repo: View API Flow", - "category": "Multi-Repo" - }, - { - "command": "multi-repo.view-matrix", - "title": "Multi-Repo: View Service Matrix", - "category": "Multi-Repo" - }, - { - "command": "multi-repo.export-all", - "title": "Multi-Repo: Export All Diagrams", - "category": "Multi-Repo" - } - ], - "keybindings": [ - { - "command": "multi-repo.analyze", - "key": "cmd+shift+m a", - "when": "editorTextFocus" - }, - { - "command": "multi-repo.view-overview", - "key": "cmd+shift+m o", - "when": "editorTextFocus" - } - ] -}`; - } -} - -/** - * CLI Integration for Cursor - */ -export class CursorCLI { - static async run(args: string[]): Promise { - const command = args[0]; - const integration = new CursorMultiRepoIntegration(); - - switch (command) { - case 'analyze': - logger.info('🔍 Analyzing repositories...'); - await integration.initialize(); - logger.success(' Analysis complete'); - break; - - case 'export': - logger.info('📊 Generating Cursor-compatible views...'); - await integration.initialize(); - const files = await integration.generateCursorViews(); - logger.success(' Generated files:'); - files.forEach(f => logger.info(` - ${f}`)); - logger.info('\n💡 Tip: Open any .md file and press Cmd+Shift+V to preview with Mermaid support'); - break; - - case 'commands': - logger.info('📋 Cursor command configuration:'); - logger.info(integration.generateCursorCommands()); - logger.info('\n💡 Add this to your .cursor/settings.json'); - break; - - default: - logger.info(` -Cursor Multi-Repo Integration - -Usage: - cursor-multi-repo analyze Analyze workspace repositories - cursor-multi-repo export Generate Cursor-compatible diagrams - cursor-multi-repo commands Show Cursor command configuration - -Examples: - npx indexer cursor analyze - npx indexer cursor export - `); - } - } -} - -/** - * Cursor-specific helpers - */ -export const CursorHelpers = { - /** - * Check if running in Cursor - */ - isInCursor(): boolean { - return process.env.CURSOR_IDE === 'true' || - process.env.TERM_PROGRAM === 'cursor' || - fs.existsSync(path.join(process.cwd(), '.cursor')); - }, - - /** - * Get Cursor workspace path - */ - getCursorWorkspace(): string | null { - // Check for .cursor directory - const cursorDir = path.join(process.cwd(), '.cursor'); - if (fs.existsSync(cursorDir)) { - return process.cwd(); - } - - // Check environment variables - return process.env.CURSOR_WORKSPACE || process.env.PWD || null; - }, - - /** - * Open file in Cursor - */ - openInCursor(filePath: string): void { - const { exec } = require('child_process'); - exec(`cursor "${filePath}"`, (error: any) => { - if (error) { - logger.error('Failed to open in Cursor:', error); - // Fallback to code command - exec(`code "${filePath}"`); - } - }); - } -}; - -// Export for use in CLI +import * as path from 'path'; +import * as fs from 'fs'; +import { promises as fsPromises } from 'fs'; +import { MultiRepoKnowledgeGraph } from '../core/multi-repo-knowledge-graph'; +import { MultiRepoMermaidExporter } from '../exporters/mermaid-multi-repo'; +import logger from '../utils/logger'; + +/** + * Cursor IDE Integration for Multi-Repository Knowledge Graph + * Provides seamless Mermaid diagram viewing within Cursor + */ +export class CursorMultiRepoIntegration { + private graph: MultiRepoKnowledgeGraph | null = null; + private outputDir: string; + + constructor(workspacePath?: string) { + // Default output directory for Cursor + this.outputDir = path.join( + workspacePath || process.cwd(), + '.indexer-output', + 'visualizations' + ); + + // Ensure output directory exists + if (!fs.existsSync(this.outputDir)) { + fs.mkdirSync(this.outputDir, { recursive: true }); + } + } + + /** + * Initialize and analyze repositories + */ + async initialize(repos?: Array<{ name: string; path: string; type: string }>): Promise { + this.graph = new MultiRepoKnowledgeGraph(); + + if (repos) { + for (const repo of repos) { + await this.graph.addRepository(repo as any); + } + } else { + // Auto-detect repositories + const detected = await this.autoDetectRepositories(); + for (const repo of detected) { + await this.graph.addRepository(repo); + } + } + + await this.graph.analyzeConnections(); + } + + /** + * Generate Cursor-compatible Markdown with Mermaid diagrams + * Cursor has excellent built-in Mermaid support in Markdown preview + */ + async generateCursorViews(): Promise { + if (!this.graph || !this.graph.hasRepositories()) { + throw new Error('No repositories analyzed. Call initialize() first.'); + } + + const exporter = new MultiRepoMermaidExporter(this.graph.getExportableGraph()); + const generatedFiles: string[] = []; + + // Generate main overview file + const overviewPath = path.join(this.outputDir, 'multi-repo-overview.md'); + await exporter.exportForVSCode(overviewPath); + generatedFiles.push(overviewPath); + + // Generate API flow diagram for Cursor + const apiFlowPath = path.join(this.outputDir, 'multi-repo-api-flows.md'); + const apiFlowContent = await exporter.generateAPIFlowDiagram(); + await fsPromises.writeFile(apiFlowPath, '# Multi-Repo API Flows\n\n' + apiFlowContent); + generatedFiles.push(apiFlowPath); + + // Generate individual diagram files for better Cursor integration + const diagrams = await this.generateIndividualDiagrams(exporter); + generatedFiles.push(...diagrams); + + // Generate README with links to all diagrams + const readmePath = await this.generateCursorReadme(generatedFiles); + generatedFiles.push(readmePath); + + return generatedFiles; + } + + /** + * Generate individual diagram files for focused viewing + */ + private async generateIndividualDiagrams(exporter: MultiRepoMermaidExporter): Promise { + const files: string[] = []; + + // Overview diagram + const overviewPath = path.join(this.outputDir, 'diagram-overview.md'); + const overviewContent = await exporter.generateMultiRepoDiagram(); + fs.writeFileSync(overviewPath, `# Repository Overview\n\n${overviewContent}`); + files.push(overviewPath); + + // API Flow diagram + const apiFlowPath = path.join(this.outputDir, 'diagram-api-flow.md'); + const apiFlowContent = await exporter.generateAPIFlowDiagram(); + fs.writeFileSync(apiFlowPath, `# API Flow Diagram\n\n${apiFlowContent}`); + files.push(apiFlowPath); + + // Service Matrix diagram (method not yet implemented) + // const matrixPath = path.join(this.outputDir, 'diagram-service-matrix.md'); + // const matrixContent = await exporter.generateServiceMatrix(); + // fs.writeFileSync(matrixPath, `# Service Interaction Matrix\n\n${matrixContent}`); + // files.push(matrixPath); + + return files; + } + + /** + * Generate Cursor-specific README with navigation + */ + private async generateCursorReadme(generatedFiles: string[]): Promise { + const readmePath = path.join(this.outputDir, 'README.md'); + + const content = `# Multi-Repository Knowledge Graph - Cursor View + +## 🎯 Quick Navigation + +### Interactive Views +- [📊 Full Interactive Dashboard](./multi-repo-interactive.html) - Open in Cursor's preview +- [📝 Complete Analysis Report](./multi-repo-overview.md) - Best viewed in Cursor's Markdown preview + +### Individual Diagrams +- [🗺️ Repository Overview](./diagram-overview.md) - Overall repository structure +- [🔄 API Flow Diagram](./diagram-api-flow.md) - Frontend to backend connections +- [📈 Service Interaction Matrix](./diagram-service-matrix.md) - Connection density visualization + +## 💡 Viewing Tips for Cursor + +### Markdown Preview with Mermaid +1. Open any \`.md\` file from above +2. Press \`Cmd+Shift+V\` (Mac) or \`Ctrl+Shift+V\` (Windows/Linux) +3. Cursor will automatically render Mermaid diagrams + +### Split View +1. Open a diagram file +2. Press \`Cmd+\\\` (Mac) or \`Ctrl+\\\` (Windows/Linux) to split editor +3. Open preview in one pane, keep code in the other + +### Interactive HTML +1. Right-click on \`multi-repo-interactive.html\` +2. Select "Open with Live Server" or "Preview" +3. Interact with tabs and zoom controls + +## 📊 Current Statistics + +- **Repositories Analyzed**: ${this.graph?.getExportableGraph().repositories.length || 0} +- **Total Connections**: ${this.graph?.getExportableGraph().connections.length || 0} +- **Connection Types**: ${this.getConnectionTypes()} + +## 🔄 Refresh Data + +Run the following in Cursor's terminal: +\`\`\`bash +# Re-analyze all repositories +npx indexer multi-repo --analyze + +# Export fresh diagrams +npx indexer multi-repo --export cursor +\`\`\` + +## 🎨 Diagram Legend + +### Connection Types +- **Solid Arrow (→)**: API calls +- **Dashed Arrow (⇢)**: GraphQL operations +- **Thick Arrow (⇒)**: Event-based connections +- **Dotted Line (⋯)**: Import dependencies + +### Repository Colors +- 🟦 **Frontend**: Light blue (#61dafb) +- 🟩 **Backend**: Green (#68a063) +- 🟡 **Skills**: Yellow (#f1e05a) +- 🟥 **Data Ops**: Red (#f97583) +- 🟧 **Marketing**: Orange (#ff6b6b) + +## 🚀 Cursor-Specific Features + +### AI-Powered Analysis +Ask Cursor's AI about your architecture: +- "What are the main API dependencies?" +- "Which services have circular dependencies?" +- "How can I optimize the service connections?" + +### Quick Actions +- \`Cmd+P\` → Type "multi-repo" to find all generated files +- \`Cmd+Shift+F\` → Search across all diagram files +- Use Cursor's outline view to navigate large diagrams + +--- +Generated: ${new Date().toISOString()} +`; + + fs.writeFileSync(readmePath, content); + return readmePath; + } + + /** + * Get connection types summary + */ + private getConnectionTypes(): string { + if (!this.graph) return 'N/A'; + + const connections = this.graph.getExportableGraph().connections; + const types = new Set(connections.map(c => c.type)); + return Array.from(types).join(', '); + } + + /** + * Auto-detect repositories in workspace + */ + private async autoDetectRepositories(): Promise> { + const repos: Array = []; + const workspaceRoot = process.cwd(); + + // Common monorepo patterns + const patterns = [ + 'frontend', 'backend', 'skills', 'data-ops', 'marketing', + 'packages/*', 'services/*', 'apps/*', 'libs/*' + ]; + + for (const pattern of patterns) { + const searchPath = path.join(workspaceRoot, pattern); + + if (pattern.includes('*')) { + const dir = path.dirname(searchPath); + if (fs.existsSync(dir)) { + const subdirs = fs.readdirSync(dir, { withFileTypes: true }) + .filter(d => d.isDirectory()) + .map(d => path.join(dir, d.name)); + + for (const subdir of subdirs) { + repos.push({ + name: path.basename(subdir), + path: subdir, + type: this.detectRepoType(subdir) + }); + } + } + } else if (fs.existsSync(searchPath)) { + repos.push({ + name: path.basename(searchPath), + path: searchPath, + type: this.detectRepoType(searchPath) + }); + } + } + + return repos; + } + + /** + * Detect repository type + */ + private detectRepoType(repoPath: string): 'frontend' | 'backend' | 'skills' | 'data-ops' | 'marketing' | 'library' { + const name = path.basename(repoPath).toLowerCase(); + + if (name.includes('frontend') || name.includes('ui')) return 'frontend'; + if (name.includes('backend') || name.includes('api')) return 'backend'; + if (name.includes('skill')) return 'skills'; + if (name.includes('data')) return 'data-ops'; + if (name.includes('marketing')) return 'marketing'; + + // Check package.json + const pkgPath = path.join(repoPath, 'package.json'); + if (fs.existsSync(pkgPath)) { + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + if (pkg.dependencies?.react || pkg.dependencies?.vue) return 'frontend'; + if (pkg.dependencies?.express || pkg.dependencies?.fastify) return 'backend'; + } + + return 'backend'; + } + + /** + * Generate Cursor command palette commands + */ + generateCursorCommands(): string { + return `{ + "commands": [ + { + "command": "multi-repo.analyze", + "title": "Multi-Repo: Analyze Workspace", + "category": "Multi-Repo" + }, + { + "command": "multi-repo.view-overview", + "title": "Multi-Repo: View Overview Diagram", + "category": "Multi-Repo" + }, + { + "command": "multi-repo.view-api-flow", + "title": "Multi-Repo: View API Flow", + "category": "Multi-Repo" + }, + { + "command": "multi-repo.view-matrix", + "title": "Multi-Repo: View Service Matrix", + "category": "Multi-Repo" + }, + { + "command": "multi-repo.export-all", + "title": "Multi-Repo: Export All Diagrams", + "category": "Multi-Repo" + } + ], + "keybindings": [ + { + "command": "multi-repo.analyze", + "key": "cmd+shift+m a", + "when": "editorTextFocus" + }, + { + "command": "multi-repo.view-overview", + "key": "cmd+shift+m o", + "when": "editorTextFocus" + } + ] +}`; + } +} + +/** + * CLI Integration for Cursor + */ +export class CursorCLI { + static async run(args: string[]): Promise { + const command = args[0]; + const integration = new CursorMultiRepoIntegration(); + + switch (command) { + case 'analyze': + logger.info('🔍 Analyzing repositories...'); + await integration.initialize(); + logger.success(' Analysis complete'); + break; + + case 'export': + logger.info('📊 Generating Cursor-compatible views...'); + await integration.initialize(); + const files = await integration.generateCursorViews(); + logger.success(' Generated files:'); + files.forEach(f => logger.info(` - ${f}`)); + logger.info('\n💡 Tip: Open any .md file and press Cmd+Shift+V to preview with Mermaid support'); + break; + + case 'commands': + logger.info('📋 Cursor command configuration:'); + logger.info(integration.generateCursorCommands()); + logger.info('\n💡 Add this to your .cursor/settings.json'); + break; + + default: + logger.info(` +Cursor Multi-Repo Integration + +Usage: + cursor-multi-repo analyze Analyze workspace repositories + cursor-multi-repo export Generate Cursor-compatible diagrams + cursor-multi-repo commands Show Cursor command configuration + +Examples: + npx indexer cursor analyze + npx indexer cursor export + `); + } + } +} + +/** + * Cursor-specific helpers + */ +export const CursorHelpers = { + /** + * Check if running in Cursor + */ + isInCursor(): boolean { + return process.env.CURSOR_IDE === 'true' || + process.env.TERM_PROGRAM === 'cursor' || + fs.existsSync(path.join(process.cwd(), '.cursor')); + }, + + /** + * Get Cursor workspace path + */ + getCursorWorkspace(): string | null { + // Check for .cursor directory + const cursorDir = path.join(process.cwd(), '.cursor'); + if (fs.existsSync(cursorDir)) { + return process.cwd(); + } + + // Check environment variables + return process.env.CURSOR_WORKSPACE || process.env.PWD || null; + }, + + /** + * Open file in Cursor + */ + openInCursor(filePath: string): void { + const { exec } = require('child_process'); + exec(`cursor "${filePath}"`, (error: any) => { + if (error) { + logger.error('Failed to open in Cursor:', error); + // Fallback to code command + exec(`code "${filePath}"`); + } + }); + } +}; + +// Export for use in CLI export default CursorMultiRepoIntegration; \ No newline at end of file diff --git a/src/exporters/ascii.ts b/src/exporters/ascii.ts index 67decba..75437af 100644 --- a/src/exporters/ascii.ts +++ b/src/exporters/ascii.ts @@ -1,522 +1,522 @@ -import { ProjectIndex, Exporter } from '../types'; -import * as fs from 'fs/promises'; -import * as path from 'path'; - -export interface ASCIIOptions { - style?: 'tree' | 'graph' | 'table' | 'boxes'; - maxWidth?: number; - showStats?: boolean; - colorize?: boolean; - includeComplexity?: boolean; -} - -export class ASCIIExporter implements Exporter { - private readonly COLORS = { - reset: '\x1b[0m', - bright: '\x1b[1m', - dim: '\x1b[2m', - red: '\x1b[31m', - green: '\x1b[32m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - magenta: '\x1b[35m', - cyan: '\x1b[36m', - white: '\x1b[37m' - }; - - async export(index: ProjectIndex, outputPath: string, options: ASCIIOptions = {}): Promise { - const { - style = 'tree', - maxWidth = 120, - showStats = true, - colorize = false, - includeComplexity = true - } = options; - - const lines: string[] = []; - - // Add header - this.addHeader(lines, index, maxWidth); - - // Generate visualization based on style - switch (style) { - case 'tree': - this.generateTree(lines, index, options); - break; - case 'graph': - this.generateGraph(lines, index, options); - break; - case 'table': - this.generateTable(lines, index, options); - break; - case 'boxes': - this.generateBoxes(lines, index, options); - break; - } - - // Add statistics if requested - if (showStats) { - this.addStatistics(lines, index, maxWidth); - } - - // Ensure output directory exists - const dir = path.dirname(outputPath); - await fs.mkdir(dir, { recursive: true }); - - // Write to file (remove colors if not writing to terminal) - const content = colorize ? lines.join('\n') : this.stripColors(lines.join('\n')); - await fs.writeFile(outputPath, content, 'utf-8'); - } - - private addHeader(lines: string[], index: ProjectIndex, maxWidth: number): void { - const title = 'PROJECT DEPENDENCY VISUALIZATION'; - const border = '='.repeat(maxWidth); - - lines.push(border); - lines.push(this.center(title, maxWidth)); - lines.push(this.center(`Generated: ${new Date().toISOString()}`, maxWidth)); - lines.push(border); - lines.push(''); - } - - private generateTree(lines: string[], index: ProjectIndex, options: ASCIIOptions): void { - const { colorize = false, includeComplexity = true } = options; - - // Group files by directory - const tree = this.buildDirectoryTree(index); - - lines.push('Project Structure:'); - lines.push(''); - this.renderTree(lines, tree, '', true, index, { colorize, includeComplexity }); - } - - private generateGraph(lines: string[], index: ProjectIndex, options: ASCIIOptions): void { - const { maxWidth = 120, colorize = false } = options; - - lines.push('Dependency Graph:'); - lines.push(''); - - // Create simplified dependency map - const deps = new Map>(); - - for (const [filePath, fileData] of Object.entries(index.files)) { - const simpleName = this.getSimpleName(filePath); - - if (!deps.has(simpleName)) { - deps.set(simpleName, new Set()); - } - - if (fileData.dependencies) { - for (const dep of fileData.dependencies) { - if (dep.startsWith('.')) { - const depPath = this.resolveDependency(filePath, dep); - const depSimpleName = this.getSimpleName(depPath); - - if (depSimpleName !== simpleName) { - deps.get(simpleName)!.add(depSimpleName); - } - } - } - } - } - - // Render as ASCII graph - const nodes = Array.from(deps.keys()).slice(0, 20); // Limit for readability - const nodeWidth = Math.max(...nodes.map(n => n.length)) + 4; - const cols = Math.floor(maxWidth / nodeWidth); - - for (let i = 0; i < nodes.length; i += cols) { - const row = nodes.slice(i, i + cols); - - // Draw nodes - lines.push(' ' + row.map(n => this.drawBox(n, nodeWidth)).join(' ')); - - // Draw connections (simplified) - if (i + cols < nodes.length) { - const connections = row.map((n, idx) => { - const hasDeps = deps.get(n)!.size > 0; - return hasDeps ? ' '.repeat(Math.floor(nodeWidth / 2) - 1) + '|' + ' '.repeat(Math.floor(nodeWidth / 2)) : ' '.repeat(nodeWidth); - }).join(' '); - lines.push(' ' + connections); - } - } - - lines.push(''); - - // Show top dependencies - lines.push('Top Dependencies:'); - const sortedDeps = Array.from(deps.entries()) - .sort((a, b) => b[1].size - a[1].size) - .slice(0, 10); - - for (const [file, fileDeps] of sortedDeps) { - const arrow = colorize ? `${this.COLORS.cyan}-->${this.COLORS.reset}` : '-->'; - const depList = Array.from(fileDeps).slice(0, 3).join(', '); - lines.push(` ${file} ${arrow} ${depList}${fileDeps.size > 3 ? ` (+${fileDeps.size - 3} more)` : ''}`); - } - } - - private generateTable(lines: string[], index: ProjectIndex, options: ASCIIOptions): void { - const { maxWidth = 120, includeComplexity = true } = options; - - lines.push('File Statistics Table:'); - lines.push(''); - - // Prepare table data - const rows: Array<{ - file: string; - language: string; - functions: number; - classes: number; - imports: number; - complexity?: number; - }> = []; - - for (const [filePath, fileData] of Object.entries(index.files)) { - rows.push({ - file: path.basename(filePath), - language: this.detectLanguage(filePath), - functions: fileData.functions?.length || 0, - classes: fileData.classes?.length || 0, - imports: fileData.imports?.length || 0, - complexity: fileData.complexity - }); - } - - // Sort by complexity if available - if (includeComplexity) { - rows.sort((a, b) => (b.complexity || 0) - (a.complexity || 0)); - } - - // Calculate column widths - const cols = { - file: Math.min(40, Math.max(4, ...rows.map(r => r.file.length))), - language: 10, - functions: 9, - classes: 7, - imports: 7, - complexity: includeComplexity ? 10 : 0 - }; - - // Draw header - const header = this.padRight('File', cols.file) + ' | ' + - this.padRight('Language', cols.language) + ' | ' + - this.padRight('Functions', cols.functions) + ' | ' + - this.padRight('Classes', cols.classes) + ' | ' + - this.padRight('Imports', cols.imports) + - (includeComplexity ? ' | ' + this.padRight('Complexity', cols.complexity) : ''); - - const separator = '-'.repeat(header.length); - - lines.push(header); - lines.push(separator); - - // Draw rows (limit to top 20) - for (const row of rows.slice(0, 20)) { - const rowStr = this.padRight(this.truncate(row.file, cols.file), cols.file) + ' | ' + - this.padRight(row.language, cols.language) + ' | ' + - this.padRight(row.functions.toString(), cols.functions) + ' | ' + - this.padRight(row.classes.toString(), cols.classes) + ' | ' + - this.padRight(row.imports.toString(), cols.imports) + - (includeComplexity ? ' | ' + this.padRight((row.complexity || 0).toFixed(1), cols.complexity) : ''); - - lines.push(rowStr); - } - - if (rows.length > 20) { - lines.push(`... and ${rows.length - 20} more files`); - } - } - - private generateBoxes(lines: string[], index: ProjectIndex, options: ASCIIOptions): void { - const { maxWidth = 120, colorize = false } = options; - - lines.push('Service Architecture:'); - lines.push(''); - - // Group files by service - const services = this.groupByService(index); - - // Draw service boxes - const boxWidth = Math.min(40, Math.floor((maxWidth - 10) / 3)); - - for (const [service, files] of services) { - const box = this.drawServiceBox(service, files, boxWidth, colorize); - lines.push(...box); - lines.push(''); - } - - // Draw connections between services - lines.push('Service Dependencies:'); - lines.push(''); - - const serviceDeps = this.analyzeServiceDependencies(index); - for (const [from, tos] of serviceDeps) { - const arrow = colorize ? `${this.COLORS.green}===>${this.COLORS.reset}` : '===>'; - lines.push(` ${from} ${arrow} ${Array.from(tos).join(', ')}`); - } - } - - private addStatistics(lines: string[], index: ProjectIndex, maxWidth: number): void { - lines.push(''); - lines.push('='.repeat(maxWidth)); - lines.push('Statistics:'); - lines.push(''); - - const stats = index.statistics || {}; - - const statLines = [ - `Total Files: ${stats.totalFiles || 0}`, - `Total Functions: ${stats.totalFunctions || 0}`, - `Total Classes: ${stats.totalClasses || 0}`, - `Total Constants: ${stats.totalConstants || 0}`, - `Average Complexity: ${stats.avgComplexity?.toFixed(2) || 'N/A'}` - ]; - - // Language breakdown - if (stats.languages) { - lines.push('Languages:'); - for (const [lang, count] of Object.entries(stats.languages)) { - const percentage = ((count as number / (stats.totalFiles || 1)) * 100).toFixed(1); - const bar = this.drawProgressBar(count as number, stats.totalFiles || 1, 30); - lines.push(` ${this.padRight(lang + ':', 12)} ${bar} ${count} files (${percentage}%)`); - } - lines.push(''); - } - - for (const line of statLines) { - lines.push(line); - } - } - - private buildDirectoryTree(index: ProjectIndex): any { - const tree: any = {}; - - for (const filePath of Object.keys(index.files)) { - const parts = filePath.split('/'); - let current = tree; - - for (let i = 0; i < parts.length; i++) { - const part = parts[i]; - - if (i === parts.length - 1) { - // File - if (!current._files) current._files = []; - current._files.push(part); - } else { - // Directory - if (!current[part]) current[part] = {}; - current = current[part]; - } - } - } - - return tree; - } - - private renderTree( - lines: string[], - tree: any, - prefix: string, - isLast: boolean, - index: ProjectIndex, - options: { colorize?: boolean; includeComplexity?: boolean } - ): void { - const entries = Object.entries(tree).filter(([key]) => key !== '_files'); - const files = tree._files || []; - - // Render directories - entries.forEach(([name, subtree], idx) => { - const isLastEntry = idx === entries.length - 1 && files.length === 0; - const connector = isLastEntry ? '└── ' : '├── '; - const extension = isLastEntry ? ' ' : '│ '; - - const dirName = options.colorize ? `${this.COLORS.blue}${name}/${this.COLORS.reset}` : `${name}/`; - lines.push(prefix + connector + dirName); - - this.renderTree(lines, subtree, prefix + extension, isLastEntry, index, options); - }); - - // Render files - files.forEach((file: string, idx: number) => { - const isLastFile = idx === files.length - 1; - const connector = isLastFile ? '└── ' : '├── '; - - let fileName = file; - if (options.colorize) { - const ext = path.extname(file); - const color = this.getColorForExtension(ext); - fileName = `${color}${file}${this.COLORS.reset}`; - } - - lines.push(prefix + connector + fileName); - }); - } - - private drawBox(text: string, width: number): string { - const padded = this.center(text, width - 2); - return '+' + '-'.repeat(width - 2) + '+\n' + - '|' + padded + '|\n' + - '+' + '-'.repeat(width - 2) + '+'; - } - - private drawServiceBox(service: string, files: string[], width: number, colorize: boolean): string[] { - const lines: string[] = []; - const border = '+' + '-'.repeat(width - 2) + '+'; - - lines.push(border); - - const title = colorize ? `${this.COLORS.bright}${service.toUpperCase()}${this.COLORS.reset}` : service.toUpperCase(); - lines.push('|' + this.center(title, width - 2) + '|'); - lines.push('|' + '-'.repeat(width - 2) + '|'); - - // File counts by type - const typeCounts: Record = {}; - for (const file of files) { - const ext = path.extname(file); - typeCounts[ext] = (typeCounts[ext] || 0) + 1; - } - - for (const [ext, count] of Object.entries(typeCounts).slice(0, 5)) { - const line = `${ext}: ${count} files`; - lines.push('|' + this.padRight(' ' + line, width - 2) + '|'); - } - - lines.push('|' + ' '.repeat(width - 2) + '|'); - lines.push('|' + this.padRight(` Total: ${files.length} files`, width - 2) + '|'); - lines.push(border); - - return lines; - } - - private drawProgressBar(value: number, max: number, width: number): string { - const percentage = value / max; - const filled = Math.floor(percentage * width); - const empty = width - filled; - - return '[' + '#'.repeat(filled) + '-'.repeat(empty) + ']'; - } - - private groupByService(index: ProjectIndex): Map { - const services = new Map(); - - for (const filePath of Object.keys(index.files)) { - const service = this.detectService(filePath); - - if (!services.has(service)) { - services.set(service, []); - } - - services.get(service)!.push(filePath); - } - - return services; - } - - private analyzeServiceDependencies(index: ProjectIndex): Map> { - const deps = new Map>(); - - for (const [filePath, fileData] of Object.entries(index.files)) { - const fromService = this.detectService(filePath); - - if (!deps.has(fromService)) { - deps.set(fromService, new Set()); - } - - if (fileData.imports) { - for (const imp of fileData.imports) { - const source = typeof imp === 'string' ? imp : imp.source; - if (source && source.startsWith('.')) { - const depPath = this.resolveDependency(filePath, source); - const toService = this.detectService(depPath); - - if (toService !== fromService) { - deps.get(fromService)!.add(toService); - } - } - } - } - } - - return deps; - } - - private detectService(filePath: string): string { - if (filePath.includes('frontend/')) return 'Frontend'; - if (filePath.includes('backend/')) return 'Backend'; - if (filePath.includes('skills/')) return 'Skills'; - if (filePath.includes('data-ops/')) return 'DataOps'; - if (filePath.includes('marketing/')) return 'Marketing'; - return 'Other'; - } - - private detectLanguage(filePath: string): string { - const ext = path.extname(filePath); - const languages: Record = { - '.ts': 'TypeScript', - '.tsx': 'TypeScript', - '.js': 'JavaScript', - '.jsx': 'JavaScript', - '.py': 'Python', - '.go': 'Go', - '.sql': 'SQL', - '.graphql': 'GraphQL', - '.gql': 'GraphQL', - '.astro': 'Astro' - }; - return languages[ext] || 'Unknown'; - } - - private getColorForExtension(ext: string): string { - const colors: Record = { - '.ts': this.COLORS.blue, - '.tsx': this.COLORS.blue, - '.js': this.COLORS.yellow, - '.jsx': this.COLORS.yellow, - '.py': this.COLORS.green, - '.go': this.COLORS.cyan, - '.graphql': this.COLORS.magenta, - '.gql': this.COLORS.magenta, - '.astro': this.COLORS.red - }; - return colors[ext] || this.COLORS.white; - } - - private getSimpleName(filePath: string): string { - const parts = filePath.split('/'); - if (parts.length > 2) { - return `${parts[0]}/${path.basename(filePath)}`; - } - return path.basename(filePath); - } - - private resolveDependency(fromFile: string, dep: string): string { - if (!dep.startsWith('.')) { - return dep; - } - - const dir = path.dirname(fromFile); - const resolved = path.join(dir, dep); - return resolved.replace(/\\/g, '/'); - } - - private center(text: string, width: number): string { - const padding = Math.max(0, width - text.length); - const leftPad = Math.floor(padding / 2); - const rightPad = padding - leftPad; - return ' '.repeat(leftPad) + text + ' '.repeat(rightPad); - } - - private padRight(text: string, width: number): string { - return text + ' '.repeat(Math.max(0, width - text.length)); - } - - private truncate(text: string, maxLength: number): string { - if (text.length <= maxLength) return text; - return text.substring(0, maxLength - 3) + '...'; - } - - private stripColors(text: string): string { - return text.replace(/\x1b\[[0-9;]*m/g, ''); - } +import { ProjectIndex, Exporter } from '../types'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +export interface ASCIIOptions { + style?: 'tree' | 'graph' | 'table' | 'boxes'; + maxWidth?: number; + showStats?: boolean; + colorize?: boolean; + includeComplexity?: boolean; +} + +export class ASCIIExporter implements Exporter { + private readonly COLORS = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m' + }; + + async export(index: ProjectIndex, outputPath: string, options: ASCIIOptions = {}): Promise { + const { + style = 'tree', + maxWidth = 120, + showStats = true, + colorize = false, + includeComplexity = true + } = options; + + const lines: string[] = []; + + // Add header + this.addHeader(lines, index, maxWidth); + + // Generate visualization based on style + switch (style) { + case 'tree': + this.generateTree(lines, index, options); + break; + case 'graph': + this.generateGraph(lines, index, options); + break; + case 'table': + this.generateTable(lines, index, options); + break; + case 'boxes': + this.generateBoxes(lines, index, options); + break; + } + + // Add statistics if requested + if (showStats) { + this.addStatistics(lines, index, maxWidth); + } + + // Ensure output directory exists + const dir = path.dirname(outputPath); + await fs.mkdir(dir, { recursive: true }); + + // Write to file (remove colors if not writing to terminal) + const content = colorize ? lines.join('\n') : this.stripColors(lines.join('\n')); + await fs.writeFile(outputPath, content, 'utf-8'); + } + + private addHeader(lines: string[], index: ProjectIndex, maxWidth: number): void { + const title = 'PROJECT DEPENDENCY VISUALIZATION'; + const border = '='.repeat(maxWidth); + + lines.push(border); + lines.push(this.center(title, maxWidth)); + lines.push(this.center(`Generated: ${new Date().toISOString()}`, maxWidth)); + lines.push(border); + lines.push(''); + } + + private generateTree(lines: string[], index: ProjectIndex, options: ASCIIOptions): void { + const { colorize = false, includeComplexity = true } = options; + + // Group files by directory + const tree = this.buildDirectoryTree(index); + + lines.push('Project Structure:'); + lines.push(''); + this.renderTree(lines, tree, '', true, index, { colorize, includeComplexity }); + } + + private generateGraph(lines: string[], index: ProjectIndex, options: ASCIIOptions): void { + const { maxWidth = 120, colorize = false } = options; + + lines.push('Dependency Graph:'); + lines.push(''); + + // Create simplified dependency map + const deps = new Map>(); + + for (const [filePath, fileData] of Object.entries(index.files)) { + const simpleName = this.getSimpleName(filePath); + + if (!deps.has(simpleName)) { + deps.set(simpleName, new Set()); + } + + if (fileData.dependencies) { + for (const dep of fileData.dependencies) { + if (dep.startsWith('.')) { + const depPath = this.resolveDependency(filePath, dep); + const depSimpleName = this.getSimpleName(depPath); + + if (depSimpleName !== simpleName) { + deps.get(simpleName)!.add(depSimpleName); + } + } + } + } + } + + // Render as ASCII graph + const nodes = Array.from(deps.keys()).slice(0, 20); // Limit for readability + const nodeWidth = Math.max(...nodes.map(n => n.length)) + 4; + const cols = Math.floor(maxWidth / nodeWidth); + + for (let i = 0; i < nodes.length; i += cols) { + const row = nodes.slice(i, i + cols); + + // Draw nodes + lines.push(' ' + row.map(n => this.drawBox(n, nodeWidth)).join(' ')); + + // Draw connections (simplified) + if (i + cols < nodes.length) { + const connections = row.map((n, idx) => { + const hasDeps = deps.get(n)!.size > 0; + return hasDeps ? ' '.repeat(Math.floor(nodeWidth / 2) - 1) + '|' + ' '.repeat(Math.floor(nodeWidth / 2)) : ' '.repeat(nodeWidth); + }).join(' '); + lines.push(' ' + connections); + } + } + + lines.push(''); + + // Show top dependencies + lines.push('Top Dependencies:'); + const sortedDeps = Array.from(deps.entries()) + .sort((a, b) => b[1].size - a[1].size) + .slice(0, 10); + + for (const [file, fileDeps] of sortedDeps) { + const arrow = colorize ? `${this.COLORS.cyan}-->${this.COLORS.reset}` : '-->'; + const depList = Array.from(fileDeps).slice(0, 3).join(', '); + lines.push(` ${file} ${arrow} ${depList}${fileDeps.size > 3 ? ` (+${fileDeps.size - 3} more)` : ''}`); + } + } + + private generateTable(lines: string[], index: ProjectIndex, options: ASCIIOptions): void { + const { maxWidth = 120, includeComplexity = true } = options; + + lines.push('File Statistics Table:'); + lines.push(''); + + // Prepare table data + const rows: Array<{ + file: string; + language: string; + functions: number; + classes: number; + imports: number; + complexity?: number; + }> = []; + + for (const [filePath, fileData] of Object.entries(index.files)) { + rows.push({ + file: path.basename(filePath), + language: this.detectLanguage(filePath), + functions: fileData.functions?.length || 0, + classes: fileData.classes?.length || 0, + imports: fileData.imports?.length || 0, + complexity: fileData.complexity + }); + } + + // Sort by complexity if available + if (includeComplexity) { + rows.sort((a, b) => (b.complexity || 0) - (a.complexity || 0)); + } + + // Calculate column widths + const cols = { + file: Math.min(40, Math.max(4, ...rows.map(r => r.file.length))), + language: 10, + functions: 9, + classes: 7, + imports: 7, + complexity: includeComplexity ? 10 : 0 + }; + + // Draw header + const header = this.padRight('File', cols.file) + ' | ' + + this.padRight('Language', cols.language) + ' | ' + + this.padRight('Functions', cols.functions) + ' | ' + + this.padRight('Classes', cols.classes) + ' | ' + + this.padRight('Imports', cols.imports) + + (includeComplexity ? ' | ' + this.padRight('Complexity', cols.complexity) : ''); + + const separator = '-'.repeat(header.length); + + lines.push(header); + lines.push(separator); + + // Draw rows (limit to top 20) + for (const row of rows.slice(0, 20)) { + const rowStr = this.padRight(this.truncate(row.file, cols.file), cols.file) + ' | ' + + this.padRight(row.language, cols.language) + ' | ' + + this.padRight(row.functions.toString(), cols.functions) + ' | ' + + this.padRight(row.classes.toString(), cols.classes) + ' | ' + + this.padRight(row.imports.toString(), cols.imports) + + (includeComplexity ? ' | ' + this.padRight((row.complexity || 0).toFixed(1), cols.complexity) : ''); + + lines.push(rowStr); + } + + if (rows.length > 20) { + lines.push(`... and ${rows.length - 20} more files`); + } + } + + private generateBoxes(lines: string[], index: ProjectIndex, options: ASCIIOptions): void { + const { maxWidth = 120, colorize = false } = options; + + lines.push('Service Architecture:'); + lines.push(''); + + // Group files by service + const services = this.groupByService(index); + + // Draw service boxes + const boxWidth = Math.min(40, Math.floor((maxWidth - 10) / 3)); + + for (const [service, files] of services) { + const box = this.drawServiceBox(service, files, boxWidth, colorize); + lines.push(...box); + lines.push(''); + } + + // Draw connections between services + lines.push('Service Dependencies:'); + lines.push(''); + + const serviceDeps = this.analyzeServiceDependencies(index); + for (const [from, tos] of serviceDeps) { + const arrow = colorize ? `${this.COLORS.green}===>${this.COLORS.reset}` : '===>'; + lines.push(` ${from} ${arrow} ${Array.from(tos).join(', ')}`); + } + } + + private addStatistics(lines: string[], index: ProjectIndex, maxWidth: number): void { + lines.push(''); + lines.push('='.repeat(maxWidth)); + lines.push('Statistics:'); + lines.push(''); + + const stats = index.statistics || {}; + + const statLines = [ + `Total Files: ${stats.totalFiles || 0}`, + `Total Functions: ${stats.totalFunctions || 0}`, + `Total Classes: ${stats.totalClasses || 0}`, + `Total Constants: ${stats.totalConstants || 0}`, + `Average Complexity: ${stats.avgComplexity?.toFixed(2) || 'N/A'}` + ]; + + // Language breakdown + if (stats.languages) { + lines.push('Languages:'); + for (const [lang, count] of Object.entries(stats.languages)) { + const percentage = ((count as number / (stats.totalFiles || 1)) * 100).toFixed(1); + const bar = this.drawProgressBar(count as number, stats.totalFiles || 1, 30); + lines.push(` ${this.padRight(lang + ':', 12)} ${bar} ${count} files (${percentage}%)`); + } + lines.push(''); + } + + for (const line of statLines) { + lines.push(line); + } + } + + private buildDirectoryTree(index: ProjectIndex): any { + const tree: any = {}; + + for (const filePath of Object.keys(index.files)) { + const parts = filePath.split('/'); + let current = tree; + + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + + if (i === parts.length - 1) { + // File + if (!current._files) current._files = []; + current._files.push(part); + } else { + // Directory + if (!current[part]) current[part] = {}; + current = current[part]; + } + } + } + + return tree; + } + + private renderTree( + lines: string[], + tree: any, + prefix: string, + isLast: boolean, + index: ProjectIndex, + options: { colorize?: boolean; includeComplexity?: boolean } + ): void { + const entries = Object.entries(tree).filter(([key]) => key !== '_files'); + const files = tree._files || []; + + // Render directories + entries.forEach(([name, subtree], idx) => { + const isLastEntry = idx === entries.length - 1 && files.length === 0; + const connector = isLastEntry ? '└── ' : '├── '; + const extension = isLastEntry ? ' ' : '│ '; + + const dirName = options.colorize ? `${this.COLORS.blue}${name}/${this.COLORS.reset}` : `${name}/`; + lines.push(prefix + connector + dirName); + + this.renderTree(lines, subtree, prefix + extension, isLastEntry, index, options); + }); + + // Render files + files.forEach((file: string, idx: number) => { + const isLastFile = idx === files.length - 1; + const connector = isLastFile ? '└── ' : '├── '; + + let fileName = file; + if (options.colorize) { + const ext = path.extname(file); + const color = this.getColorForExtension(ext); + fileName = `${color}${file}${this.COLORS.reset}`; + } + + lines.push(prefix + connector + fileName); + }); + } + + private drawBox(text: string, width: number): string { + const padded = this.center(text, width - 2); + return '+' + '-'.repeat(width - 2) + '+\n' + + '|' + padded + '|\n' + + '+' + '-'.repeat(width - 2) + '+'; + } + + private drawServiceBox(service: string, files: string[], width: number, colorize: boolean): string[] { + const lines: string[] = []; + const border = '+' + '-'.repeat(width - 2) + '+'; + + lines.push(border); + + const title = colorize ? `${this.COLORS.bright}${service.toUpperCase()}${this.COLORS.reset}` : service.toUpperCase(); + lines.push('|' + this.center(title, width - 2) + '|'); + lines.push('|' + '-'.repeat(width - 2) + '|'); + + // File counts by type + const typeCounts: Record = {}; + for (const file of files) { + const ext = path.extname(file); + typeCounts[ext] = (typeCounts[ext] || 0) + 1; + } + + for (const [ext, count] of Object.entries(typeCounts).slice(0, 5)) { + const line = `${ext}: ${count} files`; + lines.push('|' + this.padRight(' ' + line, width - 2) + '|'); + } + + lines.push('|' + ' '.repeat(width - 2) + '|'); + lines.push('|' + this.padRight(` Total: ${files.length} files`, width - 2) + '|'); + lines.push(border); + + return lines; + } + + private drawProgressBar(value: number, max: number, width: number): string { + const percentage = value / max; + const filled = Math.floor(percentage * width); + const empty = width - filled; + + return '[' + '#'.repeat(filled) + '-'.repeat(empty) + ']'; + } + + private groupByService(index: ProjectIndex): Map { + const services = new Map(); + + for (const filePath of Object.keys(index.files)) { + const service = this.detectService(filePath); + + if (!services.has(service)) { + services.set(service, []); + } + + services.get(service)!.push(filePath); + } + + return services; + } + + private analyzeServiceDependencies(index: ProjectIndex): Map> { + const deps = new Map>(); + + for (const [filePath, fileData] of Object.entries(index.files)) { + const fromService = this.detectService(filePath); + + if (!deps.has(fromService)) { + deps.set(fromService, new Set()); + } + + if (fileData.imports) { + for (const imp of fileData.imports) { + const source = typeof imp === 'string' ? imp : imp.source; + if (source && source.startsWith('.')) { + const depPath = this.resolveDependency(filePath, source); + const toService = this.detectService(depPath); + + if (toService !== fromService) { + deps.get(fromService)!.add(toService); + } + } + } + } + } + + return deps; + } + + private detectService(filePath: string): string { + if (filePath.includes('frontend/')) return 'Frontend'; + if (filePath.includes('backend/')) return 'Backend'; + if (filePath.includes('skills/')) return 'Skills'; + if (filePath.includes('data-ops/')) return 'DataOps'; + if (filePath.includes('marketing/')) return 'Marketing'; + return 'Other'; + } + + private detectLanguage(filePath: string): string { + const ext = path.extname(filePath); + const languages: Record = { + '.ts': 'TypeScript', + '.tsx': 'TypeScript', + '.js': 'JavaScript', + '.jsx': 'JavaScript', + '.py': 'Python', + '.go': 'Go', + '.sql': 'SQL', + '.graphql': 'GraphQL', + '.gql': 'GraphQL', + '.astro': 'Astro' + }; + return languages[ext] || 'Unknown'; + } + + private getColorForExtension(ext: string): string { + const colors: Record = { + '.ts': this.COLORS.blue, + '.tsx': this.COLORS.blue, + '.js': this.COLORS.yellow, + '.jsx': this.COLORS.yellow, + '.py': this.COLORS.green, + '.go': this.COLORS.cyan, + '.graphql': this.COLORS.magenta, + '.gql': this.COLORS.magenta, + '.astro': this.COLORS.red + }; + return colors[ext] || this.COLORS.white; + } + + private getSimpleName(filePath: string): string { + const parts = filePath.split('/'); + if (parts.length > 2) { + return `${parts[0]}/${path.basename(filePath)}`; + } + return path.basename(filePath); + } + + private resolveDependency(fromFile: string, dep: string): string { + if (!dep.startsWith('.')) { + return dep; + } + + const dir = path.dirname(fromFile); + const resolved = path.join(dir, dep); + return resolved.replace(/\\/g, '/'); + } + + private center(text: string, width: number): string { + const padding = Math.max(0, width - text.length); + const leftPad = Math.floor(padding / 2); + const rightPad = padding - leftPad; + return ' '.repeat(leftPad) + text + ' '.repeat(rightPad); + } + + private padRight(text: string, width: number): string { + return text + ' '.repeat(Math.max(0, width - text.length)); + } + + private truncate(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + return text.substring(0, maxLength - 3) + '...'; + } + + private stripColors(text: string): string { + return text.replace(/\x1b\[[0-9;]*m/g, ''); + } } \ No newline at end of file diff --git a/src/exporters/graphviz.ts b/src/exporters/graphviz.ts index 6ef1f8a..86cb763 100644 --- a/src/exporters/graphviz.ts +++ b/src/exporters/graphviz.ts @@ -1,255 +1,255 @@ -import { ProjectIndex, Exporter } from '../types'; -import * as fs from 'fs/promises'; -import * as path from 'path'; - -export interface GraphVizOptions { - rankdir?: 'TB' | 'BT' | 'LR' | 'RL'; - includeExternals?: boolean; - groupByDirectory?: boolean; - colorScheme?: 'default' | 'monochrome' | 'colorful'; -} - -export class GraphVizExporter implements Exporter { - async export(index: ProjectIndex, outputPath: string, options: GraphVizOptions = {}): Promise { - const { - rankdir = 'LR', - includeExternals = false, - groupByDirectory = true, - colorScheme = 'default' - } = options; - - const lines: string[] = []; - - // Start digraph - lines.push('digraph Dependencies {'); - lines.push(` rankdir=${rankdir};`); - lines.push(' node [shape=box];'); - - // Apply color scheme - this.applyColorScheme(lines, colorScheme); - - // Track nodes and edges - const nodes = new Set(); - const edges: Array<[string, string]> = []; - - // Process each file - for (const [filePath, fileData] of Object.entries(index.files)) { - const nodeId = this.sanitizeNodeId(filePath); - nodes.add(nodeId); - - // Process dependencies - if (fileData.dependencies) { - for (const dep of fileData.dependencies) { - if (!includeExternals && !dep.startsWith('.')) { - continue; - } - - const depPath = this.resolveDependency(filePath, dep); - const depNodeId = this.sanitizeNodeId(depPath); - - if (depPath !== filePath) { - edges.push([nodeId, depNodeId]); - } - } - } - - // Process imports for more detailed dependencies - if (fileData.imports && Array.isArray(fileData.imports)) { - for (const imp of fileData.imports) { - // Handle both string imports and ImportInfo objects - const source = typeof imp === 'string' ? imp : imp.source; - if (!source || (!includeExternals && !source.startsWith('.'))) { - continue; - } - - const depPath = this.resolveDependency(filePath, source); - const depNodeId = this.sanitizeNodeId(depPath); - - if (depPath !== filePath && !edges.some(e => e[0] === nodeId && e[1] === depNodeId)) { - edges.push([nodeId, depNodeId]); - } - } - } - } - - // Group by directory if requested - if (groupByDirectory) { - const directories = this.groupFilesByDirectory(Array.from(nodes)); - - for (const [dir, files] of Object.entries(directories)) { - if (files.length > 1) { - lines.push(` subgraph "cluster_${this.sanitizeNodeId(dir)}" {`); - lines.push(` label="${dir}";`); - lines.push(' style=filled;'); - lines.push(' color=lightgrey;'); - - for (const file of files) { - const label = path.basename(file); - const nodeStyle = this.getNodeStyle(file, index.files[file]); - lines.push(` "${file}" [label="${label}"${nodeStyle}];`); - } - - lines.push(' }'); - } - } - - // Add standalone files - for (const nodeId of nodes) { - const dir = path.dirname(nodeId); - if (!directories[dir] || directories[dir].length <= 1) { - const label = path.basename(nodeId); - const nodeStyle = this.getNodeStyle(nodeId, index.files[nodeId]); - lines.push(` "${nodeId}" [label="${label}"${nodeStyle}];`); - } - } - } else { - // Add all nodes without grouping - for (const nodeId of nodes) { - const label = path.basename(nodeId); - const nodeStyle = this.getNodeStyle(nodeId, index.files[nodeId]); - lines.push(` "${nodeId}" [label="${label}"${nodeStyle}];`); - } - } - - // Add edges - for (const [from, to] of edges) { - const edgeStyle = this.getEdgeStyle(from, to, index); - lines.push(` "${from}" -> "${to}"${edgeStyle};`); - } - - // Add legend - this.addLegend(lines, colorScheme); - - // Close digraph - lines.push('}'); - - // Ensure output directory exists - const dir = path.dirname(outputPath); - await fs.mkdir(dir, { recursive: true }); - - // Write to file - await fs.writeFile(outputPath, lines.join('\n'), 'utf-8'); - } - - private sanitizeNodeId(id: string): string { - return id.replace(/[^a-zA-Z0-9_\/\.\-]/g, '_'); - } - - private resolveDependency(fromFile: string, dep: string): string { - if (!dep.startsWith('.')) { - return dep; - } - - const dir = path.dirname(fromFile); - const resolved = path.join(dir, dep); - return resolved.replace(/\\/g, '/'); - } - - private groupFilesByDirectory(files: string[]): Record { - const groups: Record = {}; - - for (const file of files) { - const dir = path.dirname(file); - if (!groups[dir]) { - groups[dir] = []; - } - groups[dir].push(file); - } - - return groups; - } - - private getNodeStyle(nodeId: string, fileData: any): string { - const styles: string[] = []; - - // Color by file type - if (nodeId.endsWith('.ts') || nodeId.endsWith('.tsx')) { - styles.push('fillcolor=lightblue', 'style=filled'); - } else if (nodeId.endsWith('.js') || nodeId.endsWith('.jsx')) { - styles.push('fillcolor=lightyellow', 'style=filled'); - } else if (nodeId.endsWith('.py')) { - styles.push('fillcolor=lightgreen', 'style=filled'); - } else if (nodeId.endsWith('.go')) { - styles.push('fillcolor=lightcyan', 'style=filled'); - } else if (nodeId.endsWith('.astro')) { - styles.push('fillcolor=orange', 'style=filled'); - } - - // Shape by content - if (fileData?.classes?.length > 0) { - styles.push('shape=component'); - } else if (fileData?.functions?.length > 5) { - styles.push('shape=box3d'); - } - - return styles.length > 0 ? `, ${styles.join(', ')}` : ''; - } - - private getEdgeStyle(from: string, to: string, index: ProjectIndex): string { - const styles: string[] = []; - - // Check for circular dependencies - if (this.hasCircularDependency(from, to, index)) { - styles.push('color=red', 'style=bold'); - } - - // Check for heavy dependencies (many imports) - const fromFile = index.files[from]; - if (fromFile?.imports && Array.isArray(fromFile.imports)) { - const heavyImports = fromFile.imports.filter(i => { - const source = typeof i === 'string' ? i : i.source; - return source && this.resolveDependency(from, source) === to; - }); - if (heavyImports.length > 3) { - styles.push('penwidth=2'); - } - } - - return styles.length > 0 ? ` [${styles.join(', ')}]` : ''; - } - - private hasCircularDependency(from: string, to: string, index: ProjectIndex): boolean { - const toFile = index.files[to]; - if (!toFile?.dependencies) return false; - - for (const dep of toFile.dependencies) { - const depPath = this.resolveDependency(to, dep); - if (depPath === from) { - return true; - } - } - - return false; - } - - private applyColorScheme(lines: string[], scheme: string): void { - switch (scheme) { - case 'monochrome': - lines.push(' node [fillcolor=white, style=filled, fontname="Arial"];'); - lines.push(' edge [color=black];'); - break; - case 'colorful': - lines.push(' node [style=filled, fontname="Arial Bold"];'); - lines.push(' edge [color=blue];'); - break; - default: - lines.push(' node [fontname="Arial"];'); - break; - } - } - - private addLegend(lines: string[], scheme: string): void { - lines.push(''); - lines.push(' subgraph cluster_legend {'); - lines.push(' label="Legend";'); - lines.push(' style=dotted;'); - lines.push(' node [shape=box, style=filled];'); - lines.push(' "TypeScript" [fillcolor=lightblue];'); - lines.push(' "JavaScript" [fillcolor=lightyellow];'); - lines.push(' "Python" [fillcolor=lightgreen];'); - lines.push(' "Go" [fillcolor=lightcyan];'); - lines.push(' "Astro" [fillcolor=orange];'); - lines.push(' "Circular Dep" [shape=point, color=red, style=bold];'); - lines.push(' }'); - } +import { ProjectIndex, Exporter } from '../types'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +export interface GraphVizOptions { + rankdir?: 'TB' | 'BT' | 'LR' | 'RL'; + includeExternals?: boolean; + groupByDirectory?: boolean; + colorScheme?: 'default' | 'monochrome' | 'colorful'; +} + +export class GraphVizExporter implements Exporter { + async export(index: ProjectIndex, outputPath: string, options: GraphVizOptions = {}): Promise { + const { + rankdir = 'LR', + includeExternals = false, + groupByDirectory = true, + colorScheme = 'default' + } = options; + + const lines: string[] = []; + + // Start digraph + lines.push('digraph Dependencies {'); + lines.push(` rankdir=${rankdir};`); + lines.push(' node [shape=box];'); + + // Apply color scheme + this.applyColorScheme(lines, colorScheme); + + // Track nodes and edges + const nodes = new Set(); + const edges: Array<[string, string]> = []; + + // Process each file + for (const [filePath, fileData] of Object.entries(index.files)) { + const nodeId = this.sanitizeNodeId(filePath); + nodes.add(nodeId); + + // Process dependencies + if (fileData.dependencies) { + for (const dep of fileData.dependencies) { + if (!includeExternals && !dep.startsWith('.')) { + continue; + } + + const depPath = this.resolveDependency(filePath, dep); + const depNodeId = this.sanitizeNodeId(depPath); + + if (depPath !== filePath) { + edges.push([nodeId, depNodeId]); + } + } + } + + // Process imports for more detailed dependencies + if (fileData.imports && Array.isArray(fileData.imports)) { + for (const imp of fileData.imports) { + // Handle both string imports and ImportInfo objects + const source = typeof imp === 'string' ? imp : imp.source; + if (!source || (!includeExternals && !source.startsWith('.'))) { + continue; + } + + const depPath = this.resolveDependency(filePath, source); + const depNodeId = this.sanitizeNodeId(depPath); + + if (depPath !== filePath && !edges.some(e => e[0] === nodeId && e[1] === depNodeId)) { + edges.push([nodeId, depNodeId]); + } + } + } + } + + // Group by directory if requested + if (groupByDirectory) { + const directories = this.groupFilesByDirectory(Array.from(nodes)); + + for (const [dir, files] of Object.entries(directories)) { + if (files.length > 1) { + lines.push(` subgraph "cluster_${this.sanitizeNodeId(dir)}" {`); + lines.push(` label="${dir}";`); + lines.push(' style=filled;'); + lines.push(' color=lightgrey;'); + + for (const file of files) { + const label = path.basename(file); + const nodeStyle = this.getNodeStyle(file, index.files[file]); + lines.push(` "${file}" [label="${label}"${nodeStyle}];`); + } + + lines.push(' }'); + } + } + + // Add standalone files + for (const nodeId of nodes) { + const dir = path.dirname(nodeId); + if (!directories[dir] || directories[dir].length <= 1) { + const label = path.basename(nodeId); + const nodeStyle = this.getNodeStyle(nodeId, index.files[nodeId]); + lines.push(` "${nodeId}" [label="${label}"${nodeStyle}];`); + } + } + } else { + // Add all nodes without grouping + for (const nodeId of nodes) { + const label = path.basename(nodeId); + const nodeStyle = this.getNodeStyle(nodeId, index.files[nodeId]); + lines.push(` "${nodeId}" [label="${label}"${nodeStyle}];`); + } + } + + // Add edges + for (const [from, to] of edges) { + const edgeStyle = this.getEdgeStyle(from, to, index); + lines.push(` "${from}" -> "${to}"${edgeStyle};`); + } + + // Add legend + this.addLegend(lines, colorScheme); + + // Close digraph + lines.push('}'); + + // Ensure output directory exists + const dir = path.dirname(outputPath); + await fs.mkdir(dir, { recursive: true }); + + // Write to file + await fs.writeFile(outputPath, lines.join('\n'), 'utf-8'); + } + + private sanitizeNodeId(id: string): string { + return id.replace(/[^a-zA-Z0-9_\/\.\-]/g, '_'); + } + + private resolveDependency(fromFile: string, dep: string): string { + if (!dep.startsWith('.')) { + return dep; + } + + const dir = path.dirname(fromFile); + const resolved = path.join(dir, dep); + return resolved.replace(/\\/g, '/'); + } + + private groupFilesByDirectory(files: string[]): Record { + const groups: Record = {}; + + for (const file of files) { + const dir = path.dirname(file); + if (!groups[dir]) { + groups[dir] = []; + } + groups[dir].push(file); + } + + return groups; + } + + private getNodeStyle(nodeId: string, fileData: any): string { + const styles: string[] = []; + + // Color by file type + if (nodeId.endsWith('.ts') || nodeId.endsWith('.tsx')) { + styles.push('fillcolor=lightblue', 'style=filled'); + } else if (nodeId.endsWith('.js') || nodeId.endsWith('.jsx')) { + styles.push('fillcolor=lightyellow', 'style=filled'); + } else if (nodeId.endsWith('.py')) { + styles.push('fillcolor=lightgreen', 'style=filled'); + } else if (nodeId.endsWith('.go')) { + styles.push('fillcolor=lightcyan', 'style=filled'); + } else if (nodeId.endsWith('.astro')) { + styles.push('fillcolor=orange', 'style=filled'); + } + + // Shape by content + if (fileData?.classes?.length > 0) { + styles.push('shape=component'); + } else if (fileData?.functions?.length > 5) { + styles.push('shape=box3d'); + } + + return styles.length > 0 ? `, ${styles.join(', ')}` : ''; + } + + private getEdgeStyle(from: string, to: string, index: ProjectIndex): string { + const styles: string[] = []; + + // Check for circular dependencies + if (this.hasCircularDependency(from, to, index)) { + styles.push('color=red', 'style=bold'); + } + + // Check for heavy dependencies (many imports) + const fromFile = index.files[from]; + if (fromFile?.imports && Array.isArray(fromFile.imports)) { + const heavyImports = fromFile.imports.filter(i => { + const source = typeof i === 'string' ? i : i.source; + return source && this.resolveDependency(from, source) === to; + }); + if (heavyImports.length > 3) { + styles.push('penwidth=2'); + } + } + + return styles.length > 0 ? ` [${styles.join(', ')}]` : ''; + } + + private hasCircularDependency(from: string, to: string, index: ProjectIndex): boolean { + const toFile = index.files[to]; + if (!toFile?.dependencies) return false; + + for (const dep of toFile.dependencies) { + const depPath = this.resolveDependency(to, dep); + if (depPath === from) { + return true; + } + } + + return false; + } + + private applyColorScheme(lines: string[], scheme: string): void { + switch (scheme) { + case 'monochrome': + lines.push(' node [fillcolor=white, style=filled, fontname="Arial"];'); + lines.push(' edge [color=black];'); + break; + case 'colorful': + lines.push(' node [style=filled, fontname="Arial Bold"];'); + lines.push(' edge [color=blue];'); + break; + default: + lines.push(' node [fontname="Arial"];'); + break; + } + } + + private addLegend(lines: string[], scheme: string): void { + lines.push(''); + lines.push(' subgraph cluster_legend {'); + lines.push(' label="Legend";'); + lines.push(' style=dotted;'); + lines.push(' node [shape=box, style=filled];'); + lines.push(' "TypeScript" [fillcolor=lightblue];'); + lines.push(' "JavaScript" [fillcolor=lightyellow];'); + lines.push(' "Python" [fillcolor=lightgreen];'); + lines.push(' "Go" [fillcolor=lightcyan];'); + lines.push(' "Astro" [fillcolor=orange];'); + lines.push(' "Circular Dep" [shape=point, color=red, style=bold];'); + lines.push(' }'); + } } \ No newline at end of file diff --git a/src/exporters/json.ts b/src/exporters/json.ts index 6546580..4be5898 100644 --- a/src/exporters/json.ts +++ b/src/exporters/json.ts @@ -1,514 +1,514 @@ -import { ProjectIndex, FunctionInfo, ClassInfo, FileIndex, Exporter } from '../types'; -import * as fs from 'fs/promises'; -import * as path from 'path'; - -export interface ExportOptions { - pretty?: boolean; - includeStatistics?: boolean; - includeMetadata?: boolean; - // Compression options - format?: 'standard' | 'compressed' | 'minified'; - compressionLevel?: 'minimal' | 'standard' | 'aggressive'; - tokenLimit?: number; - // Backward compatibility - includeCallGraph?: boolean; -} - -/** - * Unified JSON Exporter with standard, minified, and compressed formats - * Supports token-aware sizing for AI/LLM consumption - */ -export class JSONExporter implements Exporter { - private compressionLevel: 'minimal' | 'standard' | 'aggressive' = 'standard'; - private tokenLimit?: number; - - async export(index: ProjectIndex, outputPath: string, options: ExportOptions = {}): Promise { - const { - pretty = true, - includeStatistics = true, - includeMetadata = true, - format = 'standard', - compressionLevel = 'standard', - tokenLimit, - includeCallGraph = true - } = options; - - this.compressionLevel = compressionLevel; - this.tokenLimit = tokenLimit; - - let output: any; - - // Choose format based on options - if (format === 'compressed') { - output = this.createCompressedOutput(index, includeStatistics, includeCallGraph); - } else { - output = this.createStandardOutput(index, includeStatistics, includeMetadata, includeCallGraph); - } - - // Apply token limit if specified - if (tokenLimit) { - output = this.truncateToTokenLimit(output, tokenLimit); - } - - // Format JSON based on options - let jsonContent: string; - if (format === 'compressed' || format === 'minified') { - jsonContent = JSON.stringify(output); // No indentation for maximum compression - } else if (pretty) { - jsonContent = JSON.stringify(output, null, 2); - } else { - jsonContent = JSON.stringify(output); - } - - // Ensure output directory exists - const dir = path.dirname(outputPath); - await fs.mkdir(dir, { recursive: true }); - - // Write to file - await fs.writeFile(outputPath, jsonContent, 'utf-8'); - } - - /** - * Create standard format output (backward compatible) - */ - private createStandardOutput( - index: ProjectIndex, - includeStatistics: boolean, - includeMetadata: boolean, - includeCallGraph: boolean - ): any { - const output: any = { - version: index.version, - timestamp: index.timestamp, - projectRoot: index.projectRoot - }; - - // Add metadata if requested - if (includeMetadata) { - output.metadata = { - totalFiles: Object.keys(index.files).length, - totalFunctions: this.countTotalFunctions(index), - totalClasses: this.countTotalClasses(index), - totalImports: this.countTotalImports(index), - languages: this.getLanguages(index) - }; - } - - // Add files - output.files = index.files; - - // Add dependency graph if present - if (index.dependencyGraph) { - output.dependencyGraph = index.dependencyGraph; - } - - // Add call graph if present and requested - if (includeCallGraph && index.callGraph) { - output.callGraph = index.callGraph; - } - - // Add statistics if requested - if (includeStatistics && index.statistics) { - output.statistics = index.statistics; - } - - // Add monorepo info if present - if (index.monorepo) { - output.monorepo = index.monorepo; - } - - return output; - } - - /** - * Create compressed format output with abbreviated keys - */ - private createCompressedOutput( - index: ProjectIndex, - includeStatistics: boolean, - includeCallGraph: boolean - ): any { - const compressed: any = { - v: index.version, - t: index.timestamp, - root: index.projectRoot - }; - - // Compress files - if (index.files && Object.keys(index.files).length > 0) { - compressed.f = this.compressFiles(index.files); - } - - // Compress call graph - if (includeCallGraph && index.callGraph) { - compressed.g = this.compressCallGraph(index.callGraph); - } - - // Compress dependency graph - if (index.dependencyGraph && Object.keys(index.dependencyGraph).length > 0) { - compressed.deps = this.compressDependencyGraph(index.dependencyGraph); - } - - // Compress statistics - if (includeStatistics && index.statistics) { - compressed.s = this.compressStatistics(index.statistics); - } - - // Compress monorepo info - if (index.monorepo) { - compressed.m = this.compressMonorepo(index.monorepo); - } - - return compressed; - } - - /** - * Compress files section - */ - private compressFiles(files: Record): any { - const compressed: any = {}; - - for (const [filePath, fileIndex] of Object.entries(files)) { - const shortPath = this.abbreviatePath(filePath); - - if (this.compressionLevel === 'aggressive') { - // Ultra-compressed: minimal info - compressed[shortPath] = [ - fileIndex.language.substring(0, 2), - fileIndex.functions?.length || 0, - fileIndex.classes?.length || 0 - ]; - } else { - // Standard compression: include more details - compressed[shortPath] = [ - fileIndex.language.substring(0, 2), - this.compressFunctions(fileIndex.functions || []), - this.compressClasses(fileIndex.classes || []), - fileIndex.imports?.length || 0, - fileIndex.exports?.length || 0 - ]; - } - } - - return compressed; - } - - /** - * Compress functions into compact format - */ - private compressFunctions(functions: FunctionInfo[]): any[] { - if (this.compressionLevel === 'aggressive') { - return functions.map(f => `${f.name}:${f.startLine}`); - } - - return functions.map(f => { - const compressed: any = [ - f.name, - f.startLine, - f.endLine - ]; - - if (f.parameters && f.parameters.length > 0) { - compressed.push(f.parameters.map(p => p.name).join(',')); - } - - if (f.calls && f.calls.length > 0) { - compressed.push(f.calls); - } - - return compressed; - }); - } - - /** - * Compress classes into compact format - */ - private compressClasses(classes: ClassInfo[]): any[] { - if (this.compressionLevel === 'aggressive') { - return classes.map(c => c.name); - } - - return classes.map(c => ({ - n: c.name, - m: c.methods.map(m => m.name), - p: c.properties.map(p => p.name), - e: c.extends, - i: c.implements - })); - } - - /** - * Compress call graph - */ - private compressCallGraph(callGraph: any): any { - const compressed: any = {}; - - if (callGraph.calls) { - compressed.c = Object.entries(callGraph.calls) - .filter(([_, calls]) => (calls as string[]).length > 0) - .map(([func, calls]) => [func, calls]); - } - - if (callGraph.entryPoints) { - compressed.e = callGraph.entryPoints; - } - - if (callGraph.deadCode && callGraph.deadCode.length > 0) { - compressed.d = callGraph.deadCode; - } - - if (callGraph.circularDependencies && callGraph.circularDependencies.length > 0) { - compressed.x = callGraph.circularDependencies; - } - - return compressed; - } - - /** - * Compress dependency graph - */ - private compressDependencyGraph(depGraph: Record): any { - return Object.entries(depGraph) - .filter(([_, deps]) => deps.length > 0) - .map(([file, deps]) => [ - this.abbreviatePath(file), - deps.map(d => this.abbreviatePath(d)) - ]); - } - - /** - * Compress statistics - */ - private compressStatistics(stats: any): any { - const compressed: any = { - f: stats.totalFiles, - fn: stats.totalFunctions, - c: stats.totalClasses - }; - - if (stats.languages) { - compressed.l = stats.languages; - } - - if (stats.callGraphStats) { - compressed.cg = { - tc: stats.callGraphStats.totalCalls, - ac: stats.callGraphStats.averageCallsPerFunction, - md: stats.callGraphStats.maxCallDepth, - dc: stats.callGraphStats.deadCodeCount - }; - } - - return compressed; - } - - /** - * Compress monorepo information - */ - private compressMonorepo(monorepo: any): any { - const compressed: any = {}; - - if (monorepo.services) { - compressed.s = Object.entries(monorepo.services).map(([name, info]: [string, any]) => ({ - n: name, - p: this.abbreviatePath(info.path), - f: info.files, - l: info.language - })); - } - - if (monorepo.crossServiceDependencies) { - compressed.d = monorepo.crossServiceDependencies.map((dep: any) => [ - dep.from, - dep.to, - dep.type - ]); - } - - return compressed; - } - - /** - * Abbreviate file paths by removing common prefixes - */ - private abbreviatePath(filePath: string): string { - const prefixesToRemove = [ - 'src/', - 'lib/', - 'app/', - 'components/', - 'pages/', - 'api/', - 'utils/', - 'services/', - 'models/', - 'controllers/' - ]; - - let abbreviated = filePath; - for (const prefix of prefixesToRemove) { - if (abbreviated.startsWith(prefix)) { - abbreviated = abbreviated.substring(prefix.length); - break; - } - } - - // Further abbreviate file extensions in aggressive mode - if (this.compressionLevel === 'aggressive') { - abbreviated = abbreviated - .replace('.test.ts', '.t') - .replace('.spec.ts', '.s') - .replace('.test.js', '.tj') - .replace('.spec.js', '.sj') - .replace('.tsx', '.x') - .replace('.jsx', '.jx') - .replace('.ts', '.t') - .replace('.js', '.j') - .replace('.py', '.p') - .replace('.go', '.g'); - } - - return abbreviated; - } - - /** - * Estimate token count for a JSON string (rough estimation) - */ - private estimateTokenCount(json: string): number { - // Rough estimation: 1 token ≈ 4 characters - return Math.ceil(json.length / 4); - } - - /** - * Truncate compressed data to fit within token limit - */ - private truncateToTokenLimit(compressed: any, tokenLimit: number): any { - let json = JSON.stringify(compressed); - let currentTokens = this.estimateTokenCount(json); - - if (currentTokens <= tokenLimit) { - return compressed; - } - - const truncated = { ...compressed }; - - // Progressive truncation strategy - // Step 1: Remove dead code info - if (truncated.g?.d) { - delete truncated.g.d; - json = JSON.stringify(truncated); - currentTokens = this.estimateTokenCount(json); - if (currentTokens <= tokenLimit) return truncated; - } - - // Step 2: Remove circular dependencies - if (truncated.g?.x) { - delete truncated.g.x; - json = JSON.stringify(truncated); - currentTokens = this.estimateTokenCount(json); - if (currentTokens <= tokenLimit) return truncated; - } - - // Step 3: Truncate file details - if (truncated.f) { - const files = truncated.f; - for (const filePath in files) { - const fileData = files[filePath]; - if (Array.isArray(fileData) && fileData.length > 2) { - files[filePath] = [ - fileData[0], - fileData[1]?.slice(0, 10) || [], - fileData[2]?.slice(0, 5) || [] - ]; - } - } - json = JSON.stringify(truncated); - currentTokens = this.estimateTokenCount(json); - if (currentTokens <= tokenLimit) return truncated; - } - - // Step 4: Remove monorepo info - if (truncated.m) { - delete truncated.m; - json = JSON.stringify(truncated); - currentTokens = this.estimateTokenCount(json); - if (currentTokens <= tokenLimit) return truncated; - } - - // Step 5: Aggressive truncation - const essential = { - v: truncated.v, - t: truncated.t, - root: truncated.root, - s: truncated.s, - f: Object.keys(truncated.f || {}).reduce((acc: any, key: string) => { - const fileData = truncated.f[key]; - acc[key] = [ - fileData[0], - Array.isArray(fileData[1]) ? fileData[1].length : 0, - Array.isArray(fileData[2]) ? fileData[2].length : 0 - ]; - return acc; - }, {}) - }; - - return essential; - } - - /** - * Get compression statistics - */ - public getCompressionStats(index: ProjectIndex): { - originalSize: number; - compressedSize: number; - compressionRatio: number; - savedBytes: number; - savedPercentage: number; - } { - const originalJson = JSON.stringify(this.createStandardOutput(index, true, true, true), null, 2); - const compressedJson = JSON.stringify(this.createCompressedOutput(index, true, true)); - - const originalSize = originalJson.length; - const compressedSize = compressedJson.length; - const savedBytes = originalSize - compressedSize; - const compressionRatio = originalSize / compressedSize; - const savedPercentage = (savedBytes / originalSize) * 100; - - return { - originalSize, - compressedSize, - compressionRatio, - savedBytes, - savedPercentage - }; - } - - // Helper methods from original exporter - private countTotalFunctions(index: ProjectIndex): number { - return Object.values(index.files).reduce((total, file) => - total + (file.functions?.length || 0), 0 - ); - } - - private countTotalClasses(index: ProjectIndex): number { - return Object.values(index.files).reduce((total, file) => - total + (file.classes?.length || 0), 0 - ); - } - - private countTotalImports(index: ProjectIndex): number { - return Object.values(index.files).reduce((total, file) => - total + (file.imports?.length || 0), 0 - ); - } - - private getLanguages(index: ProjectIndex): string[] { - const languages = new Set(); - Object.values(index.files).forEach(file => { - if (file.language) { - languages.add(file.language); - } - }); - return Array.from(languages); - } +import { ProjectIndex, FunctionInfo, ClassInfo, FileIndex, Exporter } from '../types'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +export interface ExportOptions { + pretty?: boolean; + includeStatistics?: boolean; + includeMetadata?: boolean; + // Compression options + format?: 'standard' | 'compressed' | 'minified'; + compressionLevel?: 'minimal' | 'standard' | 'aggressive'; + tokenLimit?: number; + // Backward compatibility + includeCallGraph?: boolean; +} + +/** + * Unified JSON Exporter with standard, minified, and compressed formats + * Supports token-aware sizing for AI/LLM consumption + */ +export class JSONExporter implements Exporter { + private compressionLevel: 'minimal' | 'standard' | 'aggressive' = 'standard'; + private tokenLimit?: number; + + async export(index: ProjectIndex, outputPath: string, options: ExportOptions = {}): Promise { + const { + pretty = true, + includeStatistics = true, + includeMetadata = true, + format = 'standard', + compressionLevel = 'standard', + tokenLimit, + includeCallGraph = true + } = options; + + this.compressionLevel = compressionLevel; + this.tokenLimit = tokenLimit; + + let output: any; + + // Choose format based on options + if (format === 'compressed') { + output = this.createCompressedOutput(index, includeStatistics, includeCallGraph); + } else { + output = this.createStandardOutput(index, includeStatistics, includeMetadata, includeCallGraph); + } + + // Apply token limit if specified + if (tokenLimit) { + output = this.truncateToTokenLimit(output, tokenLimit); + } + + // Format JSON based on options + let jsonContent: string; + if (format === 'compressed' || format === 'minified') { + jsonContent = JSON.stringify(output); // No indentation for maximum compression + } else if (pretty) { + jsonContent = JSON.stringify(output, null, 2); + } else { + jsonContent = JSON.stringify(output); + } + + // Ensure output directory exists + const dir = path.dirname(outputPath); + await fs.mkdir(dir, { recursive: true }); + + // Write to file + await fs.writeFile(outputPath, jsonContent, 'utf-8'); + } + + /** + * Create standard format output (backward compatible) + */ + private createStandardOutput( + index: ProjectIndex, + includeStatistics: boolean, + includeMetadata: boolean, + includeCallGraph: boolean + ): any { + const output: any = { + version: index.version, + timestamp: index.timestamp, + projectRoot: index.projectRoot + }; + + // Add metadata if requested + if (includeMetadata) { + output.metadata = { + totalFiles: Object.keys(index.files).length, + totalFunctions: this.countTotalFunctions(index), + totalClasses: this.countTotalClasses(index), + totalImports: this.countTotalImports(index), + languages: this.getLanguages(index) + }; + } + + // Add files + output.files = index.files; + + // Add dependency graph if present + if (index.dependencyGraph) { + output.dependencyGraph = index.dependencyGraph; + } + + // Add call graph if present and requested + if (includeCallGraph && index.callGraph) { + output.callGraph = index.callGraph; + } + + // Add statistics if requested + if (includeStatistics && index.statistics) { + output.statistics = index.statistics; + } + + // Add monorepo info if present + if (index.monorepo) { + output.monorepo = index.monorepo; + } + + return output; + } + + /** + * Create compressed format output with abbreviated keys + */ + private createCompressedOutput( + index: ProjectIndex, + includeStatistics: boolean, + includeCallGraph: boolean + ): any { + const compressed: any = { + v: index.version, + t: index.timestamp, + root: index.projectRoot + }; + + // Compress files + if (index.files && Object.keys(index.files).length > 0) { + compressed.f = this.compressFiles(index.files); + } + + // Compress call graph + if (includeCallGraph && index.callGraph) { + compressed.g = this.compressCallGraph(index.callGraph); + } + + // Compress dependency graph + if (index.dependencyGraph && Object.keys(index.dependencyGraph).length > 0) { + compressed.deps = this.compressDependencyGraph(index.dependencyGraph); + } + + // Compress statistics + if (includeStatistics && index.statistics) { + compressed.s = this.compressStatistics(index.statistics); + } + + // Compress monorepo info + if (index.monorepo) { + compressed.m = this.compressMonorepo(index.monorepo); + } + + return compressed; + } + + /** + * Compress files section + */ + private compressFiles(files: Record): any { + const compressed: any = {}; + + for (const [filePath, fileIndex] of Object.entries(files)) { + const shortPath = this.abbreviatePath(filePath); + + if (this.compressionLevel === 'aggressive') { + // Ultra-compressed: minimal info + compressed[shortPath] = [ + fileIndex.language.substring(0, 2), + fileIndex.functions?.length || 0, + fileIndex.classes?.length || 0 + ]; + } else { + // Standard compression: include more details + compressed[shortPath] = [ + fileIndex.language.substring(0, 2), + this.compressFunctions(fileIndex.functions || []), + this.compressClasses(fileIndex.classes || []), + fileIndex.imports?.length || 0, + fileIndex.exports?.length || 0 + ]; + } + } + + return compressed; + } + + /** + * Compress functions into compact format + */ + private compressFunctions(functions: FunctionInfo[]): any[] { + if (this.compressionLevel === 'aggressive') { + return functions.map(f => `${f.name}:${f.startLine}`); + } + + return functions.map(f => { + const compressed: any = [ + f.name, + f.startLine, + f.endLine + ]; + + if (f.parameters && f.parameters.length > 0) { + compressed.push(f.parameters.map(p => p.name).join(',')); + } + + if (f.calls && f.calls.length > 0) { + compressed.push(f.calls); + } + + return compressed; + }); + } + + /** + * Compress classes into compact format + */ + private compressClasses(classes: ClassInfo[]): any[] { + if (this.compressionLevel === 'aggressive') { + return classes.map(c => c.name); + } + + return classes.map(c => ({ + n: c.name, + m: c.methods.map(m => m.name), + p: c.properties.map(p => p.name), + e: c.extends, + i: c.implements + })); + } + + /** + * Compress call graph + */ + private compressCallGraph(callGraph: any): any { + const compressed: any = {}; + + if (callGraph.calls) { + compressed.c = Object.entries(callGraph.calls) + .filter(([_, calls]) => (calls as string[]).length > 0) + .map(([func, calls]) => [func, calls]); + } + + if (callGraph.entryPoints) { + compressed.e = callGraph.entryPoints; + } + + if (callGraph.deadCode && callGraph.deadCode.length > 0) { + compressed.d = callGraph.deadCode; + } + + if (callGraph.circularDependencies && callGraph.circularDependencies.length > 0) { + compressed.x = callGraph.circularDependencies; + } + + return compressed; + } + + /** + * Compress dependency graph + */ + private compressDependencyGraph(depGraph: Record): any { + return Object.entries(depGraph) + .filter(([_, deps]) => deps.length > 0) + .map(([file, deps]) => [ + this.abbreviatePath(file), + deps.map(d => this.abbreviatePath(d)) + ]); + } + + /** + * Compress statistics + */ + private compressStatistics(stats: any): any { + const compressed: any = { + f: stats.totalFiles, + fn: stats.totalFunctions, + c: stats.totalClasses + }; + + if (stats.languages) { + compressed.l = stats.languages; + } + + if (stats.callGraphStats) { + compressed.cg = { + tc: stats.callGraphStats.totalCalls, + ac: stats.callGraphStats.averageCallsPerFunction, + md: stats.callGraphStats.maxCallDepth, + dc: stats.callGraphStats.deadCodeCount + }; + } + + return compressed; + } + + /** + * Compress monorepo information + */ + private compressMonorepo(monorepo: any): any { + const compressed: any = {}; + + if (monorepo.services) { + compressed.s = Object.entries(monorepo.services).map(([name, info]: [string, any]) => ({ + n: name, + p: this.abbreviatePath(info.path), + f: info.files, + l: info.language + })); + } + + if (monorepo.crossServiceDependencies) { + compressed.d = monorepo.crossServiceDependencies.map((dep: any) => [ + dep.from, + dep.to, + dep.type + ]); + } + + return compressed; + } + + /** + * Abbreviate file paths by removing common prefixes + */ + private abbreviatePath(filePath: string): string { + const prefixesToRemove = [ + 'src/', + 'lib/', + 'app/', + 'components/', + 'pages/', + 'api/', + 'utils/', + 'services/', + 'models/', + 'controllers/' + ]; + + let abbreviated = filePath; + for (const prefix of prefixesToRemove) { + if (abbreviated.startsWith(prefix)) { + abbreviated = abbreviated.substring(prefix.length); + break; + } + } + + // Further abbreviate file extensions in aggressive mode + if (this.compressionLevel === 'aggressive') { + abbreviated = abbreviated + .replace('.test.ts', '.t') + .replace('.spec.ts', '.s') + .replace('.test.js', '.tj') + .replace('.spec.js', '.sj') + .replace('.tsx', '.x') + .replace('.jsx', '.jx') + .replace('.ts', '.t') + .replace('.js', '.j') + .replace('.py', '.p') + .replace('.go', '.g'); + } + + return abbreviated; + } + + /** + * Estimate token count for a JSON string (rough estimation) + */ + private estimateTokenCount(json: string): number { + // Rough estimation: 1 token ≈ 4 characters + return Math.ceil(json.length / 4); + } + + /** + * Truncate compressed data to fit within token limit + */ + private truncateToTokenLimit(compressed: any, tokenLimit: number): any { + let json = JSON.stringify(compressed); + let currentTokens = this.estimateTokenCount(json); + + if (currentTokens <= tokenLimit) { + return compressed; + } + + const truncated = { ...compressed }; + + // Progressive truncation strategy + // Step 1: Remove dead code info + if (truncated.g?.d) { + delete truncated.g.d; + json = JSON.stringify(truncated); + currentTokens = this.estimateTokenCount(json); + if (currentTokens <= tokenLimit) return truncated; + } + + // Step 2: Remove circular dependencies + if (truncated.g?.x) { + delete truncated.g.x; + json = JSON.stringify(truncated); + currentTokens = this.estimateTokenCount(json); + if (currentTokens <= tokenLimit) return truncated; + } + + // Step 3: Truncate file details + if (truncated.f) { + const files = truncated.f; + for (const filePath in files) { + const fileData = files[filePath]; + if (Array.isArray(fileData) && fileData.length > 2) { + files[filePath] = [ + fileData[0], + fileData[1]?.slice(0, 10) || [], + fileData[2]?.slice(0, 5) || [] + ]; + } + } + json = JSON.stringify(truncated); + currentTokens = this.estimateTokenCount(json); + if (currentTokens <= tokenLimit) return truncated; + } + + // Step 4: Remove monorepo info + if (truncated.m) { + delete truncated.m; + json = JSON.stringify(truncated); + currentTokens = this.estimateTokenCount(json); + if (currentTokens <= tokenLimit) return truncated; + } + + // Step 5: Aggressive truncation + const essential = { + v: truncated.v, + t: truncated.t, + root: truncated.root, + s: truncated.s, + f: Object.keys(truncated.f || {}).reduce((acc: any, key: string) => { + const fileData = truncated.f[key]; + acc[key] = [ + fileData[0], + Array.isArray(fileData[1]) ? fileData[1].length : 0, + Array.isArray(fileData[2]) ? fileData[2].length : 0 + ]; + return acc; + }, {}) + }; + + return essential; + } + + /** + * Get compression statistics + */ + public getCompressionStats(index: ProjectIndex): { + originalSize: number; + compressedSize: number; + compressionRatio: number; + savedBytes: number; + savedPercentage: number; + } { + const originalJson = JSON.stringify(this.createStandardOutput(index, true, true, true), null, 2); + const compressedJson = JSON.stringify(this.createCompressedOutput(index, true, true)); + + const originalSize = originalJson.length; + const compressedSize = compressedJson.length; + const savedBytes = originalSize - compressedSize; + const compressionRatio = originalSize / compressedSize; + const savedPercentage = (savedBytes / originalSize) * 100; + + return { + originalSize, + compressedSize, + compressionRatio, + savedBytes, + savedPercentage + }; + } + + // Helper methods from original exporter + private countTotalFunctions(index: ProjectIndex): number { + return Object.values(index.files).reduce((total, file) => + total + (file.functions?.length || 0), 0 + ); + } + + private countTotalClasses(index: ProjectIndex): number { + return Object.values(index.files).reduce((total, file) => + total + (file.classes?.length || 0), 0 + ); + } + + private countTotalImports(index: ProjectIndex): number { + return Object.values(index.files).reduce((total, file) => + total + (file.imports?.length || 0), 0 + ); + } + + private getLanguages(index: ProjectIndex): string[] { + const languages = new Set(); + Object.values(index.files).forEach(file => { + if (file.language) { + languages.add(file.language); + } + }); + return Array.from(languages); + } } \ No newline at end of file diff --git a/src/exporters/markdown.ts b/src/exporters/markdown.ts index c0244b6..af85ba3 100644 --- a/src/exporters/markdown.ts +++ b/src/exporters/markdown.ts @@ -1,391 +1,391 @@ -import { ProjectIndex, FileInfo, Exporter } from '../types'; -import * as fs from 'fs/promises'; -import * as path from 'path'; - -export interface MarkdownOptions { - includeTableOfContents?: boolean; - includeStatistics?: boolean; - includeDependencyGraph?: boolean; - includeFileDetails?: boolean; - sortBy?: 'name' | 'type' | 'size' | 'complexity'; -} - -export class MarkdownExporter implements Exporter { - async export(index: ProjectIndex, outputPath: string, options: MarkdownOptions = {}): Promise { - const { - includeTableOfContents = true, - includeStatistics = true, - includeDependencyGraph = true, - includeFileDetails = true, - sortBy = 'name' - } = options; - - const lines: string[] = []; - - // Header - lines.push('# Project Index Report'); - lines.push(''); - lines.push(`**Generated:** ${new Date(index.timestamp).toLocaleString()}`); - lines.push(`**Version:** ${index.version}`); - lines.push(`**Project Root:** \`${index.projectRoot}\``); - lines.push(''); - - // Table of Contents - if (includeTableOfContents) { - lines.push('## Table of Contents'); - lines.push(''); - if (includeStatistics) lines.push('- [Statistics](#statistics)'); - if (index.monorepo) lines.push('- [Monorepo Structure](#monorepo-structure)'); - if (includeFileDetails) lines.push('- [File Index](#file-index)'); - if (includeDependencyGraph) lines.push('- [Dependency Analysis](#dependency-analysis)'); - lines.push('- [Language Breakdown](#language-breakdown)'); - lines.push(''); - } - - // Statistics - if (includeStatistics) { - lines.push('## Statistics'); - lines.push(''); - - const stats = this.calculateStatistics(index); - - lines.push('| Metric | Value |'); - lines.push('|--------|-------|'); - lines.push(`| Total Files | ${stats.totalFiles} |`); - lines.push(`| Total Lines | ${stats.totalLines} |`); - lines.push(`| Total Functions | ${stats.totalFunctions} |`); - lines.push(`| Total Classes | ${stats.totalClasses} |`); - lines.push(`| Total Imports | ${stats.totalImports} |`); - lines.push(`| Total Exports | ${stats.totalExports} |`); - lines.push(`| Languages | ${stats.languages.join(', ')} |`); - lines.push(`| Average Complexity | ${stats.avgComplexity.toFixed(2)} |`); - lines.push(''); - } - - // Monorepo Structure - if (index.monorepo) { - lines.push('## Monorepo Structure'); - lines.push(''); - lines.push('| Service | Path | Type | Files |'); - lines.push('|---------|------|------|-------|'); - - for (const [name, service] of Object.entries(index.monorepo.services)) { - const fileCount = Object.keys(index.files).filter(f => - f.startsWith(service.path) - ).length; - lines.push(`| ${name} | \`${service.path}\` | ${service.type || 'unknown'} | ${fileCount} |`); - } - lines.push(''); - - // Cross-service dependencies - if (index.monorepo.crossServiceDependencies?.length > 0) { - lines.push('### Cross-Service Dependencies'); - lines.push(''); - lines.push('| From Service | To Service | File Count |'); - lines.push('|--------------|------------|------------|'); - - for (const dep of index.monorepo.crossServiceDependencies) { - const fileCount = dep.files?.length || 0; - lines.push(`| ${dep.from} | ${dep.to} | ${fileCount} |`); - } - lines.push(''); - } - } - - // File Index - if (includeFileDetails) { - lines.push('## File Index'); - lines.push(''); - - const sortedFiles = this.sortFiles(Object.entries(index.files), sortBy); - - // Group by directory - const directories = this.groupByDirectory(sortedFiles); - - for (const [dir, files] of Object.entries(directories)) { - lines.push(`### ${dir || 'Root'}`); - lines.push(''); - lines.push('| File | Language | Functions | Classes | Imports | Exports | Complexity |'); - lines.push('|------|----------|-----------|---------|---------|---------|------------|'); - - for (const [filePath, fileData] of files) { - const fileName = path.basename(filePath); - const funcCount = fileData.functions?.length || 0; - const classCount = fileData.classes?.length || 0; - const importCount = fileData.imports?.length || 0; - const exportCount = fileData.exports?.length || 0; - const complexity = fileData.complexity || 0; - - lines.push(`| [${fileName}](#${this.sanitizeAnchor(filePath)}) | ${fileData.language || 'Unknown'} | ${funcCount} | ${classCount} | ${importCount} | ${exportCount} | ${complexity} |`); - } - lines.push(''); - } - - // Detailed file information - lines.push('## File Details'); - lines.push(''); - - for (const [filePath, fileData] of sortedFiles) { - lines.push(`### ${filePath}`); - lines.push(''); - - // Functions - if (fileData.functions && fileData.functions.length > 0) { - lines.push('**Functions:**'); - lines.push(''); - for (const func of fileData.functions) { - const params = func.parameters?.map((p: any) => p.name).join(', ') || ''; - const asyncTag = func.async ? ' `async`' : ''; - const generatorTag = func.generator ? ' `generator`' : ''; - lines.push(`- \`${func.name}(${params})\`${asyncTag}${generatorTag}`); - } - lines.push(''); - } - - // Classes - if (fileData.classes && fileData.classes.length > 0) { - lines.push('**Classes:**'); - lines.push(''); - for (const cls of fileData.classes) { - const extendsStr = cls.extends ? ` extends ${cls.extends}` : ''; - const implementsStr = cls.implements?.length ? ` implements ${cls.implements.join(', ')}` : ''; - lines.push(`- \`${cls.name}\`${extendsStr}${implementsStr}`); - - if (cls.methods && cls.methods.length > 0) { - lines.push(` - Methods: ${cls.methods.map((m: any) => `\`${m.name}\``).join(', ')}`); - } - if (cls.properties && cls.properties.length > 0) { - lines.push(` - Properties: ${cls.properties.map((p: any) => `\`${p.name}\``).join(', ')}`); - } - } - lines.push(''); - } - - // Dependencies - if (fileData.dependencies && fileData.dependencies.length > 0) { - lines.push('**Dependencies:**'); - lines.push(''); - for (const dep of fileData.dependencies) { - lines.push(`- \`${dep}\``); - } - lines.push(''); - } - } - } - - // Dependency Analysis - if (includeDependencyGraph && index.dependencyGraph) { - lines.push('## Dependency Analysis'); - lines.push(''); - - const analysis = this.analyzeDependencies(index); - - lines.push('### Most Depended Upon'); - lines.push(''); - lines.push('| File | Depended By Count |'); - lines.push('|------|-------------------|'); - - for (const [file, count] of analysis.mostDepended.slice(0, 10)) { - lines.push(`| \`${file}\` | ${count} |`); - } - lines.push(''); - - lines.push('### Most Dependencies'); - lines.push(''); - lines.push('| File | Dependency Count |'); - lines.push('|------|------------------|'); - - for (const [file, count] of analysis.mostDependencies.slice(0, 10)) { - lines.push(`| \`${file}\` | ${count} |`); - } - lines.push(''); - - if (analysis.circular.length > 0) { - lines.push('### ⚠️ Circular Dependencies'); - lines.push(''); - for (const [file1, file2] of analysis.circular) { - lines.push(`- \`${file1}\` ↔️ \`${file2}\``); - } - lines.push(''); - } - } - - // Language Breakdown - lines.push('## Language Breakdown'); - lines.push(''); - - const languageStats = this.getLanguageStatistics(index); - - lines.push('| Language | File Count | Function Count | Class Count | Line Count |'); - lines.push('|----------|------------|----------------|-------------|------------|'); - - for (const [lang, stats] of Object.entries(languageStats)) { - lines.push(`| ${lang} | ${stats.files} | ${stats.functions} | ${stats.classes} | ${stats.lines} |`); - } - lines.push(''); - - // Footer - lines.push('---'); - lines.push(''); - lines.push(`*Generated by @cloneglobal/indexer v${index.version} on ${new Date().toISOString()}*`); - - // Ensure output directory exists - const dir = path.dirname(outputPath); - await fs.mkdir(dir, { recursive: true }); - - // Write to file - await fs.writeFile(outputPath, lines.join('\n'), 'utf-8'); - } - - private calculateStatistics(index: ProjectIndex): any { - const stats = { - totalFiles: Object.keys(index.files).length, - totalLines: 0, - totalFunctions: 0, - totalClasses: 0, - totalImports: 0, - totalExports: 0, - languages: new Set(), - avgComplexity: 0, - totalComplexity: 0 - }; - - for (const fileData of Object.values(index.files)) { - stats.totalLines += fileData.size || 0; - stats.totalFunctions += fileData.functions?.length || 0; - stats.totalClasses += fileData.classes?.length || 0; - stats.totalImports += fileData.imports?.length || 0; - stats.totalExports += fileData.exports?.length || 0; - - if (fileData.language) { - stats.languages.add(fileData.language); - } - - if (fileData.complexity) { - stats.totalComplexity += fileData.complexity; - } - } - - stats.avgComplexity = stats.totalFiles > 0 - ? stats.totalComplexity / stats.totalFiles - : 0; - - return { - ...stats, - languages: Array.from(stats.languages) - }; - } - - private sortFiles(files: Array<[string, FileInfo]>, sortBy: string): Array<[string, FileInfo]> { - switch (sortBy) { - case 'type': - return files.sort((a, b) => { - const extA = path.extname(a[0]); - const extB = path.extname(b[0]); - return extA.localeCompare(extB); - }); - case 'size': - return files.sort((a, b) => (b[1].size || 0) - (a[1].size || 0)); - case 'complexity': - return files.sort((a, b) => (b[1].complexity || 0) - (a[1].complexity || 0)); - default: - return files.sort((a, b) => a[0].localeCompare(b[0])); - } - } - - private groupByDirectory(files: Array<[string, FileInfo]>): Record> { - const groups: Record> = {}; - - for (const [filePath, fileData] of files) { - const dir = path.dirname(filePath); - if (!groups[dir]) { - groups[dir] = []; - } - groups[dir].push([filePath, fileData]); - } - - return groups; - } - - private analyzeDependencies(index: ProjectIndex): any { - const dependedBy: Record> = {}; - const dependsOn: Record> = {}; - const circular: Array<[string, string]> = []; - - // Build dependency maps - for (const [filePath, fileData] of Object.entries(index.files)) { - if (!dependsOn[filePath]) { - dependsOn[filePath] = new Set(); - } - - if (fileData.dependencies) { - for (const dep of fileData.dependencies) { - const resolvedDep = this.resolvePath(filePath, dep); - dependsOn[filePath].add(resolvedDep); - - if (!dependedBy[resolvedDep]) { - dependedBy[resolvedDep] = new Set(); - } - dependedBy[resolvedDep].add(filePath); - - // Check for circular dependency - if (dependsOn[resolvedDep]?.has(filePath)) { - circular.push([filePath, resolvedDep]); - } - } - } - } - - // Sort by count - const mostDepended = Object.entries(dependedBy) - .map(([file, deps]) => [file, deps.size] as [string, number]) - .sort((a, b) => b[1] - a[1]); - - const mostDependencies = Object.entries(dependsOn) - .map(([file, deps]) => [file, deps.size] as [string, number]) - .sort((a, b) => b[1] - a[1]); - - return { - mostDepended, - mostDependencies, - circular - }; - } - - private getLanguageStatistics(index: ProjectIndex): Record { - const stats: Record = {}; - - for (const fileData of Object.values(index.files)) { - const lang = fileData.language || 'Unknown'; - - if (!stats[lang]) { - stats[lang] = { - files: 0, - functions: 0, - classes: 0, - lines: 0 - }; - } - - stats[lang].files++; - stats[lang].functions += fileData.functions?.length || 0; - stats[lang].classes += fileData.classes?.length || 0; - stats[lang].lines += fileData.size || 0; - } - - return stats; - } - - private sanitizeAnchor(text: string): string { - return text.toLowerCase().replace(/[^a-z0-9]/g, '-'); - } - - private resolvePath(from: string, to: string): string { - if (!to.startsWith('.')) { - return to; - } - - const dir = path.dirname(from); - return path.join(dir, to).replace(/\\/g, '/'); - } +import { ProjectIndex, FileInfo, Exporter } from '../types'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +export interface MarkdownOptions { + includeTableOfContents?: boolean; + includeStatistics?: boolean; + includeDependencyGraph?: boolean; + includeFileDetails?: boolean; + sortBy?: 'name' | 'type' | 'size' | 'complexity'; +} + +export class MarkdownExporter implements Exporter { + async export(index: ProjectIndex, outputPath: string, options: MarkdownOptions = {}): Promise { + const { + includeTableOfContents = true, + includeStatistics = true, + includeDependencyGraph = true, + includeFileDetails = true, + sortBy = 'name' + } = options; + + const lines: string[] = []; + + // Header + lines.push('# Project Index Report'); + lines.push(''); + lines.push(`**Generated:** ${new Date(index.timestamp).toLocaleString()}`); + lines.push(`**Version:** ${index.version}`); + lines.push(`**Project Root:** \`${index.projectRoot}\``); + lines.push(''); + + // Table of Contents + if (includeTableOfContents) { + lines.push('## Table of Contents'); + lines.push(''); + if (includeStatistics) lines.push('- [Statistics](#statistics)'); + if (index.monorepo) lines.push('- [Monorepo Structure](#monorepo-structure)'); + if (includeFileDetails) lines.push('- [File Index](#file-index)'); + if (includeDependencyGraph) lines.push('- [Dependency Analysis](#dependency-analysis)'); + lines.push('- [Language Breakdown](#language-breakdown)'); + lines.push(''); + } + + // Statistics + if (includeStatistics) { + lines.push('## Statistics'); + lines.push(''); + + const stats = this.calculateStatistics(index); + + lines.push('| Metric | Value |'); + lines.push('|--------|-------|'); + lines.push(`| Total Files | ${stats.totalFiles} |`); + lines.push(`| Total Lines | ${stats.totalLines} |`); + lines.push(`| Total Functions | ${stats.totalFunctions} |`); + lines.push(`| Total Classes | ${stats.totalClasses} |`); + lines.push(`| Total Imports | ${stats.totalImports} |`); + lines.push(`| Total Exports | ${stats.totalExports} |`); + lines.push(`| Languages | ${stats.languages.join(', ')} |`); + lines.push(`| Average Complexity | ${stats.avgComplexity.toFixed(2)} |`); + lines.push(''); + } + + // Monorepo Structure + if (index.monorepo) { + lines.push('## Monorepo Structure'); + lines.push(''); + lines.push('| Service | Path | Type | Files |'); + lines.push('|---------|------|------|-------|'); + + for (const [name, service] of Object.entries(index.monorepo.services)) { + const fileCount = Object.keys(index.files).filter(f => + f.startsWith(service.path) + ).length; + lines.push(`| ${name} | \`${service.path}\` | ${service.type || 'unknown'} | ${fileCount} |`); + } + lines.push(''); + + // Cross-service dependencies + if (index.monorepo.crossServiceDependencies?.length > 0) { + lines.push('### Cross-Service Dependencies'); + lines.push(''); + lines.push('| From Service | To Service | File Count |'); + lines.push('|--------------|------------|------------|'); + + for (const dep of index.monorepo.crossServiceDependencies) { + const fileCount = dep.files?.length || 0; + lines.push(`| ${dep.from} | ${dep.to} | ${fileCount} |`); + } + lines.push(''); + } + } + + // File Index + if (includeFileDetails) { + lines.push('## File Index'); + lines.push(''); + + const sortedFiles = this.sortFiles(Object.entries(index.files), sortBy); + + // Group by directory + const directories = this.groupByDirectory(sortedFiles); + + for (const [dir, files] of Object.entries(directories)) { + lines.push(`### ${dir || 'Root'}`); + lines.push(''); + lines.push('| File | Language | Functions | Classes | Imports | Exports | Complexity |'); + lines.push('|------|----------|-----------|---------|---------|---------|------------|'); + + for (const [filePath, fileData] of files) { + const fileName = path.basename(filePath); + const funcCount = fileData.functions?.length || 0; + const classCount = fileData.classes?.length || 0; + const importCount = fileData.imports?.length || 0; + const exportCount = fileData.exports?.length || 0; + const complexity = fileData.complexity || 0; + + lines.push(`| [${fileName}](#${this.sanitizeAnchor(filePath)}) | ${fileData.language || 'Unknown'} | ${funcCount} | ${classCount} | ${importCount} | ${exportCount} | ${complexity} |`); + } + lines.push(''); + } + + // Detailed file information + lines.push('## File Details'); + lines.push(''); + + for (const [filePath, fileData] of sortedFiles) { + lines.push(`### ${filePath}`); + lines.push(''); + + // Functions + if (fileData.functions && fileData.functions.length > 0) { + lines.push('**Functions:**'); + lines.push(''); + for (const func of fileData.functions) { + const params = func.parameters?.map((p: any) => p.name).join(', ') || ''; + const asyncTag = func.async ? ' `async`' : ''; + const generatorTag = func.generator ? ' `generator`' : ''; + lines.push(`- \`${func.name}(${params})\`${asyncTag}${generatorTag}`); + } + lines.push(''); + } + + // Classes + if (fileData.classes && fileData.classes.length > 0) { + lines.push('**Classes:**'); + lines.push(''); + for (const cls of fileData.classes) { + const extendsStr = cls.extends ? ` extends ${cls.extends}` : ''; + const implementsStr = cls.implements?.length ? ` implements ${cls.implements.join(', ')}` : ''; + lines.push(`- \`${cls.name}\`${extendsStr}${implementsStr}`); + + if (cls.methods && cls.methods.length > 0) { + lines.push(` - Methods: ${cls.methods.map((m: any) => `\`${m.name}\``).join(', ')}`); + } + if (cls.properties && cls.properties.length > 0) { + lines.push(` - Properties: ${cls.properties.map((p: any) => `\`${p.name}\``).join(', ')}`); + } + } + lines.push(''); + } + + // Dependencies + if (fileData.dependencies && fileData.dependencies.length > 0) { + lines.push('**Dependencies:**'); + lines.push(''); + for (const dep of fileData.dependencies) { + lines.push(`- \`${dep}\``); + } + lines.push(''); + } + } + } + + // Dependency Analysis + if (includeDependencyGraph && index.dependencyGraph) { + lines.push('## Dependency Analysis'); + lines.push(''); + + const analysis = this.analyzeDependencies(index); + + lines.push('### Most Depended Upon'); + lines.push(''); + lines.push('| File | Depended By Count |'); + lines.push('|------|-------------------|'); + + for (const [file, count] of analysis.mostDepended.slice(0, 10)) { + lines.push(`| \`${file}\` | ${count} |`); + } + lines.push(''); + + lines.push('### Most Dependencies'); + lines.push(''); + lines.push('| File | Dependency Count |'); + lines.push('|------|------------------|'); + + for (const [file, count] of analysis.mostDependencies.slice(0, 10)) { + lines.push(`| \`${file}\` | ${count} |`); + } + lines.push(''); + + if (analysis.circular.length > 0) { + lines.push('### ⚠️ Circular Dependencies'); + lines.push(''); + for (const [file1, file2] of analysis.circular) { + lines.push(`- \`${file1}\` ↔️ \`${file2}\``); + } + lines.push(''); + } + } + + // Language Breakdown + lines.push('## Language Breakdown'); + lines.push(''); + + const languageStats = this.getLanguageStatistics(index); + + lines.push('| Language | File Count | Function Count | Class Count | Line Count |'); + lines.push('|----------|------------|----------------|-------------|------------|'); + + for (const [lang, stats] of Object.entries(languageStats)) { + lines.push(`| ${lang} | ${stats.files} | ${stats.functions} | ${stats.classes} | ${stats.lines} |`); + } + lines.push(''); + + // Footer + lines.push('---'); + lines.push(''); + lines.push(`*Generated by @cloneglobal/indexer v${index.version} on ${new Date().toISOString()}*`); + + // Ensure output directory exists + const dir = path.dirname(outputPath); + await fs.mkdir(dir, { recursive: true }); + + // Write to file + await fs.writeFile(outputPath, lines.join('\n'), 'utf-8'); + } + + private calculateStatistics(index: ProjectIndex): any { + const stats = { + totalFiles: Object.keys(index.files).length, + totalLines: 0, + totalFunctions: 0, + totalClasses: 0, + totalImports: 0, + totalExports: 0, + languages: new Set(), + avgComplexity: 0, + totalComplexity: 0 + }; + + for (const fileData of Object.values(index.files)) { + stats.totalLines += fileData.size || 0; + stats.totalFunctions += fileData.functions?.length || 0; + stats.totalClasses += fileData.classes?.length || 0; + stats.totalImports += fileData.imports?.length || 0; + stats.totalExports += fileData.exports?.length || 0; + + if (fileData.language) { + stats.languages.add(fileData.language); + } + + if (fileData.complexity) { + stats.totalComplexity += fileData.complexity; + } + } + + stats.avgComplexity = stats.totalFiles > 0 + ? stats.totalComplexity / stats.totalFiles + : 0; + + return { + ...stats, + languages: Array.from(stats.languages) + }; + } + + private sortFiles(files: Array<[string, FileInfo]>, sortBy: string): Array<[string, FileInfo]> { + switch (sortBy) { + case 'type': + return files.sort((a, b) => { + const extA = path.extname(a[0]); + const extB = path.extname(b[0]); + return extA.localeCompare(extB); + }); + case 'size': + return files.sort((a, b) => (b[1].size || 0) - (a[1].size || 0)); + case 'complexity': + return files.sort((a, b) => (b[1].complexity || 0) - (a[1].complexity || 0)); + default: + return files.sort((a, b) => a[0].localeCompare(b[0])); + } + } + + private groupByDirectory(files: Array<[string, FileInfo]>): Record> { + const groups: Record> = {}; + + for (const [filePath, fileData] of files) { + const dir = path.dirname(filePath); + if (!groups[dir]) { + groups[dir] = []; + } + groups[dir].push([filePath, fileData]); + } + + return groups; + } + + private analyzeDependencies(index: ProjectIndex): any { + const dependedBy: Record> = {}; + const dependsOn: Record> = {}; + const circular: Array<[string, string]> = []; + + // Build dependency maps + for (const [filePath, fileData] of Object.entries(index.files)) { + if (!dependsOn[filePath]) { + dependsOn[filePath] = new Set(); + } + + if (fileData.dependencies) { + for (const dep of fileData.dependencies) { + const resolvedDep = this.resolvePath(filePath, dep); + dependsOn[filePath].add(resolvedDep); + + if (!dependedBy[resolvedDep]) { + dependedBy[resolvedDep] = new Set(); + } + dependedBy[resolvedDep].add(filePath); + + // Check for circular dependency + if (dependsOn[resolvedDep]?.has(filePath)) { + circular.push([filePath, resolvedDep]); + } + } + } + } + + // Sort by count + const mostDepended = Object.entries(dependedBy) + .map(([file, deps]) => [file, deps.size] as [string, number]) + .sort((a, b) => b[1] - a[1]); + + const mostDependencies = Object.entries(dependsOn) + .map(([file, deps]) => [file, deps.size] as [string, number]) + .sort((a, b) => b[1] - a[1]); + + return { + mostDepended, + mostDependencies, + circular + }; + } + + private getLanguageStatistics(index: ProjectIndex): Record { + const stats: Record = {}; + + for (const fileData of Object.values(index.files)) { + const lang = fileData.language || 'Unknown'; + + if (!stats[lang]) { + stats[lang] = { + files: 0, + functions: 0, + classes: 0, + lines: 0 + }; + } + + stats[lang].files++; + stats[lang].functions += fileData.functions?.length || 0; + stats[lang].classes += fileData.classes?.length || 0; + stats[lang].lines += fileData.size || 0; + } + + return stats; + } + + private sanitizeAnchor(text: string): string { + return text.toLowerCase().replace(/[^a-z0-9]/g, '-'); + } + + private resolvePath(from: string, to: string): string { + if (!to.startsWith('.')) { + return to; + } + + const dir = path.dirname(from); + return path.join(dir, to).replace(/\\/g, '/'); + } } \ No newline at end of file diff --git a/src/exporters/mermaid-base.ts b/src/exporters/mermaid-base.ts index b5e7ef9..9f2131d 100644 --- a/src/exporters/mermaid-base.ts +++ b/src/exporters/mermaid-base.ts @@ -1,185 +1,185 @@ -/** - * Base Mermaid Exporter - * Generates single-repository Mermaid diagrams - */ - -import { ProjectIndex, Exporter } from '../types'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import logger from '../utils/logger'; - -export interface MermaidOptions { - direction?: 'TB' | 'BT' | 'LR' | 'RL' | 'TD'; - theme?: 'default' | 'dark' | 'forest' | 'neutral'; - includeExternals?: boolean; - groupByService?: boolean; - diagramType?: 'flowchart' | 'graph' | 'classDiagram' | 'erDiagram'; - renderHtml?: boolean; - maxNodes?: number; - filterPattern?: string; - serviceFilter?: string; -} - -export class MermaidExporter implements Exporter { - async export(index: ProjectIndex, outputPath: string, options: MermaidOptions = {}): Promise { - const { - diagramType = 'flowchart', - theme = 'default', - renderHtml = false - } = options; - - const lines: string[] = []; - - // Generate diagram based on type - switch (diagramType) { - case 'flowchart': - case 'graph': - await this.generateFlowchart(lines, index, options); - break; - case 'classDiagram': - await this.generateClassDiagram(lines, index, options); - break; - case 'erDiagram': - await this.generateERDiagram(lines, index, options); - break; - default: - await this.generateFlowchart(lines, index, options); - break; - } - - // Ensure output directory exists - const dir = path.dirname(outputPath); - await fs.mkdir(dir, { recursive: true }); - - // Write Mermaid diagram or HTML with renderer - if (renderHtml) { - const htmlContent = this.generateHtmlWrapper(lines.join('\n'), theme); - const htmlPath = outputPath.replace(/\.(mmd|mermaid)$/, '.html'); - await fs.writeFile(htmlPath, htmlContent, 'utf-8'); - logger.info(`Mermaid diagram with viewer exported to: ${htmlPath}`); - } else { - await fs.writeFile(outputPath, lines.join('\n'), 'utf-8'); - } - } - - protected async generateFlowchart(lines: string[], index: ProjectIndex, options: MermaidOptions): Promise { - const { - direction = 'LR', - includeExternals = false, - groupByService = true, - maxNodes = 50, - filterPattern, - serviceFilter - } = options; - - lines.push(`flowchart ${direction}`); - lines.push(''); - - // Implementation simplified for brevity - // Full implementation would go here - lines.push(' %% Dependency flowchart'); - } - - protected async generateClassDiagram(lines: string[], index: ProjectIndex, options: MermaidOptions): Promise { - lines.push('classDiagram'); - lines.push(''); - - // Implementation simplified for brevity - lines.push(' %% Class diagram'); - } - - protected async generateERDiagram(lines: string[], index: ProjectIndex, options: MermaidOptions): Promise { - lines.push('erDiagram'); - lines.push(''); - - // Implementation simplified for brevity - lines.push(' %% Entity relationship diagram'); - } - - protected generateHtmlWrapper(mermaidContent: string, theme: string): string { - return ` - - - - - Project Dependency Diagram - - - - -
-

Project Dependency Visualization

-
-
-${mermaidContent}
-            
-
-
- - - -`; - } - - protected sanitizeNodeId(id: string): string { - return id.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^(\d)/, 'n$1'); - } - - protected detectService(filePath: string): string | undefined { - if (filePath.includes('frontend/')) return 'frontend'; - if (filePath.includes('backend/')) return 'backend'; - if (filePath.includes('skills/')) return 'skills'; - if (filePath.includes('data-ops/')) return 'dataops'; - if (filePath.includes('marketing/')) return 'marketing'; - return undefined; - } - - protected getNodeShape(type: string): string { - const shapes: Record = { - typescript: '[', - javascript: '(', - python: '{{', - go: '[/', - graphql: '>', - astro: '((', - unknown: '[' - }; - return shapes[type] || '['; - } +/** + * Base Mermaid Exporter + * Generates single-repository Mermaid diagrams + */ + +import { ProjectIndex, Exporter } from '../types'; +import * as fs from 'fs/promises'; +import * as path from 'path'; +import logger from '../utils/logger'; + +export interface MermaidOptions { + direction?: 'TB' | 'BT' | 'LR' | 'RL' | 'TD'; + theme?: 'default' | 'dark' | 'forest' | 'neutral'; + includeExternals?: boolean; + groupByService?: boolean; + diagramType?: 'flowchart' | 'graph' | 'classDiagram' | 'erDiagram'; + renderHtml?: boolean; + maxNodes?: number; + filterPattern?: string; + serviceFilter?: string; +} + +export class MermaidExporter implements Exporter { + async export(index: ProjectIndex, outputPath: string, options: MermaidOptions = {}): Promise { + const { + diagramType = 'flowchart', + theme = 'default', + renderHtml = false + } = options; + + const lines: string[] = []; + + // Generate diagram based on type + switch (diagramType) { + case 'flowchart': + case 'graph': + await this.generateFlowchart(lines, index, options); + break; + case 'classDiagram': + await this.generateClassDiagram(lines, index, options); + break; + case 'erDiagram': + await this.generateERDiagram(lines, index, options); + break; + default: + await this.generateFlowchart(lines, index, options); + break; + } + + // Ensure output directory exists + const dir = path.dirname(outputPath); + await fs.mkdir(dir, { recursive: true }); + + // Write Mermaid diagram or HTML with renderer + if (renderHtml) { + const htmlContent = this.generateHtmlWrapper(lines.join('\n'), theme); + const htmlPath = outputPath.replace(/\.(mmd|mermaid)$/, '.html'); + await fs.writeFile(htmlPath, htmlContent, 'utf-8'); + logger.info(`Mermaid diagram with viewer exported to: ${htmlPath}`); + } else { + await fs.writeFile(outputPath, lines.join('\n'), 'utf-8'); + } + } + + protected async generateFlowchart(lines: string[], index: ProjectIndex, options: MermaidOptions): Promise { + const { + direction = 'LR', + includeExternals = false, + groupByService = true, + maxNodes = 50, + filterPattern, + serviceFilter + } = options; + + lines.push(`flowchart ${direction}`); + lines.push(''); + + // Implementation simplified for brevity + // Full implementation would go here + lines.push(' %% Dependency flowchart'); + } + + protected async generateClassDiagram(lines: string[], index: ProjectIndex, options: MermaidOptions): Promise { + lines.push('classDiagram'); + lines.push(''); + + // Implementation simplified for brevity + lines.push(' %% Class diagram'); + } + + protected async generateERDiagram(lines: string[], index: ProjectIndex, options: MermaidOptions): Promise { + lines.push('erDiagram'); + lines.push(''); + + // Implementation simplified for brevity + lines.push(' %% Entity relationship diagram'); + } + + protected generateHtmlWrapper(mermaidContent: string, theme: string): string { + return ` + + + + + Project Dependency Diagram + + + + +
+

Project Dependency Visualization

+
+
+${mermaidContent}
+            
+
+
+ + + +`; + } + + protected sanitizeNodeId(id: string): string { + return id.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^(\d)/, 'n$1'); + } + + protected detectService(filePath: string): string | undefined { + if (filePath.includes('frontend/')) return 'frontend'; + if (filePath.includes('backend/')) return 'backend'; + if (filePath.includes('skills/')) return 'skills'; + if (filePath.includes('data-ops/')) return 'dataops'; + if (filePath.includes('marketing/')) return 'marketing'; + return undefined; + } + + protected getNodeShape(type: string): string { + const shapes: Record = { + typescript: '[', + javascript: '(', + python: '{{', + go: '[/', + graphql: '>', + astro: '((', + unknown: '[' + }; + return shapes[type] || '['; + } } \ No newline at end of file diff --git a/src/exporters/mermaid-multi-repo.ts b/src/exporters/mermaid-multi-repo.ts index 33f8399..272c05c 100644 --- a/src/exporters/mermaid-multi-repo.ts +++ b/src/exporters/mermaid-multi-repo.ts @@ -1,257 +1,257 @@ -/** - * Multi-Repository Mermaid Exporter - * Generates Mermaid diagrams for cross-repository dependencies - */ - -import { MultiRepoGraph, RepoConnection, RepoNode } from '../types'; -import * as fs from 'fs/promises'; -import logger from '../utils/logger'; - -export class MultiRepoMermaidExporter { - private graph: MultiRepoGraph; - - constructor(graph: MultiRepoGraph) { - this.graph = graph; - } - - /** - * Generate comprehensive multi-repo dependency diagram - */ - async generateMultiRepoDiagram(): Promise { - const lines: string[] = []; - lines.push('```mermaid'); - lines.push('graph TB'); - lines.push(' %% Multi-Repository Knowledge Graph'); - lines.push(' %% Generated by @cloneglobal/indexer'); - lines.push(''); - - // Add repository nodes with statistics - lines.push(' %% Repository Nodes'); - for (const repo of this.graph.repositories) { - const nodeId = this.sanitizeRepoId(repo.name); - const label = `${repo.name}
${repo.fileCount} files
${repo.language}`; - const style = this.getRepoStyle(repo.type); - lines.push(` ${nodeId}["${label}"]:::${repo.type}`); - } - lines.push(''); - - // Add connections grouped by type - const connectionsByType = this.groupConnectionsByType(); - - // API connections (solid arrows) - if (connectionsByType.api.length > 0) { - lines.push(' %% API Connections'); - for (const conn of connectionsByType.api) { - const label = conn.details?.endpoint ? `|"${conn.details.endpoint}"` : ''; - lines.push(` ${this.sanitizeRepoId(conn.from)} -->${label} ${this.sanitizeRepoId(conn.to)}`); - } - lines.push(''); - } - - // GraphQL connections (dashed arrows) - if (connectionsByType.graphql.length > 0) { - lines.push(' %% GraphQL Operations'); - for (const conn of connectionsByType.graphql) { - const label = conn.details?.operation ? `|"${conn.details.operation}"` : ''; - lines.push(` ${this.sanitizeRepoId(conn.from)} -.${label}.-> ${this.sanitizeRepoId(conn.to)}`); - } - lines.push(''); - } - - // Event connections (thick arrows) - if (connectionsByType.event.length > 0) { - lines.push(' %% Event-Based Connections'); - for (const conn of connectionsByType.event) { - const label = conn.details?.event ? `|"${conn.details.event}"` : ''; - lines.push(` ${this.sanitizeRepoId(conn.from)} ==>${label} ${this.sanitizeRepoId(conn.to)}`); - } - lines.push(''); - } - - // Add styles - lines.push(' %% Styles'); - lines.push(' classDef frontend fill:#61dafb,stroke:#000,stroke-width:2px,color:#000'); - lines.push(' classDef backend fill:#68a063,stroke:#000,stroke-width:2px,color:#fff'); - lines.push(' classDef skills fill:#f1e05a,stroke:#000,stroke-width:2px,color:#000'); - lines.push(' classDef dataops fill:#f97583,stroke:#000,stroke-width:2px,color:#fff'); - lines.push(' classDef marketing fill:#ff6b6b,stroke:#000,stroke-width:2px,color:#fff'); - - lines.push('```'); - lines.push(''); - - return lines.join('\n'); - } - - /** - * Generate API flow diagram showing frontend-backend connections - */ - async generateAPIFlowDiagram(): Promise { - const lines: string[] = []; - lines.push('```mermaid'); - lines.push('flowchart LR'); - lines.push(' %% API Flow Diagram'); - lines.push(''); - - // Group repositories by type - const reposByType = this.groupReposByType(); - - // Create subgraphs for each repository type - for (const [type, repos] of Object.entries(reposByType)) { - if (repos.length === 0) continue; - - const typeName = this.formatRepoType(type); - lines.push(` subgraph ${type}["${typeName}"]`); - - for (const repo of repos) { - const nodeId = this.sanitizeRepoId(repo.name); - lines.push(` ${nodeId}["${repo.name}
${repo.fileCount} files"]`); - } - - lines.push(' end'); - lines.push(''); - } - - // Add API and GraphQL connections with details - const apiConnections = this.graph.connections.filter(c => - c.type === 'api' || c.type === 'graphql' - ); - - for (const conn of apiConnections) { - const fromId = this.sanitizeRepoId(conn.from); - const toId = this.sanitizeRepoId(conn.to); - let label = ''; - - if (conn.details) { - if (conn.details.endpoint) { - label = conn.details.endpoint; - } else if (conn.details.operation) { - label = conn.details.operation; - } - } - - const arrow = conn.type === 'graphql' ? '-.->|' : '-->|'; - if (label) { - lines.push(` ${fromId} ${arrow}"${label}"| ${toId}`); - } else { - lines.push(` ${fromId} ${arrow.replace('|', '')} ${toId}`); - } - } - - lines.push('```'); - return lines.join('\n'); - } - - /** - * Export as VS Code/Cursor compatible Markdown with embedded Mermaid - */ - async exportForVSCode(outputPath: string): Promise { - const lines: string[] = []; - - // Header - lines.push('# Multi-Repository Knowledge Graph'); - lines.push(''); - lines.push(`Generated: ${new Date().toISOString()}`); - lines.push(''); - - // Statistics - lines.push('## Statistics'); - lines.push(''); - lines.push(`- **Repositories**: ${this.graph.repositories.length}`); - lines.push(`- **Total Connections**: ${this.graph.connections.length}`); - - const connectionTypes = this.groupConnectionsByType(); - for (const [type, conns] of Object.entries(connectionTypes)) { - if (conns.length > 0) { - lines.push(`- **${this.formatConnectionType(type)}**: ${conns.length}`); - } - } - lines.push(''); - - // Repository details - lines.push('## Repositories'); - lines.push(''); - for (const repo of this.graph.repositories) { - lines.push(`### ${repo.name}`); - lines.push(`- **Type**: ${repo.type}`); - lines.push(`- **Path**: ${repo.path}`); - lines.push(`- **Files**: ${repo.fileCount}`); - lines.push(`- **Language**: ${repo.language}`); - lines.push(''); - } - - // Diagrams - lines.push('## Dependency Overview'); - lines.push(''); - lines.push(await this.generateMultiRepoDiagram()); - lines.push(''); - - lines.push('## API Flow Diagram'); - lines.push(''); - lines.push(await this.generateAPIFlowDiagram()); - lines.push(''); - - // Write to file - await fs.writeFile(outputPath, lines.join('\n'), 'utf-8'); - logger.success(`✅ VS Code/Cursor compatible Markdown generated: ${outputPath}`); - } - - // Helper methods - private sanitizeRepoId(name: string): string { - return name.replace(/[^a-zA-Z0-9_]/g, '_'); - } - - private getRepoStyle(type: string): string { - return type; // Return as class name for Mermaid - } - - private formatRepoType(type: string): string { - const types: Record = { - frontend: 'Frontend Applications', - backend: 'Backend Services', - skills: 'Skill Functions', - dataops: 'Data Operations', - marketing: 'Marketing Sites' - }; - return types[type] || type; - } - - private formatConnectionType(type: string): string { - const types: Record = { - api: 'API Connections', - graphql: 'GraphQL Operations', - event: 'Event-Based Connections', - import: 'Import Dependencies' - }; - return types[type] || type; - } - - private groupConnectionsByType(): Record { - const grouped: Record = { - api: [], - graphql: [], - event: [], - import: [] - }; - - for (const conn of this.graph.connections) { - if (grouped[conn.type]) { - grouped[conn.type].push(conn); - } - } - - return grouped; - } - - private groupReposByType(): Record { - const grouped: Record = {}; - - for (const repo of this.graph.repositories) { - if (!grouped[repo.type]) { - grouped[repo.type] = []; - } - grouped[repo.type].push(repo); - } - - return grouped; - } +/** + * Multi-Repository Mermaid Exporter + * Generates Mermaid diagrams for cross-repository dependencies + */ + +import { MultiRepoGraph, RepoConnection, RepoNode } from '../types'; +import * as fs from 'fs/promises'; +import logger from '../utils/logger'; + +export class MultiRepoMermaidExporter { + private graph: MultiRepoGraph; + + constructor(graph: MultiRepoGraph) { + this.graph = graph; + } + + /** + * Generate comprehensive multi-repo dependency diagram + */ + async generateMultiRepoDiagram(): Promise { + const lines: string[] = []; + lines.push('```mermaid'); + lines.push('graph TB'); + lines.push(' %% Multi-Repository Knowledge Graph'); + lines.push(' %% Generated by @cloneglobal/indexer'); + lines.push(''); + + // Add repository nodes with statistics + lines.push(' %% Repository Nodes'); + for (const repo of this.graph.repositories) { + const nodeId = this.sanitizeRepoId(repo.name); + const label = `${repo.name}
${repo.fileCount} files
${repo.language}`; + const style = this.getRepoStyle(repo.type); + lines.push(` ${nodeId}["${label}"]:::${repo.type}`); + } + lines.push(''); + + // Add connections grouped by type + const connectionsByType = this.groupConnectionsByType(); + + // API connections (solid arrows) + if (connectionsByType.api.length > 0) { + lines.push(' %% API Connections'); + for (const conn of connectionsByType.api) { + const label = conn.details?.endpoint ? `|"${conn.details.endpoint}"` : ''; + lines.push(` ${this.sanitizeRepoId(conn.from)} -->${label} ${this.sanitizeRepoId(conn.to)}`); + } + lines.push(''); + } + + // GraphQL connections (dashed arrows) + if (connectionsByType.graphql.length > 0) { + lines.push(' %% GraphQL Operations'); + for (const conn of connectionsByType.graphql) { + const label = conn.details?.operation ? `|"${conn.details.operation}"` : ''; + lines.push(` ${this.sanitizeRepoId(conn.from)} -.${label}.-> ${this.sanitizeRepoId(conn.to)}`); + } + lines.push(''); + } + + // Event connections (thick arrows) + if (connectionsByType.event.length > 0) { + lines.push(' %% Event-Based Connections'); + for (const conn of connectionsByType.event) { + const label = conn.details?.event ? `|"${conn.details.event}"` : ''; + lines.push(` ${this.sanitizeRepoId(conn.from)} ==>${label} ${this.sanitizeRepoId(conn.to)}`); + } + lines.push(''); + } + + // Add styles + lines.push(' %% Styles'); + lines.push(' classDef frontend fill:#61dafb,stroke:#000,stroke-width:2px,color:#000'); + lines.push(' classDef backend fill:#68a063,stroke:#000,stroke-width:2px,color:#fff'); + lines.push(' classDef skills fill:#f1e05a,stroke:#000,stroke-width:2px,color:#000'); + lines.push(' classDef dataops fill:#f97583,stroke:#000,stroke-width:2px,color:#fff'); + lines.push(' classDef marketing fill:#ff6b6b,stroke:#000,stroke-width:2px,color:#fff'); + + lines.push('```'); + lines.push(''); + + return lines.join('\n'); + } + + /** + * Generate API flow diagram showing frontend-backend connections + */ + async generateAPIFlowDiagram(): Promise { + const lines: string[] = []; + lines.push('```mermaid'); + lines.push('flowchart LR'); + lines.push(' %% API Flow Diagram'); + lines.push(''); + + // Group repositories by type + const reposByType = this.groupReposByType(); + + // Create subgraphs for each repository type + for (const [type, repos] of Object.entries(reposByType)) { + if (repos.length === 0) continue; + + const typeName = this.formatRepoType(type); + lines.push(` subgraph ${type}["${typeName}"]`); + + for (const repo of repos) { + const nodeId = this.sanitizeRepoId(repo.name); + lines.push(` ${nodeId}["${repo.name}
${repo.fileCount} files"]`); + } + + lines.push(' end'); + lines.push(''); + } + + // Add API and GraphQL connections with details + const apiConnections = this.graph.connections.filter(c => + c.type === 'api' || c.type === 'graphql' + ); + + for (const conn of apiConnections) { + const fromId = this.sanitizeRepoId(conn.from); + const toId = this.sanitizeRepoId(conn.to); + let label = ''; + + if (conn.details) { + if (conn.details.endpoint) { + label = conn.details.endpoint; + } else if (conn.details.operation) { + label = conn.details.operation; + } + } + + const arrow = conn.type === 'graphql' ? '-.->|' : '-->|'; + if (label) { + lines.push(` ${fromId} ${arrow}"${label}"| ${toId}`); + } else { + lines.push(` ${fromId} ${arrow.replace('|', '')} ${toId}`); + } + } + + lines.push('```'); + return lines.join('\n'); + } + + /** + * Export as VS Code/Cursor compatible Markdown with embedded Mermaid + */ + async exportForVSCode(outputPath: string): Promise { + const lines: string[] = []; + + // Header + lines.push('# Multi-Repository Knowledge Graph'); + lines.push(''); + lines.push(`Generated: ${new Date().toISOString()}`); + lines.push(''); + + // Statistics + lines.push('## Statistics'); + lines.push(''); + lines.push(`- **Repositories**: ${this.graph.repositories.length}`); + lines.push(`- **Total Connections**: ${this.graph.connections.length}`); + + const connectionTypes = this.groupConnectionsByType(); + for (const [type, conns] of Object.entries(connectionTypes)) { + if (conns.length > 0) { + lines.push(`- **${this.formatConnectionType(type)}**: ${conns.length}`); + } + } + lines.push(''); + + // Repository details + lines.push('## Repositories'); + lines.push(''); + for (const repo of this.graph.repositories) { + lines.push(`### ${repo.name}`); + lines.push(`- **Type**: ${repo.type}`); + lines.push(`- **Path**: ${repo.path}`); + lines.push(`- **Files**: ${repo.fileCount}`); + lines.push(`- **Language**: ${repo.language}`); + lines.push(''); + } + + // Diagrams + lines.push('## Dependency Overview'); + lines.push(''); + lines.push(await this.generateMultiRepoDiagram()); + lines.push(''); + + lines.push('## API Flow Diagram'); + lines.push(''); + lines.push(await this.generateAPIFlowDiagram()); + lines.push(''); + + // Write to file + await fs.writeFile(outputPath, lines.join('\n'), 'utf-8'); + logger.success(`✅ VS Code/Cursor compatible Markdown generated: ${outputPath}`); + } + + // Helper methods + private sanitizeRepoId(name: string): string { + return name.replace(/[^a-zA-Z0-9_]/g, '_'); + } + + private getRepoStyle(type: string): string { + return type; // Return as class name for Mermaid + } + + private formatRepoType(type: string): string { + const types: Record = { + frontend: 'Frontend Applications', + backend: 'Backend Services', + skills: 'Skill Functions', + dataops: 'Data Operations', + marketing: 'Marketing Sites' + }; + return types[type] || type; + } + + private formatConnectionType(type: string): string { + const types: Record = { + api: 'API Connections', + graphql: 'GraphQL Operations', + event: 'Event-Based Connections', + import: 'Import Dependencies' + }; + return types[type] || type; + } + + private groupConnectionsByType(): Record { + const grouped: Record = { + api: [], + graphql: [], + event: [], + import: [] + }; + + for (const conn of this.graph.connections) { + if (grouped[conn.type]) { + grouped[conn.type].push(conn); + } + } + + return grouped; + } + + private groupReposByType(): Record { + const grouped: Record = {}; + + for (const repo of this.graph.repositories) { + if (!grouped[repo.type]) { + grouped[repo.type] = []; + } + grouped[repo.type].push(repo); + } + + return grouped; + } } \ No newline at end of file diff --git a/src/exporters/mermaid.ts b/src/exporters/mermaid.ts index f8e3abd..8b807c1 100644 --- a/src/exporters/mermaid.ts +++ b/src/exporters/mermaid.ts @@ -1,7 +1,7 @@ -/** - * Mermaid Diagram Exporter - * Re-exports split Mermaid exporter modules for backward compatibility - */ - -export { MermaidExporter, MermaidOptions } from './mermaid-base'; +/** + * Mermaid Diagram Exporter + * Re-exports split Mermaid exporter modules for backward compatibility + */ + +export { MermaidExporter, MermaidOptions } from './mermaid-base'; export { MultiRepoMermaidExporter } from './mermaid-multi-repo'; \ No newline at end of file diff --git a/src/hooks/claude-hook.ts b/src/hooks/claude-hook.ts index 2014527..39fc677 100644 --- a/src/hooks/claude-hook.ts +++ b/src/hooks/claude-hook.ts @@ -1,338 +1,338 @@ -#!/usr/bin/env node -import * as fs from 'fs'; -import * as path from 'path'; -import { execSync } from 'child_process'; +#!/usr/bin/env node +import * as fs from 'fs'; +import * as path from 'path'; +import { execSync } from 'child_process'; import logger from '../utils/logger'; - -// Hook types -type HookType = 'pre' | 'post' | 'pre-tool-use' | 'post-tool-use'; - -interface HookContext { - tool?: string; - args?: any[]; - result?: any; - error?: any; - timestamp: string; - projectRoot: string; - indexPath: string; -} - -class ClaudeHook { - private context: HookContext; - private indexData: any = null; - private cacheDir: string; - - constructor() { - this.context = { - timestamp: new Date().toISOString(), - projectRoot: this.findProjectRoot(), - indexPath: '' - }; - - this.context.indexPath = path.join(this.context.projectRoot, 'PROJECT_INDEX.json'); - this.cacheDir = path.join(this.context.projectRoot, '.indexer-cache'); - - // Ensure cache directory exists - if (!fs.existsSync(this.cacheDir)) { - fs.mkdirSync(this.cacheDir, { recursive: true }); - } - } - - private findProjectRoot(): string { - let currentDir = process.cwd(); - - while (currentDir !== path.dirname(currentDir)) { - // Check for common project indicators - if (fs.existsSync(path.join(currentDir, 'package.json')) || - fs.existsSync(path.join(currentDir, '.git')) || - fs.existsSync(path.join(currentDir, 'PROJECT_INDEX.json'))) { - return currentDir; - } - currentDir = path.dirname(currentDir); - } - - return process.cwd(); - } - - private loadIndex(): void { - try { - if (fs.existsSync(this.context.indexPath)) { - const content = fs.readFileSync(this.context.indexPath, 'utf-8'); - this.indexData = JSON.parse(content); - } - } catch (error) { - logger.error('Failed to load index:', error); - } - } - - private saveIndex(): void { - if (this.indexData) { - try { - const content = JSON.stringify(this.indexData, null, 2); - fs.writeFileSync(this.context.indexPath, content); - } catch (error) { - logger.error('Failed to save index:', error); - } - } - } - - private updateIndexIfNeeded(): void { - // Check if index needs updating - if (!this.indexData || this.isIndexStale()) { - this.runIndexer(); - } - } - - private isIndexStale(): boolean { - if (!this.indexData || !this.indexData.timestamp) { - return true; - } - - const indexTime = new Date(this.indexData.timestamp).getTime(); - const now = Date.now(); - const ageInMinutes = (now - indexTime) / (1000 * 60); - - // Consider index stale if older than 30 minutes - return ageInMinutes > 30; - } - - private runIndexer(): void { - try { - logger.info('Updating project index...'); - // Try local indexer first, fallback to global - const indexerPath = path.join(this.context.projectRoot, 'indexer/bin/indexer.js'); - let command = fs.existsSync(indexerPath) - ? `${indexerPath} scan --incremental --quiet` - : 'npx @cloneglobal/indexer scan --incremental --quiet'; - - execSync(command, { - cwd: this.context.projectRoot, - stdio: 'pipe' - }); - this.loadIndex(); - } catch (error) { - logger.error('Failed to update index:', error); - } - } - - private provideContextToClaudeCode(): void { - if (!this.indexData) { - return; - } - - // Create a context summary for Claude Code - const contextSummary = { - project: { - root: this.context.projectRoot, - totalFiles: Object.keys(this.indexData.files || {}).length, - languages: this.indexData.statistics?.languages || {}, - lastIndexed: this.indexData.timestamp - }, - structure: this.getProjectStructure(), - suggestions: this.generateSuggestions() - }; - - // Write context to a temporary file that Claude Code can read - const contextPath = path.join(this.cacheDir, 'claude-context.json'); - fs.writeFileSync(contextPath, JSON.stringify(contextSummary, null, 2)); - - // Set environment variable for Claude Code - process.env.CLAUDE_CODE_CONTEXT = contextPath; - } - - private getProjectStructure(): any { - if (!this.indexData || !this.indexData.files) { - return {}; - } - - const structure: any = { - services: {}, - mainFiles: [], - configFiles: [], - testFiles: [] - }; - - for (const [filePath, fileData] of Object.entries(this.indexData.files)) { - // Categorize files - if (filePath.includes('test') || filePath.includes('spec')) { - structure.testFiles.push(filePath); - } else if (filePath.match(/\.(json|yml|yaml|toml|ini|conf|config)$/)) { - structure.configFiles.push(filePath); - } else if (filePath.includes('main') || filePath.includes('index') || filePath.includes('app')) { - structure.mainFiles.push(filePath); - } - - // Detect services (for monorepos) - const service = this.detectService(filePath); - if (service) { - if (!structure.services[service]) { - structure.services[service] = { - files: [], - functions: 0, - classes: 0 - }; - } - structure.services[service].files.push(filePath); - structure.services[service].functions += (fileData as any).functions?.length || 0; - structure.services[service].classes += (fileData as any).classes?.length || 0; - } - } - - return structure; - } - - private detectService(filePath: string): string | null { - // Common monorepo patterns - const patterns = [ - /^(packages|services|apps|libs)\/([^\/]+)/, - /^(frontend|backend|api|web|mobile|shared)/, - /^src\/(services|modules|domains)\/([^\/]+)/ - ]; - - for (const pattern of patterns) { - const match = filePath.match(pattern); - if (match) { - return match[2] || match[1]; - } - } - - return null; - } - - private generateSuggestions(): string[] { - const suggestions: string[] = []; - - if (!this.indexData) { - suggestions.push('Run "npx @cloneglobal/indexer scan" to index the project'); - return suggestions; - } - - const stats = this.indexData.statistics; - if (stats) { - // Suggest based on project characteristics - if (stats.totalFiles > 1000) { - suggestions.push('Large project detected. Consider using incremental indexing with "watch" command'); - } - - if (stats.avgComplexity && stats.avgComplexity > 10) { - suggestions.push('High average complexity detected. Consider refactoring complex functions'); - } - - const languages = Object.keys(stats.languages || {}); - if (languages.length > 3) { - suggestions.push('Multi-language project. Use language-specific queries for better results'); - } - } - - // Check for monorepo - if (this.indexData.monorepo) { - suggestions.push('Monorepo detected. Use service-specific queries to narrow scope'); - - const circularDeps = this.indexData.monorepo.circularDependencies; - if (circularDeps && circularDeps.length > 0) { - suggestions.push(`Warning: ${circularDeps.length} circular dependencies detected between services`); - } - } - - return suggestions; - } - - public async execute(hookType: HookType): Promise { - try { - // Load current index - this.loadIndex(); - - switch (hookType) { - case 'pre': - case 'pre-tool-use': - // Before Claude Code executes a tool - this.updateIndexIfNeeded(); - this.provideContextToClaudeCode(); - break; - - case 'post': - case 'post-tool-use': - // After Claude Code executes a tool - // Check if files were modified and update index - const filesModified = this.detectModifiedFiles(); - if (filesModified.length > 0) { - logger.info(`Files modified: ${filesModified.length}. Updating index...`); - this.runIndexer(); - } - break; - - default: - logger.warn(`Unknown hook type: ${hookType}`); - } - } catch (error) { - logger.error('Hook execution failed:', error); - // Don't throw - we don't want to interrupt Claude Code - } - } - - private detectModifiedFiles(): string[] { - const modified: string[] = []; - - if (!this.indexData || !this.indexData.files) { - return modified; - } - - for (const [filePath, fileData] of Object.entries(this.indexData.files)) { - const absolutePath = path.join(this.context.projectRoot, filePath); - - try { - if (fs.existsSync(absolutePath)) { - const stats = fs.statSync(absolutePath); - const lastModified = (fileData as any).lastModified; - - if (lastModified && stats.mtime.toISOString() !== lastModified) { - modified.push(filePath); - } - } else { - // File was deleted - modified.push(filePath); - } - } catch (error) { - // Ignore errors for individual files - } - } - - return modified; - } - - public getInfo(): any { - return { - projectRoot: this.context.projectRoot, - indexPath: this.context.indexPath, - cacheDir: this.cacheDir, - indexLoaded: this.indexData !== null, - totalFiles: this.indexData?.files ? Object.keys(this.indexData.files).length : 0, - isMonorepo: this.indexData?.monorepo !== undefined - }; - } -} - -// Main execution -if (require.main === module) { - const args = process.argv.slice(2); - const command = args[0] || 'pre'; - - const hook = new ClaudeHook(); - - // Handle different commands - if (command === 'info') { - // Special command to get hook info - logger.info(JSON.stringify(hook.getInfo(), null, 2)); - } else { - // Execute the hook - const hookType = command as HookType; - hook.execute(hookType).then(() => { - process.exit(0); - }).catch(error => { - logger.error('Hook failed:', error); - process.exit(1); - }); - } -} - + +// Hook types +type HookType = 'pre' | 'post' | 'pre-tool-use' | 'post-tool-use'; + +interface HookContext { + tool?: string; + args?: any[]; + result?: any; + error?: any; + timestamp: string; + projectRoot: string; + indexPath: string; +} + +class ClaudeHook { + private context: HookContext; + private indexData: any = null; + private cacheDir: string; + + constructor() { + this.context = { + timestamp: new Date().toISOString(), + projectRoot: this.findProjectRoot(), + indexPath: '' + }; + + this.context.indexPath = path.join(this.context.projectRoot, 'PROJECT_INDEX.json'); + this.cacheDir = path.join(this.context.projectRoot, '.indexer-cache'); + + // Ensure cache directory exists + if (!fs.existsSync(this.cacheDir)) { + fs.mkdirSync(this.cacheDir, { recursive: true }); + } + } + + private findProjectRoot(): string { + let currentDir = process.cwd(); + + while (currentDir !== path.dirname(currentDir)) { + // Check for common project indicators + if (fs.existsSync(path.join(currentDir, 'package.json')) || + fs.existsSync(path.join(currentDir, '.git')) || + fs.existsSync(path.join(currentDir, 'PROJECT_INDEX.json'))) { + return currentDir; + } + currentDir = path.dirname(currentDir); + } + + return process.cwd(); + } + + private loadIndex(): void { + try { + if (fs.existsSync(this.context.indexPath)) { + const content = fs.readFileSync(this.context.indexPath, 'utf-8'); + this.indexData = JSON.parse(content); + } + } catch (error) { + logger.error('Failed to load index:', error); + } + } + + private saveIndex(): void { + if (this.indexData) { + try { + const content = JSON.stringify(this.indexData, null, 2); + fs.writeFileSync(this.context.indexPath, content); + } catch (error) { + logger.error('Failed to save index:', error); + } + } + } + + private updateIndexIfNeeded(): void { + // Check if index needs updating + if (!this.indexData || this.isIndexStale()) { + this.runIndexer(); + } + } + + private isIndexStale(): boolean { + if (!this.indexData || !this.indexData.timestamp) { + return true; + } + + const indexTime = new Date(this.indexData.timestamp).getTime(); + const now = Date.now(); + const ageInMinutes = (now - indexTime) / (1000 * 60); + + // Consider index stale if older than 30 minutes + return ageInMinutes > 30; + } + + private runIndexer(): void { + try { + logger.info('Updating project index...'); + // Try local indexer first, fallback to global + const indexerPath = path.join(this.context.projectRoot, 'indexer/bin/indexer.js'); + let command = fs.existsSync(indexerPath) + ? `${indexerPath} scan --incremental --quiet` + : 'npx @cloneglobal/indexer scan --incremental --quiet'; + + execSync(command, { + cwd: this.context.projectRoot, + stdio: 'pipe' + }); + this.loadIndex(); + } catch (error) { + logger.error('Failed to update index:', error); + } + } + + private provideContextToClaudeCode(): void { + if (!this.indexData) { + return; + } + + // Create a context summary for Claude Code + const contextSummary = { + project: { + root: this.context.projectRoot, + totalFiles: Object.keys(this.indexData.files || {}).length, + languages: this.indexData.statistics?.languages || {}, + lastIndexed: this.indexData.timestamp + }, + structure: this.getProjectStructure(), + suggestions: this.generateSuggestions() + }; + + // Write context to a temporary file that Claude Code can read + const contextPath = path.join(this.cacheDir, 'claude-context.json'); + fs.writeFileSync(contextPath, JSON.stringify(contextSummary, null, 2)); + + // Set environment variable for Claude Code + process.env.CLAUDE_CODE_CONTEXT = contextPath; + } + + private getProjectStructure(): any { + if (!this.indexData || !this.indexData.files) { + return {}; + } + + const structure: any = { + services: {}, + mainFiles: [], + configFiles: [], + testFiles: [] + }; + + for (const [filePath, fileData] of Object.entries(this.indexData.files)) { + // Categorize files + if (filePath.includes('test') || filePath.includes('spec')) { + structure.testFiles.push(filePath); + } else if (filePath.match(/\.(json|yml|yaml|toml|ini|conf|config)$/)) { + structure.configFiles.push(filePath); + } else if (filePath.includes('main') || filePath.includes('index') || filePath.includes('app')) { + structure.mainFiles.push(filePath); + } + + // Detect services (for monorepos) + const service = this.detectService(filePath); + if (service) { + if (!structure.services[service]) { + structure.services[service] = { + files: [], + functions: 0, + classes: 0 + }; + } + structure.services[service].files.push(filePath); + structure.services[service].functions += (fileData as any).functions?.length || 0; + structure.services[service].classes += (fileData as any).classes?.length || 0; + } + } + + return structure; + } + + private detectService(filePath: string): string | null { + // Common monorepo patterns + const patterns = [ + /^(packages|services|apps|libs)\/([^\/]+)/, + /^(frontend|backend|api|web|mobile|shared)/, + /^src\/(services|modules|domains)\/([^\/]+)/ + ]; + + for (const pattern of patterns) { + const match = filePath.match(pattern); + if (match) { + return match[2] || match[1]; + } + } + + return null; + } + + private generateSuggestions(): string[] { + const suggestions: string[] = []; + + if (!this.indexData) { + suggestions.push('Run "npx @cloneglobal/indexer scan" to index the project'); + return suggestions; + } + + const stats = this.indexData.statistics; + if (stats) { + // Suggest based on project characteristics + if (stats.totalFiles > 1000) { + suggestions.push('Large project detected. Consider using incremental indexing with "watch" command'); + } + + if (stats.avgComplexity && stats.avgComplexity > 10) { + suggestions.push('High average complexity detected. Consider refactoring complex functions'); + } + + const languages = Object.keys(stats.languages || {}); + if (languages.length > 3) { + suggestions.push('Multi-language project. Use language-specific queries for better results'); + } + } + + // Check for monorepo + if (this.indexData.monorepo) { + suggestions.push('Monorepo detected. Use service-specific queries to narrow scope'); + + const circularDeps = this.indexData.monorepo.circularDependencies; + if (circularDeps && circularDeps.length > 0) { + suggestions.push(`Warning: ${circularDeps.length} circular dependencies detected between services`); + } + } + + return suggestions; + } + + public async execute(hookType: HookType): Promise { + try { + // Load current index + this.loadIndex(); + + switch (hookType) { + case 'pre': + case 'pre-tool-use': + // Before Claude Code executes a tool + this.updateIndexIfNeeded(); + this.provideContextToClaudeCode(); + break; + + case 'post': + case 'post-tool-use': + // After Claude Code executes a tool + // Check if files were modified and update index + const filesModified = this.detectModifiedFiles(); + if (filesModified.length > 0) { + logger.info(`Files modified: ${filesModified.length}. Updating index...`); + this.runIndexer(); + } + break; + + default: + logger.warn(`Unknown hook type: ${hookType}`); + } + } catch (error) { + logger.error('Hook execution failed:', error); + // Don't throw - we don't want to interrupt Claude Code + } + } + + private detectModifiedFiles(): string[] { + const modified: string[] = []; + + if (!this.indexData || !this.indexData.files) { + return modified; + } + + for (const [filePath, fileData] of Object.entries(this.indexData.files)) { + const absolutePath = path.join(this.context.projectRoot, filePath); + + try { + if (fs.existsSync(absolutePath)) { + const stats = fs.statSync(absolutePath); + const lastModified = (fileData as any).lastModified; + + if (lastModified && stats.mtime.toISOString() !== lastModified) { + modified.push(filePath); + } + } else { + // File was deleted + modified.push(filePath); + } + } catch (error) { + // Ignore errors for individual files + } + } + + return modified; + } + + public getInfo(): any { + return { + projectRoot: this.context.projectRoot, + indexPath: this.context.indexPath, + cacheDir: this.cacheDir, + indexLoaded: this.indexData !== null, + totalFiles: this.indexData?.files ? Object.keys(this.indexData.files).length : 0, + isMonorepo: this.indexData?.monorepo !== undefined + }; + } +} + +// Main execution +if (require.main === module) { + const args = process.argv.slice(2); + const command = args[0] || 'pre'; + + const hook = new ClaudeHook(); + + // Handle different commands + if (command === 'info') { + // Special command to get hook info + logger.info(JSON.stringify(hook.getInfo(), null, 2)); + } else { + // Execute the hook + const hookType = command as HookType; + hook.execute(hookType).then(() => { + process.exit(0); + }).catch(error => { + logger.error('Hook failed:', error); + process.exit(1); + }); + } +} + export default ClaudeHook; \ No newline at end of file diff --git a/src/hooks/post-write.js b/src/hooks/post-write.js index 57e5efd..214047c 100644 --- a/src/hooks/post-write.js +++ b/src/hooks/post-write.js @@ -1,244 +1,244 @@ -#!/usr/bin/env node -/** - * Post-Write Hook for Claude Code Integration - * - * This hook is triggered AFTER Claude writes or modifies any file. - * It updates the index to reflect the changes immediately. - * - * Spec-compliant structure for Claude Code hooks. - */ - -const fs = require('fs'); -const path = require('path'); -const { execSync } = require('child_process'); +#!/usr/bin/env node +/** + * Post-Write Hook for Claude Code Integration + * + * This hook is triggered AFTER Claude writes or modifies any file. + * It updates the index to reflect the changes immediately. + * + * Spec-compliant structure for Claude Code hooks. + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); import logger from '../../utils/logger'; - -/** - * Main hook handler - * @param {Object} context - Hook context from Claude Code - * @param {string} context.file - File path that was written - * @param {string} context.operation - Operation type (write, create, delete, etc.) - * @param {Object} context.metadata - Additional metadata - * @param {boolean} context.success - Whether the operation succeeded - * @returns {Object} Modified context or null to proceed normally - */ -async function postWriteHook(context) { - try { - // Extract file information - const { file, operation, metadata = {}, success = true } = context; - - // Skip if operation failed - if (!success) { - return null; - } - - // Handle different operations - const updateOperations = ['write', 'create', 'modify', 'delete']; - if (!updateOperations.includes(operation)) { - return null; - } - - // Get file extension to check if it's a source file - const ext = path.extname(file).toLowerCase(); - const sourceExtensions = [ - '.js', '.jsx', '.ts', '.tsx', - '.py', '.go', '.java', '.cpp', '.c', '.h', '.hpp', - '.cs', '.rb', '.php', '.swift', '.kt', '.rs', - '.sql', '.graphql', '.gql', '.yaml', '.yml', '.astro' - ]; - - // Skip non-source files - if (!sourceExtensions.includes(ext)) { - return null; - } - - // Check if we should update the index - const indexPath = path.resolve('.indexer-output/current/indexes/INDEX.json'); - const shouldUpdate = fs.existsSync(indexPath); - - if (shouldUpdate) { - logger.info(`[Indexer] Updating index after ${operation}: ${file}`); - - // Perform incremental update for single file - try { - // Use incremental mode for better performance - const result = execSync('indexer scan --incremental --quiet', { - stdio: 'pipe', - timeout: 5000 - }); - - // Log success - if (process.env.DEBUG) { - logger.info('[Indexer] Index updated successfully'); - } - - // Parse updated index to get file info - if (fs.existsSync(indexPath)) { - const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8')); - const relativeFile = path.relative(process.cwd(), file); - - if (operation === 'delete') { - // Verify file was removed from index - if (!index.files[relativeFile]) { - logger.info(`[Indexer] File removed from index: ${relativeFile}`); - } - } else if (index.files[relativeFile]) { - // Get updated file info - const fileInfo = index.files[relativeFile]; - - // Add post-write metadata - context.metadata = { - ...metadata, - indexed: true, - updateTime: new Date().toISOString(), - functions: fileInfo.functions?.length || 0, - classes: fileInfo.classes?.length || 0, - complexity: fileInfo.complexity || 0, - linesOfCode: fileInfo.linesOfCode || 0 - }; - - // Detect potential issues - if (fileInfo.complexity > 20) { - logger.info(`[Indexer] ⚠️ High complexity detected in ${relativeFile}: ${fileInfo.complexity}`); - } - - // Check for circular dependencies - if (fileInfo.circularDependencies?.length > 0) { - logger.info(`[Indexer] ⚠️ Circular dependencies detected in ${relativeFile}`); - } - } - } - - } catch (error) { - // Don't fail the hook, just log the error - logger.warn('[Indexer] Could not update index:', error.message); - - // Mark for manual update - context.metadata = { - ...metadata, - indexUpdatePending: true, - indexError: error.message - }; - } - } else { - // No index exists yet - if (process.env.DEBUG) { - logger.info('[Indexer] No index to update. Run "indexer init" to create one.'); - } - } - - // Track file changes for batch updates - const changesFile = path.resolve('.indexer-cache/pending-changes.json'); - const cacheDir = path.dirname(changesFile); - - // Ensure cache directory exists - if (!fs.existsSync(cacheDir)) { - fs.mkdirSync(cacheDir, { recursive: true }); - } - - // Load or create changes list - let changes = []; - if (fs.existsSync(changesFile)) { - try { - changes = JSON.parse(fs.readFileSync(changesFile, 'utf-8')); - } catch (e) { - changes = []; - } - } - - // Add this change - changes.push({ - file: path.relative(process.cwd(), file), - operation, - timestamp: new Date().toISOString(), - metadata: context.metadata - }); - - // Keep only last 100 changes - if (changes.length > 100) { - changes = changes.slice(-100); - } - - // Save changes - fs.writeFileSync(changesFile, JSON.stringify(changes, null, 2)); - - // Check if we should trigger a batch update - if (changes.length >= 10) { - logger.info('[Indexer] Multiple changes detected. Consider running "indexer scan" for a full update.'); - } - - // Return null to proceed normally (don't modify Claude's behavior) - return null; - - } catch (error) { - // Log errors but don't interrupt Claude's operation - logger.error('[Indexer] Post-write hook error:', error.message); - return null; - } -} - -// Export for use as a module -if (typeof module !== 'undefined' && module.exports) { - module.exports = postWriteHook; -} - -// Handle direct execution (when called by Claude Code) -if (require.main === module) { - // Read context from stdin or command line args - let context; - - if (process.argv.length > 2) { - // Context passed as command line argument - try { - context = JSON.parse(process.argv[2]); - } catch (error) { - logger.error('[Indexer] Invalid context JSON:', error.message); - process.exit(1); - } - - // Execute hook - postWriteHook(context).then(result => { - if (result) { - logger.info(JSON.stringify(result)); - } - process.exit(0); - }); - } else { - // Context passed via stdin - let input = ''; - process.stdin.on('data', chunk => input += chunk); - process.stdin.on('end', async () => { - try { - context = JSON.parse(input); - const result = await postWriteHook(context); - - // Output result for Claude Code - if (result) { - logger.info(JSON.stringify(result)); - } - process.exit(0); - } catch (error) { - logger.error('[Indexer] Hook execution error:', error.message); - process.exit(1); - } - }); - - // If no stdin after 100ms, try to read from environment - setTimeout(() => { - if (!input && process.env.CLAUDE_HOOK_CONTEXT) { - try { - context = JSON.parse(process.env.CLAUDE_HOOK_CONTEXT); - postWriteHook(context).then(result => { - if (result) { - logger.info(JSON.stringify(result)); - } - process.exit(0); - }); - } catch (error) { - logger.error('[Indexer] Environment context error:', error.message); - process.exit(1); - } - } - }, 100); - } + +/** + * Main hook handler + * @param {Object} context - Hook context from Claude Code + * @param {string} context.file - File path that was written + * @param {string} context.operation - Operation type (write, create, delete, etc.) + * @param {Object} context.metadata - Additional metadata + * @param {boolean} context.success - Whether the operation succeeded + * @returns {Object} Modified context or null to proceed normally + */ +async function postWriteHook(context) { + try { + // Extract file information + const { file, operation, metadata = {}, success = true } = context; + + // Skip if operation failed + if (!success) { + return null; + } + + // Handle different operations + const updateOperations = ['write', 'create', 'modify', 'delete']; + if (!updateOperations.includes(operation)) { + return null; + } + + // Get file extension to check if it's a source file + const ext = path.extname(file).toLowerCase(); + const sourceExtensions = [ + '.js', '.jsx', '.ts', '.tsx', + '.py', '.go', '.java', '.cpp', '.c', '.h', '.hpp', + '.cs', '.rb', '.php', '.swift', '.kt', '.rs', + '.sql', '.graphql', '.gql', '.yaml', '.yml', '.astro' + ]; + + // Skip non-source files + if (!sourceExtensions.includes(ext)) { + return null; + } + + // Check if we should update the index + const indexPath = path.resolve('.indexer-output/current/indexes/INDEX.json'); + const shouldUpdate = fs.existsSync(indexPath); + + if (shouldUpdate) { + logger.info(`[Indexer] Updating index after ${operation}: ${file}`); + + // Perform incremental update for single file + try { + // Use incremental mode for better performance + const result = execSync('indexer scan --incremental --quiet', { + stdio: 'pipe', + timeout: 5000 + }); + + // Log success + if (process.env.DEBUG) { + logger.info('[Indexer] Index updated successfully'); + } + + // Parse updated index to get file info + if (fs.existsSync(indexPath)) { + const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8')); + const relativeFile = path.relative(process.cwd(), file); + + if (operation === 'delete') { + // Verify file was removed from index + if (!index.files[relativeFile]) { + logger.info(`[Indexer] File removed from index: ${relativeFile}`); + } + } else if (index.files[relativeFile]) { + // Get updated file info + const fileInfo = index.files[relativeFile]; + + // Add post-write metadata + context.metadata = { + ...metadata, + indexed: true, + updateTime: new Date().toISOString(), + functions: fileInfo.functions?.length || 0, + classes: fileInfo.classes?.length || 0, + complexity: fileInfo.complexity || 0, + linesOfCode: fileInfo.linesOfCode || 0 + }; + + // Detect potential issues + if (fileInfo.complexity > 20) { + logger.info(`[Indexer] ⚠️ High complexity detected in ${relativeFile}: ${fileInfo.complexity}`); + } + + // Check for circular dependencies + if (fileInfo.circularDependencies?.length > 0) { + logger.info(`[Indexer] ⚠️ Circular dependencies detected in ${relativeFile}`); + } + } + } + + } catch (error) { + // Don't fail the hook, just log the error + logger.warn('[Indexer] Could not update index:', error.message); + + // Mark for manual update + context.metadata = { + ...metadata, + indexUpdatePending: true, + indexError: error.message + }; + } + } else { + // No index exists yet + if (process.env.DEBUG) { + logger.info('[Indexer] No index to update. Run "indexer init" to create one.'); + } + } + + // Track file changes for batch updates + const changesFile = path.resolve('.indexer-cache/pending-changes.json'); + const cacheDir = path.dirname(changesFile); + + // Ensure cache directory exists + if (!fs.existsSync(cacheDir)) { + fs.mkdirSync(cacheDir, { recursive: true }); + } + + // Load or create changes list + let changes = []; + if (fs.existsSync(changesFile)) { + try { + changes = JSON.parse(fs.readFileSync(changesFile, 'utf-8')); + } catch (e) { + changes = []; + } + } + + // Add this change + changes.push({ + file: path.relative(process.cwd(), file), + operation, + timestamp: new Date().toISOString(), + metadata: context.metadata + }); + + // Keep only last 100 changes + if (changes.length > 100) { + changes = changes.slice(-100); + } + + // Save changes + fs.writeFileSync(changesFile, JSON.stringify(changes, null, 2)); + + // Check if we should trigger a batch update + if (changes.length >= 10) { + logger.info('[Indexer] Multiple changes detected. Consider running "indexer scan" for a full update.'); + } + + // Return null to proceed normally (don't modify Claude's behavior) + return null; + + } catch (error) { + // Log errors but don't interrupt Claude's operation + logger.error('[Indexer] Post-write hook error:', error.message); + return null; + } +} + +// Export for use as a module +if (typeof module !== 'undefined' && module.exports) { + module.exports = postWriteHook; +} + +// Handle direct execution (when called by Claude Code) +if (require.main === module) { + // Read context from stdin or command line args + let context; + + if (process.argv.length > 2) { + // Context passed as command line argument + try { + context = JSON.parse(process.argv[2]); + } catch (error) { + logger.error('[Indexer] Invalid context JSON:', error.message); + process.exit(1); + } + + // Execute hook + postWriteHook(context).then(result => { + if (result) { + logger.info(JSON.stringify(result)); + } + process.exit(0); + }); + } else { + // Context passed via stdin + let input = ''; + process.stdin.on('data', chunk => input += chunk); + process.stdin.on('end', async () => { + try { + context = JSON.parse(input); + const result = await postWriteHook(context); + + // Output result for Claude Code + if (result) { + logger.info(JSON.stringify(result)); + } + process.exit(0); + } catch (error) { + logger.error('[Indexer] Hook execution error:', error.message); + process.exit(1); + } + }); + + // If no stdin after 100ms, try to read from environment + setTimeout(() => { + if (!input && process.env.CLAUDE_HOOK_CONTEXT) { + try { + context = JSON.parse(process.env.CLAUDE_HOOK_CONTEXT); + postWriteHook(context).then(result => { + if (result) { + logger.info(JSON.stringify(result)); + } + process.exit(0); + }); + } catch (error) { + logger.error('[Indexer] Environment context error:', error.message); + process.exit(1); + } + } + }, 100); + } } \ No newline at end of file diff --git a/src/hooks/pre-read.js b/src/hooks/pre-read.js index beef1d1..e03af5b 100644 --- a/src/hooks/pre-read.js +++ b/src/hooks/pre-read.js @@ -1,150 +1,150 @@ -#!/usr/bin/env node -/** - * Pre-Read Hook for Claude Code Integration - * - * This hook is triggered BEFORE Claude reads any file. - * It allows the indexer to prepare context or update the index if needed. - * - * Spec-compliant structure for Claude Code hooks. - */ - -const fs = require('fs'); -const path = require('path'); +#!/usr/bin/env node +/** + * Pre-Read Hook for Claude Code Integration + * + * This hook is triggered BEFORE Claude reads any file. + * It allows the indexer to prepare context or update the index if needed. + * + * Spec-compliant structure for Claude Code hooks. + */ + +const fs = require('fs'); +const path = require('path'); import logger from '../../utils/logger'; - -/** - * Main hook handler - * @param {Object} context - Hook context from Claude Code - * @param {string} context.file - File path being read - * @param {string} context.operation - Operation type (read, write, etc.) - * @param {Object} context.metadata - Additional metadata - * @returns {Object} Modified context or null to proceed normally - */ -async function preReadHook(context) { - try { - // Extract file information - const { file, operation, metadata = {} } = context; - - // Skip if not a read operation - if (operation !== 'read') { - return null; - } - - // Check if index exists and is current - const indexPath = path.resolve('.indexer-output/current/indexes/INDEX.json'); - if (!fs.existsSync(indexPath)) { - logger.info('[Indexer] No index found. Creating index for better context...'); - - // Trigger index creation - const { execSync } = require('child_process'); - try { - execSync('indexer scan --quiet', { - stdio: 'pipe', - timeout: 30000 - }); - logger.info('[Indexer] Index created successfully'); - } catch (error) { - logger.warn('[Indexer] Could not create index:', error.message); - } - } else { - // Check if index is stale - const stats = fs.statSync(indexPath); - const ageInMinutes = (Date.now() - stats.mtimeMs) / 60000; - - if (ageInMinutes > 60) { - logger.info('[Indexer] Index is stale. Consider running "indexer scan" for updated context.'); - } - } - - // Check if the file being read is in the index - if (fs.existsSync(indexPath)) { - const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8')); - const relativeFile = path.relative(process.cwd(), file); - - if (index.files && index.files[relativeFile]) { - const fileInfo = index.files[relativeFile]; - - // Add context metadata - context.metadata = { - ...metadata, - indexed: true, - language: fileInfo.language, - functions: fileInfo.functions?.length || 0, - classes: fileInfo.classes?.length || 0, - imports: fileInfo.imports?.length || 0, - exports: fileInfo.exports?.length || 0, - complexity: fileInfo.complexity || 0 - }; - - // Log file context (won't affect Claude's context window) - if (process.env.DEBUG) { - logger.info(`[Indexer] File context: ${JSON.stringify(context.metadata)}`); - } - } - } - - // Return null to proceed with normal read - // Returning modified context would override the read - return null; - - } catch (error) { - // Log errors but don't interrupt Claude's operation - logger.error('[Indexer] Pre-read hook error:', error.message); - return null; - } -} - -// Export for use as a module -if (typeof module !== 'undefined' && module.exports) { - module.exports = preReadHook; -} - -// Handle direct execution (when called by Claude Code) -if (require.main === module) { - // Read context from stdin or command line args - let context; - - if (process.argv.length > 2) { - // Context passed as command line argument - try { - context = JSON.parse(process.argv[2]); - } catch (error) { - logger.error('[Indexer] Invalid context JSON:', error.message); - process.exit(1); - } - } else { - // Context passed via stdin - let input = ''; - process.stdin.on('data', chunk => input += chunk); - process.stdin.on('end', async () => { - try { - context = JSON.parse(input); - const result = await preReadHook(context); - - // Output result for Claude Code - if (result) { - logger.info(JSON.stringify(result)); - } - } catch (error) { - logger.error('[Indexer] Hook execution error:', error.message); - process.exit(1); - } - }); - - // If no stdin after 100ms, try to read from environment - setTimeout(() => { - if (!input && process.env.CLAUDE_HOOK_CONTEXT) { - try { - context = JSON.parse(process.env.CLAUDE_HOOK_CONTEXT); - preReadHook(context).then(result => { - if (result) { - logger.info(JSON.stringify(result)); - } - }); - } catch (error) { - logger.error('[Indexer] Environment context error:', error.message); - } - } - }, 100); - } + +/** + * Main hook handler + * @param {Object} context - Hook context from Claude Code + * @param {string} context.file - File path being read + * @param {string} context.operation - Operation type (read, write, etc.) + * @param {Object} context.metadata - Additional metadata + * @returns {Object} Modified context or null to proceed normally + */ +async function preReadHook(context) { + try { + // Extract file information + const { file, operation, metadata = {} } = context; + + // Skip if not a read operation + if (operation !== 'read') { + return null; + } + + // Check if index exists and is current + const indexPath = path.resolve('.indexer-output/current/indexes/INDEX.json'); + if (!fs.existsSync(indexPath)) { + logger.info('[Indexer] No index found. Creating index for better context...'); + + // Trigger index creation + const { execSync } = require('child_process'); + try { + execSync('indexer scan --quiet', { + stdio: 'pipe', + timeout: 30000 + }); + logger.info('[Indexer] Index created successfully'); + } catch (error) { + logger.warn('[Indexer] Could not create index:', error.message); + } + } else { + // Check if index is stale + const stats = fs.statSync(indexPath); + const ageInMinutes = (Date.now() - stats.mtimeMs) / 60000; + + if (ageInMinutes > 60) { + logger.info('[Indexer] Index is stale. Consider running "indexer scan" for updated context.'); + } + } + + // Check if the file being read is in the index + if (fs.existsSync(indexPath)) { + const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8')); + const relativeFile = path.relative(process.cwd(), file); + + if (index.files && index.files[relativeFile]) { + const fileInfo = index.files[relativeFile]; + + // Add context metadata + context.metadata = { + ...metadata, + indexed: true, + language: fileInfo.language, + functions: fileInfo.functions?.length || 0, + classes: fileInfo.classes?.length || 0, + imports: fileInfo.imports?.length || 0, + exports: fileInfo.exports?.length || 0, + complexity: fileInfo.complexity || 0 + }; + + // Log file context (won't affect Claude's context window) + if (process.env.DEBUG) { + logger.info(`[Indexer] File context: ${JSON.stringify(context.metadata)}`); + } + } + } + + // Return null to proceed with normal read + // Returning modified context would override the read + return null; + + } catch (error) { + // Log errors but don't interrupt Claude's operation + logger.error('[Indexer] Pre-read hook error:', error.message); + return null; + } +} + +// Export for use as a module +if (typeof module !== 'undefined' && module.exports) { + module.exports = preReadHook; +} + +// Handle direct execution (when called by Claude Code) +if (require.main === module) { + // Read context from stdin or command line args + let context; + + if (process.argv.length > 2) { + // Context passed as command line argument + try { + context = JSON.parse(process.argv[2]); + } catch (error) { + logger.error('[Indexer] Invalid context JSON:', error.message); + process.exit(1); + } + } else { + // Context passed via stdin + let input = ''; + process.stdin.on('data', chunk => input += chunk); + process.stdin.on('end', async () => { + try { + context = JSON.parse(input); + const result = await preReadHook(context); + + // Output result for Claude Code + if (result) { + logger.info(JSON.stringify(result)); + } + } catch (error) { + logger.error('[Indexer] Hook execution error:', error.message); + process.exit(1); + } + }); + + // If no stdin after 100ms, try to read from environment + setTimeout(() => { + if (!input && process.env.CLAUDE_HOOK_CONTEXT) { + try { + context = JSON.parse(process.env.CLAUDE_HOOK_CONTEXT); + preReadHook(context).then(result => { + if (result) { + logger.info(JSON.stringify(result)); + } + }); + } catch (error) { + logger.error('[Indexer] Environment context error:', error.message); + } + } + }, 100); + } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ac11fd1..8b3f788 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,26 +1,26 @@ -// Core functionality -export { Indexer } from './core/indexer'; -export { ConfigLoader } from './core/config'; -export { CacheManager } from './core/cache-manager'; -export { FileWatcher } from './core/watcher'; -export { MonorepoAnalyzer } from './core/monorepo'; - -// Parsers -export { getParser, JavaScriptParser, TypeScriptParser, PythonParser, GoParser, SQLParser, GraphQLParser, YAMLParser, AstroParser } from './parsers'; - -// Exporters -export { JSONExporter } from './exporters/json'; -export { GraphVizExporter } from './exporters/graphviz'; -export { MarkdownExporter } from './exporters/markdown'; -export { MermaidExporter } from './exporters/mermaid'; -export { ASCIIExporter } from './exporters/ascii'; - -// Search and query -export { fuzzyMatch } from './search/fuzzy-matcher'; -export { QueryEngine } from './utils/query'; - -// Integrations -export * from './integrations'; - -// Types +// Core functionality +export { Indexer } from './core/indexer'; +export { ConfigLoader } from './core/config'; +export { CacheManager } from './core/cache-manager'; +export { FileWatcher } from './core/watcher'; +export { MonorepoAnalyzer } from './core/monorepo'; + +// Parsers +export { getParser, JavaScriptParser, TypeScriptParser, PythonParser, GoParser, SQLParser, GraphQLParser, YAMLParser, AstroParser } from './parsers'; + +// Exporters +export { JSONExporter } from './exporters/json'; +export { GraphVizExporter } from './exporters/graphviz'; +export { MarkdownExporter } from './exporters/markdown'; +export { MermaidExporter } from './exporters/mermaid'; +export { ASCIIExporter } from './exporters/ascii'; + +// Search and query +export { fuzzyMatch } from './search/fuzzy-matcher'; +export { QueryEngine } from './utils/query'; + +// Integrations +export * from './integrations'; + +// Types export * from './types'; \ No newline at end of file diff --git a/src/integrations/datadog/dashboard.json b/src/integrations/datadog/dashboard.json index 9e1b5fc..267f63c 100644 --- a/src/integrations/datadog/dashboard.json +++ b/src/integrations/datadog/dashboard.json @@ -1,197 +1,197 @@ -{ - "title": "Code Indexer Performance Dashboard", - "description": "Monitor the performance and health of the @cloneglobal/indexer", - "widgets": [ - { - "id": 1, - "definition": { - "title": "Indexing Performance", - "type": "timeseries", - "requests": [ - { - "q": "avg:indexer.index.duration{*}", - "display_type": "line", - "style": { - "palette": "dog_classic", - "line_type": "solid", - "line_width": "normal" - } - } - ], - "yaxis": { - "label": "Duration (ms)", - "scale": "linear" - } - } - }, - { - "id": 2, - "definition": { - "title": "Files Processed", - "type": "query_value", - "requests": [ - { - "q": "sum:indexer.index.file_count{*}.as_count()", - "aggregator": "last" - } - ], - "precision": 0 - } - }, - { - "id": 3, - "definition": { - "title": "Throughput (files/sec)", - "type": "timeseries", - "requests": [ - { - "q": "avg:indexer.index.throughput_files_per_second{*}", - "display_type": "bars" - } - ] - } - }, - { - "id": 4, - "definition": { - "title": "Memory Usage", - "type": "timeseries", - "requests": [ - { - "q": "avg:indexer.memory.heap_used{*}", - "display_type": "line", - "style": { - "palette": "warm" - } - }, - { - "q": "avg:indexer.memory.heap_total{*}", - "display_type": "line", - "style": { - "palette": "cool" - } - } - ], - "yaxis": { - "label": "Bytes", - "scale": "linear" - } - } - }, - { - "id": 5, - "definition": { - "title": "Cache Performance", - "type": "timeseries", - "requests": [ - { - "q": "sum:indexer.cache.hit{*}.as_rate()", - "display_type": "line", - "style": { - "palette": "green" - } - }, - { - "q": "sum:indexer.cache.miss{*}.as_rate()", - "display_type": "line", - "style": { - "palette": "orange" - } - }, - { - "q": "sum:indexer.cache.eviction{*}.as_rate()", - "display_type": "line", - "style": { - "palette": "red" - } - } - ] - } - }, - { - "id": 6, - "definition": { - "title": "Error Rate", - "type": "timeseries", - "requests": [ - { - "q": "sum:indexer.errors{*}.as_rate()", - "display_type": "bars", - "style": { - "palette": "red" - } - } - ] - } - }, - { - "id": 7, - "definition": { - "title": "Language Distribution", - "type": "toplist", - "requests": [ - { - "q": "top(avg:indexer.languages.file_count{*} by {language}, 10, 'mean', 'desc')" - } - ] - } - }, - { - "id": 8, - "definition": { - "title": "Success Rate", - "type": "query_value", - "requests": [ - { - "q": "(sum:indexer.index.succeeded{*}.as_count()/(sum:indexer.index.succeeded{*}.as_count()+sum:indexer.index.failed{*}.as_count()))*100", - "aggregator": "last" - } - ], - "precision": 2, - "custom_unit": "%" - } - }, - { - "id": 9, - "definition": { - "title": "Average Complexity", - "type": "query_value", - "requests": [ - { - "q": "avg:indexer.project.avg_complexity{*}", - "aggregator": "last" - } - ], - "precision": 2 - } - }, - { - "id": 10, - "definition": { - "title": "File Operation Performance", - "type": "distribution", - "requests": [ - { - "q": "avg:indexer.file.operation.duration{*} by {operation,file_type}" - } - ] - } - } - ], - "template_variables": [ - { - "name": "env", - "default": "development", - "prefix": "env" - }, - { - "name": "version", - "default": "*", - "prefix": "version" - } - ], - "layout_type": "ordered", - "is_read_only": false, - "notify_list": [], - "reflow_type": "fixed" +{ + "title": "Code Indexer Performance Dashboard", + "description": "Monitor the performance and health of the @cloneglobal/indexer", + "widgets": [ + { + "id": 1, + "definition": { + "title": "Indexing Performance", + "type": "timeseries", + "requests": [ + { + "q": "avg:indexer.index.duration{*}", + "display_type": "line", + "style": { + "palette": "dog_classic", + "line_type": "solid", + "line_width": "normal" + } + } + ], + "yaxis": { + "label": "Duration (ms)", + "scale": "linear" + } + } + }, + { + "id": 2, + "definition": { + "title": "Files Processed", + "type": "query_value", + "requests": [ + { + "q": "sum:indexer.index.file_count{*}.as_count()", + "aggregator": "last" + } + ], + "precision": 0 + } + }, + { + "id": 3, + "definition": { + "title": "Throughput (files/sec)", + "type": "timeseries", + "requests": [ + { + "q": "avg:indexer.index.throughput_files_per_second{*}", + "display_type": "bars" + } + ] + } + }, + { + "id": 4, + "definition": { + "title": "Memory Usage", + "type": "timeseries", + "requests": [ + { + "q": "avg:indexer.memory.heap_used{*}", + "display_type": "line", + "style": { + "palette": "warm" + } + }, + { + "q": "avg:indexer.memory.heap_total{*}", + "display_type": "line", + "style": { + "palette": "cool" + } + } + ], + "yaxis": { + "label": "Bytes", + "scale": "linear" + } + } + }, + { + "id": 5, + "definition": { + "title": "Cache Performance", + "type": "timeseries", + "requests": [ + { + "q": "sum:indexer.cache.hit{*}.as_rate()", + "display_type": "line", + "style": { + "palette": "green" + } + }, + { + "q": "sum:indexer.cache.miss{*}.as_rate()", + "display_type": "line", + "style": { + "palette": "orange" + } + }, + { + "q": "sum:indexer.cache.eviction{*}.as_rate()", + "display_type": "line", + "style": { + "palette": "red" + } + } + ] + } + }, + { + "id": 6, + "definition": { + "title": "Error Rate", + "type": "timeseries", + "requests": [ + { + "q": "sum:indexer.errors{*}.as_rate()", + "display_type": "bars", + "style": { + "palette": "red" + } + } + ] + } + }, + { + "id": 7, + "definition": { + "title": "Language Distribution", + "type": "toplist", + "requests": [ + { + "q": "top(avg:indexer.languages.file_count{*} by {language}, 10, 'mean', 'desc')" + } + ] + } + }, + { + "id": 8, + "definition": { + "title": "Success Rate", + "type": "query_value", + "requests": [ + { + "q": "(sum:indexer.index.succeeded{*}.as_count()/(sum:indexer.index.succeeded{*}.as_count()+sum:indexer.index.failed{*}.as_count()))*100", + "aggregator": "last" + } + ], + "precision": 2, + "custom_unit": "%" + } + }, + { + "id": 9, + "definition": { + "title": "Average Complexity", + "type": "query_value", + "requests": [ + { + "q": "avg:indexer.project.avg_complexity{*}", + "aggregator": "last" + } + ], + "precision": 2 + } + }, + { + "id": 10, + "definition": { + "title": "File Operation Performance", + "type": "distribution", + "requests": [ + { + "q": "avg:indexer.file.operation.duration{*} by {operation,file_type}" + } + ] + } + } + ], + "template_variables": [ + { + "name": "env", + "default": "development", + "prefix": "env" + }, + { + "name": "version", + "default": "*", + "prefix": "version" + } + ], + "layout_type": "ordered", + "is_read_only": false, + "notify_list": [], + "reflow_type": "fixed" } \ No newline at end of file diff --git a/src/integrations/datadog/index.ts b/src/integrations/datadog/index.ts index b9364e6..f6ec553 100644 --- a/src/integrations/datadog/index.ts +++ b/src/integrations/datadog/index.ts @@ -1,2 +1,2 @@ -export { DatadogMetrics } from './metrics'; +export { DatadogMetrics } from './metrics'; export type { DatadogConfig, IndexMetrics } from './metrics'; \ No newline at end of file diff --git a/src/integrations/datadog/metrics.ts b/src/integrations/datadog/metrics.ts index 230bc18..ef12a06 100644 --- a/src/integrations/datadog/metrics.ts +++ b/src/integrations/datadog/metrics.ts @@ -1,222 +1,222 @@ -import { StatsD } from 'node-dogstatsd'; -import { ProjectIndex } from '../../types'; +import { StatsD } from 'node-dogstatsd'; +import { ProjectIndex } from '../../types'; import logger from '../../utils/logger'; - -export interface DatadogConfig { - apiKey?: string; - appKey?: string; - host?: string; - port?: number; - prefix?: string; - globalTags?: string[]; - enabled?: boolean; -} - -export interface IndexMetrics { - duration: number; - fileCount: number; - functionCount: number; - classCount: number; - languages: Record; - errors: number; - cacheHitRate?: number; - memoryUsage: number; -} - -export class DatadogMetrics { - private client: StatsD | null = null; - private enabled: boolean; - private startTimes: Map = new Map(); - - constructor(config: DatadogConfig = {}) { - this.enabled = config.enabled !== false; - - if (this.enabled) { - try { - this.client = new StatsD({ - host: config.host || process.env.DD_AGENT_HOST || 'localhost', - port: config.port || 8125, - prefix: config.prefix || 'indexer.', - globalTags: [ - ...(config.globalTags || []), - `version:${require('../../../package.json').version}`, - `env:${process.env.NODE_ENV || 'development'}`, - `service:indexer` - ] - }); - } catch (error) { - logger.warn('Failed to initialize Datadog client:', error); - this.enabled = false; - } - } - } - - // Track indexing performance - trackIndexing(metrics: IndexMetrics): void { - if (!this.enabled || !this.client) return; - - try { - // Performance metrics - this.client.histogram('index.duration', metrics.duration); - this.client.gauge('index.file_count', metrics.fileCount); - this.client.gauge('index.function_count', metrics.functionCount); - this.client.gauge('index.class_count', metrics.classCount); - this.client.gauge('index.memory_usage_bytes', metrics.memoryUsage); - - // Cache metrics - if (metrics.cacheHitRate !== undefined) { - this.client.gauge('cache.hit_rate', metrics.cacheHitRate); - } - - // Language distribution - for (const [lang, count] of Object.entries(metrics.languages)) { - this.client.gauge('languages.file_count', count, [`language:${lang}`]); - } - - // Success/error tracking - if (metrics.errors > 0) { - this.client.increment('index.failed', 1); - this.client.gauge('index.error_count', metrics.errors); - } else { - this.client.increment('index.succeeded', 1); - } - - // Throughput metric - if (metrics.duration > 0) { - const filesPerSecond = (metrics.fileCount / metrics.duration) * 1000; - this.client.gauge('index.throughput_files_per_second', filesPerSecond); - } - } catch (error) { - logger.error('Failed to send metrics to Datadog:', error); - } - } - - // Track errors with context - trackError(error: Error, context: { component: string; operation?: string; metadata?: any }): void { - if (!this.enabled || !this.client) return; - - try { - const tags = [ - `error_type:${error.name}`, - `component:${context.component}` - ]; - - if (context.operation) { - tags.push(`operation:${context.operation}`); - } - - this.client.increment('errors', 1, tags); - - // Send error details as an event - this.client.event( - `Error in ${context.component}`, - error.message, - { - alert_type: 'error', - tags, - aggregation_key: `${context.component}:${error.name}` - } - ); - } catch (err) { - logger.error('Failed to track error:', err); - } - } - - // Start a timing span - startSpan(operation: string): void { - if (!this.enabled) return; - this.startTimes.set(operation, Date.now()); - } - - // End a timing span and record the duration - endSpan(operation: string, tags?: string[]): void { - if (!this.enabled || !this.client) return; - - const startTime = this.startTimes.get(operation); - if (startTime) { - const duration = Date.now() - startTime; - this.client.histogram(`operation.${operation}.duration`, duration, tags); - this.startTimes.delete(operation); - } - } - - // Track file operations - trackFileOperation(operation: 'read' | 'write' | 'parse', success: boolean, duration: number, fileType?: string): void { - if (!this.enabled || !this.client) return; - - const tags = [ - `operation:${operation}`, - `success:${success}` - ]; - - if (fileType) { - tags.push(`file_type:${fileType}`); - } - - this.client.histogram('file.operation.duration', duration, tags); - this.client.increment('file.operations', 1, tags); - } - - // Track cache performance - trackCacheOperation(operation: 'hit' | 'miss' | 'eviction', tags?: string[]): void { - if (!this.enabled || !this.client) return; - - this.client.increment(`cache.${operation}`, 1, tags); - } - - // Track memory usage - trackMemoryUsage(): void { - if (!this.enabled || !this.client) return; - - const usage = process.memoryUsage(); - this.client.gauge('memory.heap_used', usage.heapUsed); - this.client.gauge('memory.heap_total', usage.heapTotal); - this.client.gauge('memory.rss', usage.rss); - this.client.gauge('memory.external', usage.external); - } - - // Track project statistics from index - trackProjectStats(index: ProjectIndex): void { - if (!this.enabled || !this.client) return; - - const stats = index.statistics; - - this.client.gauge('project.total_files', stats.totalFiles); - this.client.gauge('project.total_functions', stats.totalFunctions); - this.client.gauge('project.total_classes', stats.totalClasses); - - if (stats.avgComplexity) { - this.client.gauge('project.avg_complexity', stats.avgComplexity); - } - - // Track monorepo services if applicable - if (index.monorepo) { - const serviceCount = Object.keys(index.monorepo.services || {}).length; - this.client.gauge('project.service_count', serviceCount); - - // Track cross-service dependencies - if (index.monorepo.crossServiceDependencies) { - this.client.gauge('project.cross_service_dependencies', index.monorepo.crossServiceDependencies.length); - } - } - } - - // Send a custom event - sendEvent(title: string, text: string, alertType: 'info' | 'warning' | 'error' | 'success' = 'info'): void { - if (!this.enabled || !this.client) return; - - this.client.event(title, text, { - alert_type: alertType, - tags: [`service:indexer`] - }); - } - - // Flush any pending metrics - async flush(): Promise { - if (!this.enabled || !this.client) return; - - return new Promise((resolve) => { - this.client!.close(() => resolve()); - }); - } + +export interface DatadogConfig { + apiKey?: string; + appKey?: string; + host?: string; + port?: number; + prefix?: string; + globalTags?: string[]; + enabled?: boolean; +} + +export interface IndexMetrics { + duration: number; + fileCount: number; + functionCount: number; + classCount: number; + languages: Record; + errors: number; + cacheHitRate?: number; + memoryUsage: number; +} + +export class DatadogMetrics { + private client: StatsD | null = null; + private enabled: boolean; + private startTimes: Map = new Map(); + + constructor(config: DatadogConfig = {}) { + this.enabled = config.enabled !== false; + + if (this.enabled) { + try { + this.client = new StatsD({ + host: config.host || process.env.DD_AGENT_HOST || 'localhost', + port: config.port || 8125, + prefix: config.prefix || 'indexer.', + globalTags: [ + ...(config.globalTags || []), + `version:${require('../../../package.json').version}`, + `env:${process.env.NODE_ENV || 'development'}`, + `service:indexer` + ] + }); + } catch (error) { + logger.warn('Failed to initialize Datadog client:', error); + this.enabled = false; + } + } + } + + // Track indexing performance + trackIndexing(metrics: IndexMetrics): void { + if (!this.enabled || !this.client) return; + + try { + // Performance metrics + this.client.histogram('index.duration', metrics.duration); + this.client.gauge('index.file_count', metrics.fileCount); + this.client.gauge('index.function_count', metrics.functionCount); + this.client.gauge('index.class_count', metrics.classCount); + this.client.gauge('index.memory_usage_bytes', metrics.memoryUsage); + + // Cache metrics + if (metrics.cacheHitRate !== undefined) { + this.client.gauge('cache.hit_rate', metrics.cacheHitRate); + } + + // Language distribution + for (const [lang, count] of Object.entries(metrics.languages)) { + this.client.gauge('languages.file_count', count, [`language:${lang}`]); + } + + // Success/error tracking + if (metrics.errors > 0) { + this.client.increment('index.failed', 1); + this.client.gauge('index.error_count', metrics.errors); + } else { + this.client.increment('index.succeeded', 1); + } + + // Throughput metric + if (metrics.duration > 0) { + const filesPerSecond = (metrics.fileCount / metrics.duration) * 1000; + this.client.gauge('index.throughput_files_per_second', filesPerSecond); + } + } catch (error) { + logger.error('Failed to send metrics to Datadog:', error); + } + } + + // Track errors with context + trackError(error: Error, context: { component: string; operation?: string; metadata?: any }): void { + if (!this.enabled || !this.client) return; + + try { + const tags = [ + `error_type:${error.name}`, + `component:${context.component}` + ]; + + if (context.operation) { + tags.push(`operation:${context.operation}`); + } + + this.client.increment('errors', 1, tags); + + // Send error details as an event + this.client.event( + `Error in ${context.component}`, + error.message, + { + alert_type: 'error', + tags, + aggregation_key: `${context.component}:${error.name}` + } + ); + } catch (err) { + logger.error('Failed to track error:', err); + } + } + + // Start a timing span + startSpan(operation: string): void { + if (!this.enabled) return; + this.startTimes.set(operation, Date.now()); + } + + // End a timing span and record the duration + endSpan(operation: string, tags?: string[]): void { + if (!this.enabled || !this.client) return; + + const startTime = this.startTimes.get(operation); + if (startTime) { + const duration = Date.now() - startTime; + this.client.histogram(`operation.${operation}.duration`, duration, tags); + this.startTimes.delete(operation); + } + } + + // Track file operations + trackFileOperation(operation: 'read' | 'write' | 'parse', success: boolean, duration: number, fileType?: string): void { + if (!this.enabled || !this.client) return; + + const tags = [ + `operation:${operation}`, + `success:${success}` + ]; + + if (fileType) { + tags.push(`file_type:${fileType}`); + } + + this.client.histogram('file.operation.duration', duration, tags); + this.client.increment('file.operations', 1, tags); + } + + // Track cache performance + trackCacheOperation(operation: 'hit' | 'miss' | 'eviction', tags?: string[]): void { + if (!this.enabled || !this.client) return; + + this.client.increment(`cache.${operation}`, 1, tags); + } + + // Track memory usage + trackMemoryUsage(): void { + if (!this.enabled || !this.client) return; + + const usage = process.memoryUsage(); + this.client.gauge('memory.heap_used', usage.heapUsed); + this.client.gauge('memory.heap_total', usage.heapTotal); + this.client.gauge('memory.rss', usage.rss); + this.client.gauge('memory.external', usage.external); + } + + // Track project statistics from index + trackProjectStats(index: ProjectIndex): void { + if (!this.enabled || !this.client) return; + + const stats = index.statistics; + + this.client.gauge('project.total_files', stats.totalFiles); + this.client.gauge('project.total_functions', stats.totalFunctions); + this.client.gauge('project.total_classes', stats.totalClasses); + + if (stats.avgComplexity) { + this.client.gauge('project.avg_complexity', stats.avgComplexity); + } + + // Track monorepo services if applicable + if (index.monorepo) { + const serviceCount = Object.keys(index.monorepo.services || {}).length; + this.client.gauge('project.service_count', serviceCount); + + // Track cross-service dependencies + if (index.monorepo.crossServiceDependencies) { + this.client.gauge('project.cross_service_dependencies', index.monorepo.crossServiceDependencies.length); + } + } + } + + // Send a custom event + sendEvent(title: string, text: string, alertType: 'info' | 'warning' | 'error' | 'success' = 'info'): void { + if (!this.enabled || !this.client) return; + + this.client.event(title, text, { + alert_type: alertType, + tags: [`service:indexer`] + }); + } + + // Flush any pending metrics + async flush(): Promise { + if (!this.enabled || !this.client) return; + + return new Promise((resolve) => { + this.client!.close(() => resolve()); + }); + } } \ No newline at end of file diff --git a/src/integrations/index.ts b/src/integrations/index.ts index d7c9f90..e036162 100644 --- a/src/integrations/index.ts +++ b/src/integrations/index.ts @@ -1,5 +1,5 @@ -// Datadog monitoring integration -export * from './datadog'; - -// Slack bot integration for bug monitoring +// Datadog monitoring integration +export * from './datadog'; + +// Slack bot integration for bug monitoring export * from './slack'; \ No newline at end of file diff --git a/src/integrations/slack/README.md b/src/integrations/slack/README.md index 6bd78c3..d290c33 100644 --- a/src/integrations/slack/README.md +++ b/src/integrations/slack/README.md @@ -1,252 +1,252 @@ -# Slack Integration for Bug Monitoring - -The indexer includes a powerful Slack bot integration that monitors bug reports in your Slack channels and automatically creates Linear tickets with code context. - -## Features - -- **Automatic Bug Detection**: Monitors designated Slack channels for bug reports -- **Intelligent Parsing**: Extracts keywords, stack traces, and severity from messages -- **Code Context**: Uses the project index to identify affected files and functions -- **Linear Integration**: Creates detailed tickets with code references -- **Severity Triage**: Automatically assigns priority based on message content -- **Optional PR Creation**: Can create pull requests for simple fixes - -## Setup - -### 1. Environment Variables - -```bash -export SLACK_BOT_TOKEN="xoxb-your-bot-token" -export SLACK_SIGNING_SECRET="your-signing-secret" -export LINEAR_API_KEY="lin_api_your_key" -``` - -### 2. Configuration - -Add to your `.indexer.yml`: - -```yaml -integrations: - slack: - enabled: true - bugChannel: bugs # or channel ID - port: 3000 - linear: - apiKey: ${LINEAR_API_KEY} - defaultPriority: 3 - autoCreatePR: false - monitoring: - autoTriage: true - maxTicketsPerHour: 20 - severityThresholds: - critical: ['production down', 'data loss', 'security'] - high: ['crash', 'broken', 'urgent'] - medium: ['error', 'exception', 'fail'] - low: ['warning', 'issue', 'bug'] -``` - -### 3. Slack App Setup - -1. Create a new Slack app at https://api.slack.com/apps -2. Add OAuth scopes: - - `channels:history` - Read channel messages - - `chat:write` - Post messages - - `users:read` - Get user information -3. Install app to workspace -4. Copy Bot User OAuth Token - -### 4. Linear Setup - -1. Get API key from https://linear.app/settings/api -2. Note your team ID (visible in Linear URLs) - -## Usage - -### Programmatic - -```typescript -import { SlackBugMonitor, SlackConfigLoader } from '@cloneglobal/indexer'; - -// Load configuration -const configLoader = new SlackConfigLoader('.indexer.yml'); - -// Initialize bot -const bot = new SlackBugMonitor({ - slackToken: process.env.SLACK_BOT_TOKEN, - slackSigningSecret: process.env.SLACK_SIGNING_SECRET, - linearApiKey: process.env.LINEAR_API_KEY, - bugChannelId: 'C1234567890', // Your #bugs channel ID - projectIndexPath: './PROJECT_INDEX.json', - enabled: true -}); - -// Start monitoring -await bot.start(); -console.log('Slack bot is monitoring for bugs...'); - -// Graceful shutdown -process.on('SIGTERM', async () => { - await bot.stop(); - process.exit(0); -}); -``` - -### CLI Integration - -```bash -# Start the Slack bot alongside indexer -indexer watch --slack - -# Or run standalone -indexer slack start --port 3000 -``` - -## How It Works - -### 1. Message Detection - -The bot monitors messages in the configured channel for bug indicators: -- Keywords: bug, error, crash, fail, exception -- Error patterns: TypeError, ReferenceError, SyntaxError -- Stack traces: Automatically extracted - -### 2. Bug Analysis - -For each detected bug report: -1. Extracts keywords and function names -2. Searches PROJECT_INDEX.json for related code -3. Identifies affected files and dependencies -4. Determines severity level - -### 3. Linear Ticket Creation - -Creates a comprehensive ticket including: -- Bug description and reporter -- Stack trace (if available) -- Affected files list -- Related functions and classes -- Code context from the index -- Severity-based priority - -### 4. Slack Notification - -Posts back to the thread with: -- Confirmation of ticket creation -- Linear ticket link -- Analysis summary -- Affected components - -## Example Bug Report Flow - -**User posts in #bugs:** -``` -"Getting TypeError in handleUserLogin function when email is null. -Stack trace: - at handleUserLogin (auth.service.ts:45:12) - at AuthController.login (auth.controller.ts:23:5)" -``` - -**Bot response:** -``` -🤖 Bug detected! Analyzing and creating Linear ticket... -Severity: medium -Keywords: TypeError, handleUserLogin, email, null -Affected files: 2 identified - -✅ Linear ticket created: LIN-1234 -Title: Bug: TypeError, handleUserLogin, email -``` - -**Linear ticket contains:** -- Full bug description -- Stack trace -- Links to auth.service.ts:45 and auth.controller.ts:23 -- Related function signatures -- Dependencies that might be affected - -## Advanced Features - -### Custom Severity Rules - -```typescript -const bot = new SlackBugMonitor({ - // ... other config - monitoring: { - severityThresholds: { - critical: ['production', 'data loss', 'security breach'], - high: ['crash', 'payment', 'authentication'], - medium: ['error', 'bug', 'issue'], - low: ['warning', 'typo', 'enhancement'] - } - } -}); -``` - -### Filtering Rules - -```typescript -// Only process messages matching certain criteria -bot.addFilter((message) => { - return message.channel === 'bugs' && - !message.text.includes('[DUPLICATE]'); -}); -``` - -### Custom Ticket Fields - -```typescript -// Add custom fields to Linear tickets -bot.onTicketCreate((ticket, bugReport) => { - ticket.customFields = { - reportedVia: 'Slack', - environment: detectEnvironment(bugReport.text), - component: identifyComponent(bugReport.affectedFiles) - }; - return ticket; -}); -``` - -## Monitoring & Metrics - -The Slack integration automatically sends metrics to Datadog: - -- `slack.bugs.detected` - Number of bugs detected -- `slack.tickets.created` - Linear tickets created -- `slack.processing.duration` - Time to process each bug -- `slack.severity.distribution` - Breakdown by severity - -## Security Considerations - -- Store tokens in environment variables, never in code -- Use Slack signing secret to verify requests -- Limit bot permissions to minimum required -- Implement rate limiting (default: 20 tickets/hour) -- Sanitize user input before creating tickets - -## Troubleshooting - -### Bot not responding -- Check Slack token and permissions -- Verify channel ID is correct -- Ensure PROJECT_INDEX.json exists -- Check network connectivity - -### Tickets not created -- Verify Linear API key -- Check team ID configuration -- Review rate limits -- Check Linear API status - -### Missing code context -- Ensure index is up-to-date -- Verify file paths in stack traces -- Check index includes affected files - -## Future Enhancements - -- AI-powered root cause analysis -- Automatic fix suggestions -- Duplicate detection -- Cross-reference with existing tickets -- Integration with GitHub Issues +# Slack Integration for Bug Monitoring + +The indexer includes a powerful Slack bot integration that monitors bug reports in your Slack channels and automatically creates Linear tickets with code context. + +## Features + +- **Automatic Bug Detection**: Monitors designated Slack channels for bug reports +- **Intelligent Parsing**: Extracts keywords, stack traces, and severity from messages +- **Code Context**: Uses the project index to identify affected files and functions +- **Linear Integration**: Creates detailed tickets with code references +- **Severity Triage**: Automatically assigns priority based on message content +- **Optional PR Creation**: Can create pull requests for simple fixes + +## Setup + +### 1. Environment Variables + +```bash +export SLACK_BOT_TOKEN="xoxb-your-bot-token" +export SLACK_SIGNING_SECRET="your-signing-secret" +export LINEAR_API_KEY="lin_api_your_key" +``` + +### 2. Configuration + +Add to your `.indexer.yml`: + +```yaml +integrations: + slack: + enabled: true + bugChannel: bugs # or channel ID + port: 3000 + linear: + apiKey: ${LINEAR_API_KEY} + defaultPriority: 3 + autoCreatePR: false + monitoring: + autoTriage: true + maxTicketsPerHour: 20 + severityThresholds: + critical: ['production down', 'data loss', 'security'] + high: ['crash', 'broken', 'urgent'] + medium: ['error', 'exception', 'fail'] + low: ['warning', 'issue', 'bug'] +``` + +### 3. Slack App Setup + +1. Create a new Slack app at https://api.slack.com/apps +2. Add OAuth scopes: + - `channels:history` - Read channel messages + - `chat:write` - Post messages + - `users:read` - Get user information +3. Install app to workspace +4. Copy Bot User OAuth Token + +### 4. Linear Setup + +1. Get API key from https://linear.app/settings/api +2. Note your team ID (visible in Linear URLs) + +## Usage + +### Programmatic + +```typescript +import { SlackBugMonitor, SlackConfigLoader } from '@cloneglobal/indexer'; + +// Load configuration +const configLoader = new SlackConfigLoader('.indexer.yml'); + +// Initialize bot +const bot = new SlackBugMonitor({ + slackToken: process.env.SLACK_BOT_TOKEN, + slackSigningSecret: process.env.SLACK_SIGNING_SECRET, + linearApiKey: process.env.LINEAR_API_KEY, + bugChannelId: 'C1234567890', // Your #bugs channel ID + projectIndexPath: './PROJECT_INDEX.json', + enabled: true +}); + +// Start monitoring +await bot.start(); +console.log('Slack bot is monitoring for bugs...'); + +// Graceful shutdown +process.on('SIGTERM', async () => { + await bot.stop(); + process.exit(0); +}); +``` + +### CLI Integration + +```bash +# Start the Slack bot alongside indexer +indexer watch --slack + +# Or run standalone +indexer slack start --port 3000 +``` + +## How It Works + +### 1. Message Detection + +The bot monitors messages in the configured channel for bug indicators: +- Keywords: bug, error, crash, fail, exception +- Error patterns: TypeError, ReferenceError, SyntaxError +- Stack traces: Automatically extracted + +### 2. Bug Analysis + +For each detected bug report: +1. Extracts keywords and function names +2. Searches PROJECT_INDEX.json for related code +3. Identifies affected files and dependencies +4. Determines severity level + +### 3. Linear Ticket Creation + +Creates a comprehensive ticket including: +- Bug description and reporter +- Stack trace (if available) +- Affected files list +- Related functions and classes +- Code context from the index +- Severity-based priority + +### 4. Slack Notification + +Posts back to the thread with: +- Confirmation of ticket creation +- Linear ticket link +- Analysis summary +- Affected components + +## Example Bug Report Flow + +**User posts in #bugs:** +``` +"Getting TypeError in handleUserLogin function when email is null. +Stack trace: + at handleUserLogin (auth.service.ts:45:12) + at AuthController.login (auth.controller.ts:23:5)" +``` + +**Bot response:** +``` +🤖 Bug detected! Analyzing and creating Linear ticket... +Severity: medium +Keywords: TypeError, handleUserLogin, email, null +Affected files: 2 identified + +✅ Linear ticket created: LIN-1234 +Title: Bug: TypeError, handleUserLogin, email +``` + +**Linear ticket contains:** +- Full bug description +- Stack trace +- Links to auth.service.ts:45 and auth.controller.ts:23 +- Related function signatures +- Dependencies that might be affected + +## Advanced Features + +### Custom Severity Rules + +```typescript +const bot = new SlackBugMonitor({ + // ... other config + monitoring: { + severityThresholds: { + critical: ['production', 'data loss', 'security breach'], + high: ['crash', 'payment', 'authentication'], + medium: ['error', 'bug', 'issue'], + low: ['warning', 'typo', 'enhancement'] + } + } +}); +``` + +### Filtering Rules + +```typescript +// Only process messages matching certain criteria +bot.addFilter((message) => { + return message.channel === 'bugs' && + !message.text.includes('[DUPLICATE]'); +}); +``` + +### Custom Ticket Fields + +```typescript +// Add custom fields to Linear tickets +bot.onTicketCreate((ticket, bugReport) => { + ticket.customFields = { + reportedVia: 'Slack', + environment: detectEnvironment(bugReport.text), + component: identifyComponent(bugReport.affectedFiles) + }; + return ticket; +}); +``` + +## Monitoring & Metrics + +The Slack integration automatically sends metrics to Datadog: + +- `slack.bugs.detected` - Number of bugs detected +- `slack.tickets.created` - Linear tickets created +- `slack.processing.duration` - Time to process each bug +- `slack.severity.distribution` - Breakdown by severity + +## Security Considerations + +- Store tokens in environment variables, never in code +- Use Slack signing secret to verify requests +- Limit bot permissions to minimum required +- Implement rate limiting (default: 20 tickets/hour) +- Sanitize user input before creating tickets + +## Troubleshooting + +### Bot not responding +- Check Slack token and permissions +- Verify channel ID is correct +- Ensure PROJECT_INDEX.json exists +- Check network connectivity + +### Tickets not created +- Verify Linear API key +- Check team ID configuration +- Review rate limits +- Check Linear API status + +### Missing code context +- Ensure index is up-to-date +- Verify file paths in stack traces +- Check index includes affected files + +## Future Enhancements + +- AI-powered root cause analysis +- Automatic fix suggestions +- Duplicate detection +- Cross-reference with existing tickets +- Integration with GitHub Issues - Custom workflow automation \ No newline at end of file diff --git a/src/integrations/slack/bot.ts b/src/integrations/slack/bot.ts index 2ee48fe..83ef622 100644 --- a/src/integrations/slack/bot.ts +++ b/src/integrations/slack/bot.ts @@ -1,524 +1,524 @@ -import { WebClient } from '@slack/web-api'; -import { createEventAdapter } from '@slack/events-api'; -import { LinearClient } from '@linear/sdk'; -import { ProjectIndex } from '../../types'; -import { Indexer } from '../../core/indexer'; -import { ConfigLoader } from '../../core/config'; -import * as fs from 'fs'; -import * as path from 'path'; +import { WebClient } from '@slack/web-api'; +import { createEventAdapter } from '@slack/events-api'; +import { LinearClient } from '@linear/sdk'; +import { ProjectIndex } from '../../types'; +import { Indexer } from '../../core/indexer'; +import { ConfigLoader } from '../../core/config'; +import * as fs from 'fs'; +import * as path from 'path'; import logger from '../../utils/logger'; - -export interface SlackBotConfig { - slackToken: string; - slackSigningSecret: string; - linearApiKey: string; - bugChannelId?: string; - port?: number; - projectIndexPath?: string; - enabled?: boolean; -} - -export interface BugReport { - id: string; - text: string; - userId: string; - userName?: string; - timestamp: string; - threadTs?: string; - channel: string; - keywords: string[]; - severity?: 'low' | 'medium' | 'high' | 'critical'; - affectedFiles?: string[]; - stackTrace?: string; -} - -export class SlackBugMonitor { - private slackClient!: WebClient; - private linearClient!: LinearClient; - private eventAdapter: any; - private indexer!: Indexer; - private projectIndex: ProjectIndex | null = null; - private config: SlackBotConfig; - private processingQueue: Map = new Map(); - - constructor(config: SlackBotConfig) { - this.config = config; - - if (!config.enabled) { - logger.info('Slack integration disabled'); - return; - } - - this.slackClient = new WebClient(config.slackToken); - this.linearClient = new LinearClient({ apiKey: config.linearApiKey }); - this.eventAdapter = createEventAdapter(config.slackSigningSecret); - - const configLoader = new ConfigLoader(); - this.indexer = new Indexer(configLoader); - - this.loadProjectIndex(); - this.setupEventListeners(); - } - - private loadProjectIndex(): void { - const indexPath = this.config.projectIndexPath || './PROJECT_INDEX.json'; - - try { - if (fs.existsSync(indexPath)) { - const content = fs.readFileSync(indexPath, 'utf-8'); - this.projectIndex = JSON.parse(content); - logger.info('Project index loaded successfully'); - } else { - logger.warn('Project index not found at:', indexPath); - } - } catch (error) { - logger.error('Failed to load project index:', error); - } - } - - private setupEventListeners(): void { - this.eventAdapter.on('message', async (event: any) => { - if (!this.shouldProcessMessage(event)) { - return; - } - - try { - const bugReport = await this.parseBugReport(event); - if (bugReport) { - await this.processBugReport(bugReport); - } - } catch (error) { - logger.error('Failed to process message:', error); - } - }); - - this.eventAdapter.on('error', (error: Error) => { - logger.error('Slack event adapter error:', error); - }); - } - - private shouldProcessMessage(event: any): boolean { - if (!event.text || event.subtype === 'bot_message') { - return false; - } - - const channelId = this.config.bugChannelId || 'bugs'; - if (event.channel !== channelId && !event.channel.includes(channelId)) { - return false; - } - - const bugKeywords = [ - 'bug', 'error', 'issue', 'broken', 'crash', 'fail', - 'exception', 'stack trace', 'TypeError', 'ReferenceError', - 'SyntaxError', 'undefined', 'null pointer', 'not working' - ]; - - const text = event.text.toLowerCase(); - return bugKeywords.some(keyword => text.includes(keyword)); - } - - private async parseBugReport(event: any): Promise { - const bugReport: BugReport = { - id: event.ts, - text: event.text, - userId: event.user, - timestamp: new Date(parseFloat(event.ts) * 1000).toISOString(), - threadTs: event.thread_ts, - channel: event.channel, - keywords: this.extractKeywords(event.text), - severity: this.determineSeverity(event.text), - stackTrace: this.extractStackTrace(event.text) - }; - - try { - const userInfo = await this.slackClient.users.info({ user: event.user }); - bugReport.userName = userInfo.user?.real_name || userInfo.user?.name; - } catch (error) { - logger.warn('Failed to fetch user info:', error); - } - - bugReport.affectedFiles = await this.identifyAffectedFiles(bugReport); - - return bugReport; - } - - private extractKeywords(text: string): string[] { - const keywords: string[] = []; - - const functionPattern = /(\w+)\(\)/g; - const filePattern = /([a-zA-Z0-9_-]+\.(ts|tsx|js|jsx|py|java|go))/g; - const classPattern = /class\s+(\w+)/g; - const errorPattern = /(Error|Exception|Warning):\s*([^\n]+)/g; - - let match; - while (match = functionPattern.exec(text)) { - keywords.push(match[1]); - } - - while (match = filePattern.exec(text)) { - keywords.push(match[1]); - } - - while (match = classPattern.exec(text)) { - keywords.push(match[1]); - } - - while (match = errorPattern.exec(text)) { - keywords.push(match[2].trim()); - } - - return [...new Set(keywords)]; - } - - private determineSeverity(text: string): 'low' | 'medium' | 'high' | 'critical' { - const lowerText = text.toLowerCase(); - - if (lowerText.includes('critical') || lowerText.includes('urgent') || - lowerText.includes('production down') || lowerText.includes('data loss')) { - return 'critical'; - } - - if (lowerText.includes('high priority') || lowerText.includes('crash') || - lowerText.includes('broken') || lowerText.includes('blocker')) { - return 'high'; - } - - if (lowerText.includes('error') || lowerText.includes('exception') || - lowerText.includes('fail')) { - return 'medium'; - } - - return 'low'; - } - - private extractStackTrace(text: string): string | undefined { - const stackTracePattern = /(at\s+.+\s+\(.+:\d+:\d+\)|^\s+at\s+.+)/gm; - const matches = text.match(stackTracePattern); - - if (matches && matches.length > 0) { - return matches.join('\n'); - } - - const errorLinePattern = /([a-zA-Z0-9_-]+\.(ts|tsx|js|jsx|py|java|go)):(\d+)/g; - const errorMatches = text.match(errorLinePattern); - - if (errorMatches && errorMatches.length > 0) { - return errorMatches.join('\n'); - } - - return undefined; - } - - private async identifyAffectedFiles(bugReport: BugReport): Promise { - const affectedFiles: Set = new Set(); - - if (!this.projectIndex) { - return []; - } - - for (const keyword of bugReport.keywords) { - for (const [filePath, fileIndex] of Object.entries(this.projectIndex.files)) { - if (fileIndex.functions.some(f => f.name === keyword)) { - affectedFiles.add(filePath); - } - - if (fileIndex.classes.some(c => c.name === keyword)) { - affectedFiles.add(filePath); - } - - if (filePath.includes(keyword)) { - affectedFiles.add(filePath); - } - } - } - - if (bugReport.stackTrace) { - const filePattern = /([a-zA-Z0-9_/-]+\.(ts|tsx|js|jsx|py|java|go))/g; - let match; - while (match = filePattern.exec(bugReport.stackTrace)) { - const fileName = match[1]; - for (const filePath of Object.keys(this.projectIndex.files)) { - if (filePath.includes(fileName)) { - affectedFiles.add(filePath); - } - } - } - } - - return Array.from(affectedFiles).slice(0, 10); - } - - private async processBugReport(bugReport: BugReport): Promise { - if (this.processingQueue.has(bugReport.id)) { - return; - } - - this.processingQueue.set(bugReport.id, bugReport); - - try { - await this.notifySlackThread(bugReport); - - const codeContext = await this.gatherCodeContext(bugReport); - - const linearIssue = await this.createLinearTicket(bugReport, codeContext); - - await this.postLinearLinkToSlack(bugReport, linearIssue); - - if (this.shouldCreatePR(bugReport, codeContext)) { - await this.createPullRequest(bugReport, codeContext, linearIssue); - } - } catch (error) { - logger.error('Failed to process bug report:', error); - await this.notifySlackError(bugReport, error); - } finally { - this.processingQueue.delete(bugReport.id); - } - } - - private async notifySlackThread(bugReport: BugReport): Promise { - try { - await this.slackClient.chat.postMessage({ - channel: bugReport.channel, - thread_ts: bugReport.threadTs || bugReport.id, - text: `🤖 Bug detected! Analyzing and creating Linear ticket...`, - blocks: [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: `*Bug Analysis Started*\n` + - `Severity: *${bugReport.severity}*\n` + - `Keywords: ${bugReport.keywords.join(', ')}\n` + - `Affected files: ${bugReport.affectedFiles?.length || 0} identified` - } - } - ] - }); - } catch (error) { - logger.error('Failed to post to Slack thread:', error); - } - } - - private async gatherCodeContext(bugReport: BugReport): Promise { - const context: any = { - functions: [], - classes: [], - relevantCode: [], - dependencies: [] - }; - - if (!this.projectIndex || !bugReport.affectedFiles) { - return context; - } - - for (const filePath of bugReport.affectedFiles) { - const fileIndex = this.projectIndex.files[filePath]; - if (!fileIndex) continue; - - for (const func of fileIndex.functions) { - if (bugReport.keywords.includes(func.name)) { - context.functions.push({ - file: filePath, - ...func - }); - } - } - - for (const cls of fileIndex.classes) { - if (bugReport.keywords.includes(cls.name)) { - context.classes.push({ - file: filePath, - ...cls - }); - } - } - - if (this.projectIndex.dependencyGraph[filePath]) { - context.dependencies.push({ - file: filePath, - deps: this.projectIndex.dependencyGraph[filePath] - }); - } - } - - return context; - } - - private async createLinearTicket(bugReport: BugReport, codeContext: any): Promise { - const description = this.formatLinearDescription(bugReport, codeContext); - - try { - const teams = await this.linearClient.teams(); - const team = teams.nodes[0]; - - if (!team) { - throw new Error('No Linear team found'); - } - - const issuePayload = { - teamId: team.id, - title: `Bug: ${bugReport.keywords.slice(0, 3).join(', ')}`, - description, - priority: this.mapSeverityToPriority(bugReport.severity), - labels: ['bug', 'automated'] - }; - - const issue = await this.linearClient.createIssue(issuePayload); - - return issue; - } catch (error) { - logger.error('Failed to create Linear ticket:', error); - throw error; - } - } - - private formatLinearDescription(bugReport: BugReport, codeContext: any): string { - let description = `## Bug Report\n\n`; - description += `**Reported by:** ${bugReport.userName || bugReport.userId}\n`; - description += `**Date:** ${bugReport.timestamp}\n`; - description += `**Severity:** ${bugReport.severity}\n\n`; - - description += `### Description\n${bugReport.text}\n\n`; - - if (bugReport.stackTrace) { - description += `### Stack Trace\n\`\`\`\n${bugReport.stackTrace}\n\`\`\`\n\n`; - } - - if (bugReport.affectedFiles && bugReport.affectedFiles.length > 0) { - description += `### Affected Files\n`; - for (const file of bugReport.affectedFiles) { - description += `- ${file}\n`; - } - description += '\n'; - } - - if (codeContext.functions.length > 0) { - description += `### Related Functions\n`; - for (const func of codeContext.functions) { - description += `- \`${func.name}\` in ${func.file}:${func.startLine}\n`; - } - description += '\n'; - } - - if (codeContext.classes.length > 0) { - description += `### Related Classes\n`; - for (const cls of codeContext.classes) { - description += `- \`${cls.name}\` in ${cls.file}:${cls.startLine}\n`; - } - description += '\n'; - } - - description += `\n---\n*This ticket was automatically created by the Code Indexer Slack Bot*`; - - return description; - } - - private mapSeverityToPriority(severity?: string): number { - switch (severity) { - case 'critical': return 1; - case 'high': return 2; - case 'medium': return 3; - case 'low': return 4; - default: return 3; - } - } - - private async postLinearLinkToSlack(bugReport: BugReport, linearIssue: any): Promise { - try { - const issueUrl = `https://linear.app/team/${linearIssue.identifier}`; - - await this.slackClient.chat.postMessage({ - channel: bugReport.channel, - thread_ts: bugReport.threadTs || bugReport.id, - text: `✅ Linear ticket created: ${issueUrl}`, - blocks: [ - { - type: 'section', - text: { - type: 'mrkdwn', - text: `*Linear Ticket Created*\n` + - `Issue: <${issueUrl}|${linearIssue.identifier}>\n` + - `Title: ${linearIssue.title}` - } - }, - { - type: 'actions', - elements: [ - { - type: 'button', - text: { - type: 'plain_text', - text: 'View in Linear' - }, - url: issueUrl - } - ] - } - ] - }); - } catch (error) { - logger.error('Failed to post Linear link to Slack:', error); - } - } - - private shouldCreatePR(bugReport: BugReport, codeContext: any): boolean { - if (bugReport.severity === 'critical') { - return false; - } - - const hasSimpleFix = codeContext.functions.length === 1 && - codeContext.classes.length === 0; - - const isTypographicError = bugReport.keywords.some(k => - k.includes('typo') || k.includes('spelling')); - - return hasSimpleFix || isTypographicError; - } - - private async createPullRequest(bugReport: BugReport, codeContext: any, linearIssue: any): Promise { - logger.info('PR creation for bug fixes not yet implemented'); - } - - private async notifySlackError(bugReport: BugReport, error: any): Promise { - try { - await this.slackClient.chat.postMessage({ - channel: bugReport.channel, - thread_ts: bugReport.threadTs || bugReport.id, - text: `❌ Failed to process bug report: ${error.message}` - }); - } catch (slackError) { - logger.error('Failed to notify Slack of error:', slackError); - } - } - - public async start(): Promise { - if (!this.config.enabled) { - return; - } - - const port = this.config.port || 3000; - - try { - await this.eventAdapter.start(port); - logger.info(`Slack bot listening on port ${port}`); - - const channels = await this.slackClient.conversations.list(); - const bugChannel = channels.channels?.find((c: any) => - c.name === 'bugs' || c.id === this.config.bugChannelId - ); - - if (bugChannel) { - logger.info(`Monitoring bug channel: #${bugChannel.name} (${bugChannel.id})`); - } - } catch (error) { - logger.error('Failed to start Slack bot:', error); - throw error; - } - } - - public async stop(): Promise { - if (this.eventAdapter) { - await this.eventAdapter.stop(); - } - } + +export interface SlackBotConfig { + slackToken: string; + slackSigningSecret: string; + linearApiKey: string; + bugChannelId?: string; + port?: number; + projectIndexPath?: string; + enabled?: boolean; +} + +export interface BugReport { + id: string; + text: string; + userId: string; + userName?: string; + timestamp: string; + threadTs?: string; + channel: string; + keywords: string[]; + severity?: 'low' | 'medium' | 'high' | 'critical'; + affectedFiles?: string[]; + stackTrace?: string; +} + +export class SlackBugMonitor { + private slackClient!: WebClient; + private linearClient!: LinearClient; + private eventAdapter: any; + private indexer!: Indexer; + private projectIndex: ProjectIndex | null = null; + private config: SlackBotConfig; + private processingQueue: Map = new Map(); + + constructor(config: SlackBotConfig) { + this.config = config; + + if (!config.enabled) { + logger.info('Slack integration disabled'); + return; + } + + this.slackClient = new WebClient(config.slackToken); + this.linearClient = new LinearClient({ apiKey: config.linearApiKey }); + this.eventAdapter = createEventAdapter(config.slackSigningSecret); + + const configLoader = new ConfigLoader(); + this.indexer = new Indexer(configLoader); + + this.loadProjectIndex(); + this.setupEventListeners(); + } + + private loadProjectIndex(): void { + const indexPath = this.config.projectIndexPath || './PROJECT_INDEX.json'; + + try { + if (fs.existsSync(indexPath)) { + const content = fs.readFileSync(indexPath, 'utf-8'); + this.projectIndex = JSON.parse(content); + logger.info('Project index loaded successfully'); + } else { + logger.warn('Project index not found at:', indexPath); + } + } catch (error) { + logger.error('Failed to load project index:', error); + } + } + + private setupEventListeners(): void { + this.eventAdapter.on('message', async (event: any) => { + if (!this.shouldProcessMessage(event)) { + return; + } + + try { + const bugReport = await this.parseBugReport(event); + if (bugReport) { + await this.processBugReport(bugReport); + } + } catch (error) { + logger.error('Failed to process message:', error); + } + }); + + this.eventAdapter.on('error', (error: Error) => { + logger.error('Slack event adapter error:', error); + }); + } + + private shouldProcessMessage(event: any): boolean { + if (!event.text || event.subtype === 'bot_message') { + return false; + } + + const channelId = this.config.bugChannelId || 'bugs'; + if (event.channel !== channelId && !event.channel.includes(channelId)) { + return false; + } + + const bugKeywords = [ + 'bug', 'error', 'issue', 'broken', 'crash', 'fail', + 'exception', 'stack trace', 'TypeError', 'ReferenceError', + 'SyntaxError', 'undefined', 'null pointer', 'not working' + ]; + + const text = event.text.toLowerCase(); + return bugKeywords.some(keyword => text.includes(keyword)); + } + + private async parseBugReport(event: any): Promise { + const bugReport: BugReport = { + id: event.ts, + text: event.text, + userId: event.user, + timestamp: new Date(parseFloat(event.ts) * 1000).toISOString(), + threadTs: event.thread_ts, + channel: event.channel, + keywords: this.extractKeywords(event.text), + severity: this.determineSeverity(event.text), + stackTrace: this.extractStackTrace(event.text) + }; + + try { + const userInfo = await this.slackClient.users.info({ user: event.user }); + bugReport.userName = userInfo.user?.real_name || userInfo.user?.name; + } catch (error) { + logger.warn('Failed to fetch user info:', error); + } + + bugReport.affectedFiles = await this.identifyAffectedFiles(bugReport); + + return bugReport; + } + + private extractKeywords(text: string): string[] { + const keywords: string[] = []; + + const functionPattern = /(\w+)\(\)/g; + const filePattern = /([a-zA-Z0-9_-]+\.(ts|tsx|js|jsx|py|java|go))/g; + const classPattern = /class\s+(\w+)/g; + const errorPattern = /(Error|Exception|Warning):\s*([^\n]+)/g; + + let match; + while (match = functionPattern.exec(text)) { + keywords.push(match[1]); + } + + while (match = filePattern.exec(text)) { + keywords.push(match[1]); + } + + while (match = classPattern.exec(text)) { + keywords.push(match[1]); + } + + while (match = errorPattern.exec(text)) { + keywords.push(match[2].trim()); + } + + return [...new Set(keywords)]; + } + + private determineSeverity(text: string): 'low' | 'medium' | 'high' | 'critical' { + const lowerText = text.toLowerCase(); + + if (lowerText.includes('critical') || lowerText.includes('urgent') || + lowerText.includes('production down') || lowerText.includes('data loss')) { + return 'critical'; + } + + if (lowerText.includes('high priority') || lowerText.includes('crash') || + lowerText.includes('broken') || lowerText.includes('blocker')) { + return 'high'; + } + + if (lowerText.includes('error') || lowerText.includes('exception') || + lowerText.includes('fail')) { + return 'medium'; + } + + return 'low'; + } + + private extractStackTrace(text: string): string | undefined { + const stackTracePattern = /(at\s+.+\s+\(.+:\d+:\d+\)|^\s+at\s+.+)/gm; + const matches = text.match(stackTracePattern); + + if (matches && matches.length > 0) { + return matches.join('\n'); + } + + const errorLinePattern = /([a-zA-Z0-9_-]+\.(ts|tsx|js|jsx|py|java|go)):(\d+)/g; + const errorMatches = text.match(errorLinePattern); + + if (errorMatches && errorMatches.length > 0) { + return errorMatches.join('\n'); + } + + return undefined; + } + + private async identifyAffectedFiles(bugReport: BugReport): Promise { + const affectedFiles: Set = new Set(); + + if (!this.projectIndex) { + return []; + } + + for (const keyword of bugReport.keywords) { + for (const [filePath, fileIndex] of Object.entries(this.projectIndex.files)) { + if (fileIndex.functions.some(f => f.name === keyword)) { + affectedFiles.add(filePath); + } + + if (fileIndex.classes.some(c => c.name === keyword)) { + affectedFiles.add(filePath); + } + + if (filePath.includes(keyword)) { + affectedFiles.add(filePath); + } + } + } + + if (bugReport.stackTrace) { + const filePattern = /([a-zA-Z0-9_/-]+\.(ts|tsx|js|jsx|py|java|go))/g; + let match; + while (match = filePattern.exec(bugReport.stackTrace)) { + const fileName = match[1]; + for (const filePath of Object.keys(this.projectIndex.files)) { + if (filePath.includes(fileName)) { + affectedFiles.add(filePath); + } + } + } + } + + return Array.from(affectedFiles).slice(0, 10); + } + + private async processBugReport(bugReport: BugReport): Promise { + if (this.processingQueue.has(bugReport.id)) { + return; + } + + this.processingQueue.set(bugReport.id, bugReport); + + try { + await this.notifySlackThread(bugReport); + + const codeContext = await this.gatherCodeContext(bugReport); + + const linearIssue = await this.createLinearTicket(bugReport, codeContext); + + await this.postLinearLinkToSlack(bugReport, linearIssue); + + if (this.shouldCreatePR(bugReport, codeContext)) { + await this.createPullRequest(bugReport, codeContext, linearIssue); + } + } catch (error) { + logger.error('Failed to process bug report:', error); + await this.notifySlackError(bugReport, error); + } finally { + this.processingQueue.delete(bugReport.id); + } + } + + private async notifySlackThread(bugReport: BugReport): Promise { + try { + await this.slackClient.chat.postMessage({ + channel: bugReport.channel, + thread_ts: bugReport.threadTs || bugReport.id, + text: `🤖 Bug detected! Analyzing and creating Linear ticket...`, + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: `*Bug Analysis Started*\n` + + `Severity: *${bugReport.severity}*\n` + + `Keywords: ${bugReport.keywords.join(', ')}\n` + + `Affected files: ${bugReport.affectedFiles?.length || 0} identified` + } + } + ] + }); + } catch (error) { + logger.error('Failed to post to Slack thread:', error); + } + } + + private async gatherCodeContext(bugReport: BugReport): Promise { + const context: any = { + functions: [], + classes: [], + relevantCode: [], + dependencies: [] + }; + + if (!this.projectIndex || !bugReport.affectedFiles) { + return context; + } + + for (const filePath of bugReport.affectedFiles) { + const fileIndex = this.projectIndex.files[filePath]; + if (!fileIndex) continue; + + for (const func of fileIndex.functions) { + if (bugReport.keywords.includes(func.name)) { + context.functions.push({ + file: filePath, + ...func + }); + } + } + + for (const cls of fileIndex.classes) { + if (bugReport.keywords.includes(cls.name)) { + context.classes.push({ + file: filePath, + ...cls + }); + } + } + + if (this.projectIndex.dependencyGraph[filePath]) { + context.dependencies.push({ + file: filePath, + deps: this.projectIndex.dependencyGraph[filePath] + }); + } + } + + return context; + } + + private async createLinearTicket(bugReport: BugReport, codeContext: any): Promise { + const description = this.formatLinearDescription(bugReport, codeContext); + + try { + const teams = await this.linearClient.teams(); + const team = teams.nodes[0]; + + if (!team) { + throw new Error('No Linear team found'); + } + + const issuePayload = { + teamId: team.id, + title: `Bug: ${bugReport.keywords.slice(0, 3).join(', ')}`, + description, + priority: this.mapSeverityToPriority(bugReport.severity), + labels: ['bug', 'automated'] + }; + + const issue = await this.linearClient.createIssue(issuePayload); + + return issue; + } catch (error) { + logger.error('Failed to create Linear ticket:', error); + throw error; + } + } + + private formatLinearDescription(bugReport: BugReport, codeContext: any): string { + let description = `## Bug Report\n\n`; + description += `**Reported by:** ${bugReport.userName || bugReport.userId}\n`; + description += `**Date:** ${bugReport.timestamp}\n`; + description += `**Severity:** ${bugReport.severity}\n\n`; + + description += `### Description\n${bugReport.text}\n\n`; + + if (bugReport.stackTrace) { + description += `### Stack Trace\n\`\`\`\n${bugReport.stackTrace}\n\`\`\`\n\n`; + } + + if (bugReport.affectedFiles && bugReport.affectedFiles.length > 0) { + description += `### Affected Files\n`; + for (const file of bugReport.affectedFiles) { + description += `- ${file}\n`; + } + description += '\n'; + } + + if (codeContext.functions.length > 0) { + description += `### Related Functions\n`; + for (const func of codeContext.functions) { + description += `- \`${func.name}\` in ${func.file}:${func.startLine}\n`; + } + description += '\n'; + } + + if (codeContext.classes.length > 0) { + description += `### Related Classes\n`; + for (const cls of codeContext.classes) { + description += `- \`${cls.name}\` in ${cls.file}:${cls.startLine}\n`; + } + description += '\n'; + } + + description += `\n---\n*This ticket was automatically created by the Code Indexer Slack Bot*`; + + return description; + } + + private mapSeverityToPriority(severity?: string): number { + switch (severity) { + case 'critical': return 1; + case 'high': return 2; + case 'medium': return 3; + case 'low': return 4; + default: return 3; + } + } + + private async postLinearLinkToSlack(bugReport: BugReport, linearIssue: any): Promise { + try { + const issueUrl = `https://linear.app/team/${linearIssue.identifier}`; + + await this.slackClient.chat.postMessage({ + channel: bugReport.channel, + thread_ts: bugReport.threadTs || bugReport.id, + text: `✅ Linear ticket created: ${issueUrl}`, + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: `*Linear Ticket Created*\n` + + `Issue: <${issueUrl}|${linearIssue.identifier}>\n` + + `Title: ${linearIssue.title}` + } + }, + { + type: 'actions', + elements: [ + { + type: 'button', + text: { + type: 'plain_text', + text: 'View in Linear' + }, + url: issueUrl + } + ] + } + ] + }); + } catch (error) { + logger.error('Failed to post Linear link to Slack:', error); + } + } + + private shouldCreatePR(bugReport: BugReport, codeContext: any): boolean { + if (bugReport.severity === 'critical') { + return false; + } + + const hasSimpleFix = codeContext.functions.length === 1 && + codeContext.classes.length === 0; + + const isTypographicError = bugReport.keywords.some(k => + k.includes('typo') || k.includes('spelling')); + + return hasSimpleFix || isTypographicError; + } + + private async createPullRequest(bugReport: BugReport, codeContext: any, linearIssue: any): Promise { + logger.info('PR creation for bug fixes not yet implemented'); + } + + private async notifySlackError(bugReport: BugReport, error: any): Promise { + try { + await this.slackClient.chat.postMessage({ + channel: bugReport.channel, + thread_ts: bugReport.threadTs || bugReport.id, + text: `❌ Failed to process bug report: ${error.message}` + }); + } catch (slackError) { + logger.error('Failed to notify Slack of error:', slackError); + } + } + + public async start(): Promise { + if (!this.config.enabled) { + return; + } + + const port = this.config.port || 3000; + + try { + await this.eventAdapter.start(port); + logger.info(`Slack bot listening on port ${port}`); + + const channels = await this.slackClient.conversations.list(); + const bugChannel = channels.channels?.find((c: any) => + c.name === 'bugs' || c.id === this.config.bugChannelId + ); + + if (bugChannel) { + logger.info(`Monitoring bug channel: #${bugChannel.name} (${bugChannel.id})`); + } + } catch (error) { + logger.error('Failed to start Slack bot:', error); + throw error; + } + } + + public async stop(): Promise { + if (this.eventAdapter) { + await this.eventAdapter.stop(); + } + } } \ No newline at end of file diff --git a/src/integrations/slack/config.ts b/src/integrations/slack/config.ts index 865cf0e..044eb8e 100644 --- a/src/integrations/slack/config.ts +++ b/src/integrations/slack/config.ts @@ -1,144 +1,144 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as yaml from 'js-yaml'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as yaml from 'js-yaml'; import logger from '../../utils/logger'; - -export interface SlackIntegrationConfig { - slack: { - enabled: boolean; - token?: string; - signingSecret?: string; - bugChannel?: string; - port?: number; - }; - linear: { - apiKey?: string; - teamId?: string; - defaultPriority?: number; - autoCreatePR?: boolean; - }; - monitoring: { - severityThresholds?: { - critical: string[]; - high: string[]; - medium: string[]; - low: string[]; - }; - autoTriage?: boolean; - maxTicketsPerHour?: number; - }; -} - -export class SlackConfigLoader { - private config: SlackIntegrationConfig; - - constructor(configPath?: string) { - this.config = this.loadConfig(configPath); - } - - private loadConfig(configPath?: string): SlackIntegrationConfig { - const defaultConfig: SlackIntegrationConfig = { - slack: { - enabled: false, - bugChannel: 'bugs', - port: 3000 - }, - linear: { - defaultPriority: 3, - autoCreatePR: false - }, - monitoring: { - severityThresholds: { - critical: ['production down', 'data loss', 'security breach'], - high: ['crash', 'broken', 'urgent'], - medium: ['error', 'exception', 'fail'], - low: ['warning', 'issue', 'bug'] - }, - autoTriage: true, - maxTicketsPerHour: 20 - } - }; - - // Load from environment variables - if (process.env.SLACK_BOT_TOKEN) { - defaultConfig.slack.token = process.env.SLACK_BOT_TOKEN; - defaultConfig.slack.enabled = true; - } - - if (process.env.SLACK_SIGNING_SECRET) { - defaultConfig.slack.signingSecret = process.env.SLACK_SIGNING_SECRET; - } - - if (process.env.LINEAR_API_KEY) { - defaultConfig.linear.apiKey = process.env.LINEAR_API_KEY; - } - - // Load from config file if provided - if (configPath && fs.existsSync(configPath)) { - try { - const content = fs.readFileSync(configPath, 'utf-8'); - const fileConfig = configPath.endsWith('.yml') || configPath.endsWith('.yaml') - ? yaml.load(content) as any - : JSON.parse(content); - - if (fileConfig.integrations?.slack) { - Object.assign(defaultConfig.slack, fileConfig.integrations.slack); - } - - if (fileConfig.integrations?.linear) { - Object.assign(defaultConfig.linear, fileConfig.integrations.linear); - } - - if (fileConfig.monitoring) { - Object.assign(defaultConfig.monitoring, fileConfig.monitoring); - } - } catch (error) { - logger.warn('Failed to load Slack config from file:', error); - } - } - - return defaultConfig; - } - - public getSlackConfig() { - return this.config.slack; - } - - public getLinearConfig() { - return this.config.linear; - } - - public getMonitoringConfig() { - return this.config.monitoring; - } - - public isEnabled(): boolean { - return this.config.slack.enabled && - !!this.config.slack.token && - !!this.config.slack.signingSecret && - !!this.config.linear.apiKey; - } - - public validateConfig(): { valid: boolean; errors: string[] } { - const errors: string[] = []; - - if (this.config.slack.enabled) { - if (!this.config.slack.token) { - errors.push('Slack bot token is required when Slack integration is enabled'); - } - - if (!this.config.slack.signingSecret) { - errors.push('Slack signing secret is required when Slack integration is enabled'); - } - - if (!this.config.linear.apiKey) { - errors.push('Linear API key is required for ticket creation'); - } - } - - return { - valid: errors.length === 0, - errors - }; - } + +export interface SlackIntegrationConfig { + slack: { + enabled: boolean; + token?: string; + signingSecret?: string; + bugChannel?: string; + port?: number; + }; + linear: { + apiKey?: string; + teamId?: string; + defaultPriority?: number; + autoCreatePR?: boolean; + }; + monitoring: { + severityThresholds?: { + critical: string[]; + high: string[]; + medium: string[]; + low: string[]; + }; + autoTriage?: boolean; + maxTicketsPerHour?: number; + }; +} + +export class SlackConfigLoader { + private config: SlackIntegrationConfig; + + constructor(configPath?: string) { + this.config = this.loadConfig(configPath); + } + + private loadConfig(configPath?: string): SlackIntegrationConfig { + const defaultConfig: SlackIntegrationConfig = { + slack: { + enabled: false, + bugChannel: 'bugs', + port: 3000 + }, + linear: { + defaultPriority: 3, + autoCreatePR: false + }, + monitoring: { + severityThresholds: { + critical: ['production down', 'data loss', 'security breach'], + high: ['crash', 'broken', 'urgent'], + medium: ['error', 'exception', 'fail'], + low: ['warning', 'issue', 'bug'] + }, + autoTriage: true, + maxTicketsPerHour: 20 + } + }; + + // Load from environment variables + if (process.env.SLACK_BOT_TOKEN) { + defaultConfig.slack.token = process.env.SLACK_BOT_TOKEN; + defaultConfig.slack.enabled = true; + } + + if (process.env.SLACK_SIGNING_SECRET) { + defaultConfig.slack.signingSecret = process.env.SLACK_SIGNING_SECRET; + } + + if (process.env.LINEAR_API_KEY) { + defaultConfig.linear.apiKey = process.env.LINEAR_API_KEY; + } + + // Load from config file if provided + if (configPath && fs.existsSync(configPath)) { + try { + const content = fs.readFileSync(configPath, 'utf-8'); + const fileConfig = configPath.endsWith('.yml') || configPath.endsWith('.yaml') + ? yaml.load(content) as any + : JSON.parse(content); + + if (fileConfig.integrations?.slack) { + Object.assign(defaultConfig.slack, fileConfig.integrations.slack); + } + + if (fileConfig.integrations?.linear) { + Object.assign(defaultConfig.linear, fileConfig.integrations.linear); + } + + if (fileConfig.monitoring) { + Object.assign(defaultConfig.monitoring, fileConfig.monitoring); + } + } catch (error) { + logger.warn('Failed to load Slack config from file:', error); + } + } + + return defaultConfig; + } + + public getSlackConfig() { + return this.config.slack; + } + + public getLinearConfig() { + return this.config.linear; + } + + public getMonitoringConfig() { + return this.config.monitoring; + } + + public isEnabled(): boolean { + return this.config.slack.enabled && + !!this.config.slack.token && + !!this.config.slack.signingSecret && + !!this.config.linear.apiKey; + } + + public validateConfig(): { valid: boolean; errors: string[] } { + const errors: string[] = []; + + if (this.config.slack.enabled) { + if (!this.config.slack.token) { + errors.push('Slack bot token is required when Slack integration is enabled'); + } + + if (!this.config.slack.signingSecret) { + errors.push('Slack signing secret is required when Slack integration is enabled'); + } + + if (!this.config.linear.apiKey) { + errors.push('Linear API key is required for ticket creation'); + } + } + + return { + valid: errors.length === 0, + errors + }; + } } \ No newline at end of file diff --git a/src/integrations/slack/index.ts b/src/integrations/slack/index.ts index 2d00bb1..12e0e56 100644 --- a/src/integrations/slack/index.ts +++ b/src/integrations/slack/index.ts @@ -1,4 +1,4 @@ -export { SlackBugMonitor } from './bot'; -export { SlackConfigLoader } from './config'; -export type { SlackBotConfig, BugReport } from './bot'; +export { SlackBugMonitor } from './bot'; +export { SlackConfigLoader } from './config'; +export type { SlackBotConfig, BugReport } from './bot'; export type { SlackIntegrationConfig } from './config'; \ No newline at end of file diff --git a/src/parsers/astro.ts b/src/parsers/astro.ts index 11ef99e..1d786d4 100644 --- a/src/parsers/astro.ts +++ b/src/parsers/astro.ts @@ -1,318 +1,318 @@ -import * as parser from '@babel/parser'; -import traverse from '@babel/traverse'; -import { Parser } from '../types'; -import { ParserResult} from '../types'; - -export class AstroParser implements Parser { - language = 'Astro'; - extensions = ['.astro']; - - parse(content: string, filePath: string): ParserResult { - const result: ParserResult = { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - errors: [] - }; - - try { - // Extract frontmatter (between --- markers) - const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); - - if (frontmatterMatch) { - const frontmatterCode = frontmatterMatch[1]; - - // Parse frontmatter as TypeScript - this.parseFrontmatter(frontmatterCode, result); - } - - // Extract script tags - const scriptMatches = content.matchAll(/]*>([\s\S]*?)<\/script>/g); - for (const match of scriptMatches) { - const scriptCode = match[1]; - this.parseScript(scriptCode, result); - } - - // Extract component imports from template - const componentImportMatches = content.matchAll(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g); - for (const match of componentImportMatches) { - const componentName = match[1]; - const source = match[2]; - - if (!result.imports.find((imp: any) => imp.source === source)) { - result.imports.push({ - source, - specifiers: [{ name: 'default', alias: componentName }], - type: 'static' - }); - - if (source.startsWith('.')) { - result.dependencies.push(source); - } - } - } - - // Extract Astro component usage - const componentUsageMatches = content.matchAll(/<(\w+)[\s/>]/g); - for (const match of componentUsageMatches) { - const componentName = match[1]; - - // Check if it's a custom component (starts with uppercase) - if (componentName[0] === componentName[0].toUpperCase()) { - // Track component usage as a dependency - const existingImport = result.imports.find((imp: any) => - imp.specifiers.some((spec: any) => spec.alias === componentName) - ); - - if (existingImport && !result.constants.find((c: any) => c.name === componentName)) { - result.constants.push({ - name: componentName, - type: 'AstroComponent', - value: 'component' - }); - } - } - } - - // Extract Astro.props usage - if (content.includes('Astro.props')) { - result.constants.push({ - name: 'Astro.props', - type: 'AstroProps', - value: 'props' - }); - } - - // Extract Astro.slots usage - if (content.includes('Astro.slots')) { - result.constants.push({ - name: 'Astro.slots', - type: 'AstroSlots', - value: 'slots' - }); - } - - } catch (error: any) { - result.errors.push({ - message: error.message || 'Failed to parse Astro file', - line: error.loc?.line || 0 - }); - } - - return result; - } - - private parseFrontmatter(code: string, result: ParserResult): void { - try { - const ast = parser.parse(code, { - sourceType: 'module', - plugins: ['typescript', 'jsx'], - errorRecovery: true - }); - - const self = this; - traverse(ast, { - ImportDeclaration(path) { - const source = path.node.source.value; - const specifiers = path.node.specifiers.map(spec => { - if (spec.type === 'ImportDefaultSpecifier') { - return { name: 'default', alias: spec.local.name }; - } else if (spec.type === 'ImportSpecifier') { - return { - name: spec.imported.type === 'Identifier' ? spec.imported.name : 'unknown', - alias: spec.local.name - }; - } else if (spec.type === 'ImportNamespaceSpecifier') { - return { name: '*', alias: spec.local.name }; - } - return { name: 'unknown', alias: 'unknown' }; - }); - - result.imports.push({ - source, - specifiers, - type: 'static' - }); - - if (source.startsWith('.')) { - result.dependencies.push(source); - } - }, - - ExportNamedDeclaration(path) { - if (path.node.declaration) { - if (path.node.declaration.type === 'VariableDeclaration') { - for (const decl of path.node.declaration.declarations) { - if (decl.id.type === 'Identifier') { - result.exports.push({ - name: decl.id.name, - type: 'variable' - }); - } - } - } else if (path.node.declaration.type === 'FunctionDeclaration' && path.node.declaration.id) { - result.exports.push({ - name: path.node.declaration.id.name, - type: 'function' - }); - } - } else if (path.node.specifiers) { - for (const spec of path.node.specifiers) { - if (spec.type === 'ExportSpecifier' && spec.exported.type === 'Identifier') { - result.exports.push({ - name: spec.exported.name, - type: 'named' - }); - } - } - } - }, - - VariableDeclaration(path) { - if (path.node.kind === 'const') { - for (const decl of path.node.declarations) { - if (decl.id.type === 'Identifier') { - // Check if it's an Astro.props destructuring - if (decl.init && decl.init.type === 'MemberExpression') { - const obj = decl.init.object; - const prop = decl.init.property; - - if (obj.type === 'Identifier' && obj.name === 'Astro' && - prop.type === 'Identifier' && prop.name === 'props') { - result.constants.push({ - name: decl.id.name, - type: 'AstroProp', - value: 'prop' - }); - } - } else { - result.constants.push({ - name: decl.id.name, - type: 'any', - value: self.getValueString(decl.init) - }); - } - } - } - } - }, - - FunctionDeclaration(path) { - if (path.node.id) { - const params = path.node.params.map(param => { - if (param.type === 'Identifier') { - return { name: param.name, type: 'any' }; - } - return { name: 'unknown', type: 'any' }; - }); - - result.functions.push({ - name: path.node.id.name, - parameters: params, - returnType: 'any', - async: path.node.async || false, - generator: path.node.generator || false, - startLine: path.node.loc?.start.line || 0, - endLine: path.node.loc?.end.line || 0 - }); - } - } - }); - } catch (error: any) { - result.errors.push({ - message: `Frontmatter parse error: ${error.message}`, - line: error.loc?.line || 0 - }); - } - } - - private parseScript(code: string, result: ParserResult): void { - try { - const ast = parser.parse(code, { - sourceType: 'module', - plugins: ['typescript', 'jsx'], - errorRecovery: true - }); - - const self = this; - traverse(ast, { - FunctionDeclaration(path) { - if (path.node.id) { - const params = path.node.params.map(param => { - if (param.type === 'Identifier') { - return { name: param.name, type: 'any' }; - } - return { name: 'unknown', type: 'any' }; - }); - - result.functions.push({ - name: path.node.id.name, - parameters: params, - returnType: 'any', - async: path.node.async || false, - generator: path.node.generator || false, - startLine: path.node.loc?.start.line || 0, - endLine: path.node.loc?.end.line || 0, - isClientSide: true // Script tag functions run on client - }); - } - }, - - VariableDeclaration(path) { - for (const decl of path.node.declarations) { - if (decl.id.type === 'Identifier' && decl.init) { - // Check if it's a function expression - if (decl.init.type === 'ArrowFunctionExpression' || - decl.init.type === 'FunctionExpression') { - const params = decl.init.params.map((param: any) => { - if (param.type === 'Identifier') { - return { name: param.name, type: 'any' }; - } - return { name: 'unknown', type: 'any' }; - }); - - result.functions.push({ - name: decl.id.name, - parameters: params, - returnType: 'any', - async: decl.init.async || false, - generator: decl.init.generator || false, - startLine: decl.init.loc?.start.line || 0, - endLine: decl.init.loc?.end.line || 0, - isClientSide: true - }); - } else { - result.constants.push({ - name: decl.id.name, - type: 'any', - value: self.getValueString(decl.init) - }); - } - } - } - } - }); - } catch (error: any) { - result.errors.push({ - message: `Script parse error: ${error.message}`, - line: error.loc?.line || 0 - }); - } - } - - private getValueString(node: any): string | undefined { - if (!node) return undefined; - if (node.type === 'StringLiteral') return node.value; - if (node.type === 'NumericLiteral') return String(node.value); - if (node.type === 'BooleanLiteral') return String(node.value); - if (node.type === 'NullLiteral') return 'null'; - if (node.type === 'Identifier') return node.name; - if (node.type === 'TemplateLiteral') return 'template'; - if (node.type === 'ObjectExpression') return 'object'; - if (node.type === 'ArrayExpression') return 'array'; - return undefined; - } +import * as parser from '@babel/parser'; +import traverse from '@babel/traverse'; +import { Parser } from '../types'; +import { ParserResult} from '../types'; + +export class AstroParser implements Parser { + language = 'Astro'; + extensions = ['.astro']; + + parse(content: string, filePath: string): ParserResult { + const result: ParserResult = { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + errors: [] + }; + + try { + // Extract frontmatter (between --- markers) + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + + if (frontmatterMatch) { + const frontmatterCode = frontmatterMatch[1]; + + // Parse frontmatter as TypeScript + this.parseFrontmatter(frontmatterCode, result); + } + + // Extract script tags + const scriptMatches = content.matchAll(/]*>([\s\S]*?)<\/script>/g); + for (const match of scriptMatches) { + const scriptCode = match[1]; + this.parseScript(scriptCode, result); + } + + // Extract component imports from template + const componentImportMatches = content.matchAll(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g); + for (const match of componentImportMatches) { + const componentName = match[1]; + const source = match[2]; + + if (!result.imports.find((imp: any) => imp.source === source)) { + result.imports.push({ + source, + specifiers: [{ name: 'default', alias: componentName }], + type: 'static' + }); + + if (source.startsWith('.')) { + result.dependencies.push(source); + } + } + } + + // Extract Astro component usage + const componentUsageMatches = content.matchAll(/<(\w+)[\s/>]/g); + for (const match of componentUsageMatches) { + const componentName = match[1]; + + // Check if it's a custom component (starts with uppercase) + if (componentName[0] === componentName[0].toUpperCase()) { + // Track component usage as a dependency + const existingImport = result.imports.find((imp: any) => + imp.specifiers.some((spec: any) => spec.alias === componentName) + ); + + if (existingImport && !result.constants.find((c: any) => c.name === componentName)) { + result.constants.push({ + name: componentName, + type: 'AstroComponent', + value: 'component' + }); + } + } + } + + // Extract Astro.props usage + if (content.includes('Astro.props')) { + result.constants.push({ + name: 'Astro.props', + type: 'AstroProps', + value: 'props' + }); + } + + // Extract Astro.slots usage + if (content.includes('Astro.slots')) { + result.constants.push({ + name: 'Astro.slots', + type: 'AstroSlots', + value: 'slots' + }); + } + + } catch (error: any) { + result.errors.push({ + message: error.message || 'Failed to parse Astro file', + line: error.loc?.line || 0 + }); + } + + return result; + } + + private parseFrontmatter(code: string, result: ParserResult): void { + try { + const ast = parser.parse(code, { + sourceType: 'module', + plugins: ['typescript', 'jsx'], + errorRecovery: true + }); + + const self = this; + traverse(ast, { + ImportDeclaration(path) { + const source = path.node.source.value; + const specifiers = path.node.specifiers.map(spec => { + if (spec.type === 'ImportDefaultSpecifier') { + return { name: 'default', alias: spec.local.name }; + } else if (spec.type === 'ImportSpecifier') { + return { + name: spec.imported.type === 'Identifier' ? spec.imported.name : 'unknown', + alias: spec.local.name + }; + } else if (spec.type === 'ImportNamespaceSpecifier') { + return { name: '*', alias: spec.local.name }; + } + return { name: 'unknown', alias: 'unknown' }; + }); + + result.imports.push({ + source, + specifiers, + type: 'static' + }); + + if (source.startsWith('.')) { + result.dependencies.push(source); + } + }, + + ExportNamedDeclaration(path) { + if (path.node.declaration) { + if (path.node.declaration.type === 'VariableDeclaration') { + for (const decl of path.node.declaration.declarations) { + if (decl.id.type === 'Identifier') { + result.exports.push({ + name: decl.id.name, + type: 'variable' + }); + } + } + } else if (path.node.declaration.type === 'FunctionDeclaration' && path.node.declaration.id) { + result.exports.push({ + name: path.node.declaration.id.name, + type: 'function' + }); + } + } else if (path.node.specifiers) { + for (const spec of path.node.specifiers) { + if (spec.type === 'ExportSpecifier' && spec.exported.type === 'Identifier') { + result.exports.push({ + name: spec.exported.name, + type: 'named' + }); + } + } + } + }, + + VariableDeclaration(path) { + if (path.node.kind === 'const') { + for (const decl of path.node.declarations) { + if (decl.id.type === 'Identifier') { + // Check if it's an Astro.props destructuring + if (decl.init && decl.init.type === 'MemberExpression') { + const obj = decl.init.object; + const prop = decl.init.property; + + if (obj.type === 'Identifier' && obj.name === 'Astro' && + prop.type === 'Identifier' && prop.name === 'props') { + result.constants.push({ + name: decl.id.name, + type: 'AstroProp', + value: 'prop' + }); + } + } else { + result.constants.push({ + name: decl.id.name, + type: 'any', + value: self.getValueString(decl.init) + }); + } + } + } + } + }, + + FunctionDeclaration(path) { + if (path.node.id) { + const params = path.node.params.map(param => { + if (param.type === 'Identifier') { + return { name: param.name, type: 'any' }; + } + return { name: 'unknown', type: 'any' }; + }); + + result.functions.push({ + name: path.node.id.name, + parameters: params, + returnType: 'any', + async: path.node.async || false, + generator: path.node.generator || false, + startLine: path.node.loc?.start.line || 0, + endLine: path.node.loc?.end.line || 0 + }); + } + } + }); + } catch (error: any) { + result.errors.push({ + message: `Frontmatter parse error: ${error.message}`, + line: error.loc?.line || 0 + }); + } + } + + private parseScript(code: string, result: ParserResult): void { + try { + const ast = parser.parse(code, { + sourceType: 'module', + plugins: ['typescript', 'jsx'], + errorRecovery: true + }); + + const self = this; + traverse(ast, { + FunctionDeclaration(path) { + if (path.node.id) { + const params = path.node.params.map(param => { + if (param.type === 'Identifier') { + return { name: param.name, type: 'any' }; + } + return { name: 'unknown', type: 'any' }; + }); + + result.functions.push({ + name: path.node.id.name, + parameters: params, + returnType: 'any', + async: path.node.async || false, + generator: path.node.generator || false, + startLine: path.node.loc?.start.line || 0, + endLine: path.node.loc?.end.line || 0, + isClientSide: true // Script tag functions run on client + }); + } + }, + + VariableDeclaration(path) { + for (const decl of path.node.declarations) { + if (decl.id.type === 'Identifier' && decl.init) { + // Check if it's a function expression + if (decl.init.type === 'ArrowFunctionExpression' || + decl.init.type === 'FunctionExpression') { + const params = decl.init.params.map((param: any) => { + if (param.type === 'Identifier') { + return { name: param.name, type: 'any' }; + } + return { name: 'unknown', type: 'any' }; + }); + + result.functions.push({ + name: decl.id.name, + parameters: params, + returnType: 'any', + async: decl.init.async || false, + generator: decl.init.generator || false, + startLine: decl.init.loc?.start.line || 0, + endLine: decl.init.loc?.end.line || 0, + isClientSide: true + }); + } else { + result.constants.push({ + name: decl.id.name, + type: 'any', + value: self.getValueString(decl.init) + }); + } + } + } + } + }); + } catch (error: any) { + result.errors.push({ + message: `Script parse error: ${error.message}`, + line: error.loc?.line || 0 + }); + } + } + + private getValueString(node: any): string | undefined { + if (!node) return undefined; + if (node.type === 'StringLiteral') return node.value; + if (node.type === 'NumericLiteral') return String(node.value); + if (node.type === 'BooleanLiteral') return String(node.value); + if (node.type === 'NullLiteral') return 'null'; + if (node.type === 'Identifier') return node.name; + if (node.type === 'TemplateLiteral') return 'template'; + if (node.type === 'ObjectExpression') return 'object'; + if (node.type === 'ArrayExpression') return 'array'; + return undefined; + } } \ No newline at end of file diff --git a/src/parsers/go.ts b/src/parsers/go.ts index 3b5bd42..68e3300 100644 --- a/src/parsers/go.ts +++ b/src/parsers/go.ts @@ -1,396 +1,396 @@ -import { ParserResult, FunctionInfo, ClassInfo} from '../types'; -import { Parser } from '../types'; - -export class GoParser implements Parser { - language = 'Go'; - extensions = ['.go']; - - parse(content: string, filePath: string): ParserResult { - const result: ParserResult = { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - errors: [] - }; - - try { - const lines = content.split('\n'); - let inImportBlock = false; - let inStructBlock = false; - let currentStruct: ClassInfo | null = null; - let structIndent = 0; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const trimmed = line.trim(); - const lineNumber = i + 1; - - // Skip comments and empty lines - if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed === '') { - continue; - } - - // Package declaration - if (trimmed.startsWith('package ')) { - const pkgMatch = trimmed.match(/^package\s+(\w+)/); - if (pkgMatch) { - // Store package name for export determination - result.packageName = pkgMatch[1]; - } - } - - // Import statements - if (trimmed === 'import (') { - inImportBlock = true; - continue; - } - - if (inImportBlock) { - if (trimmed === ')') { - inImportBlock = false; - continue; - } - - const importMatch = trimmed.match(/^(?:(\w+)\s+)?"([^"]+)"/); - if (importMatch) { - const alias = importMatch[1]; - const source = importMatch[2]; - - result.imports.push({ - source, - specifiers: [{ name: '*', alias: alias || source.split('/').pop() || source }], - type: 'static' - }); - - if (source.startsWith('.')) { - result.dependencies.push(source); - } - } - } - - // Single import statement - if (trimmed.startsWith('import ') && !inImportBlock) { - const singleImportMatch = trimmed.match(/^import\s+(?:(\w+)\s+)?"([^"]+)"/); - if (singleImportMatch) { - const alias = singleImportMatch[1]; - const source = singleImportMatch[2]; - - result.imports.push({ - source, - specifiers: [{ name: '*', alias: alias || source.split('/').pop() || source }], - type: 'static' - }); - - if (source.startsWith('.')) { - result.dependencies.push(source); - } - } - } - - // Function declarations - if (trimmed.startsWith('func ')) { - const funcMatch = trimmed.match(/^func\s+(?:\(([^)]*)\)\s+)?(\w+)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s+(.+))?/); - if (funcMatch) { - const receiver = funcMatch[1]; - const funcName = funcMatch[2]; - const paramsStr = funcMatch[3]; - const multipleReturns = funcMatch[4]; - const singleReturn = funcMatch[5]; - - const parameters = this.parseParameters(paramsStr); - const returnType = this.parseReturnType(multipleReturns, singleReturn); - - const funcInfo: FunctionInfo = { - name: funcName, - parameters, - returnType, - async: false, // Go doesn't have async/await syntax - generator: false, - startLine: lineNumber, - endLine: this.findEndLine(lines, i), - receiver - }; - - if (receiver) { - // It's a method - const structName = this.extractStructName(receiver); - if (structName) { - // Find or create the struct - let struct = result.classes.find(c => c.name === structName); - if (!struct) { - struct = { - name: structName, - methods: [], - properties: [], - extends: undefined, - implements: [], - startLine: 0, - endLine: 0 - }; - result.classes.push(struct); - } - - struct.methods.push({ - name: funcName, - visibility: this.isExported(funcName) ? 'public' : 'private', - static: false - }); - } - } else { - result.functions.push(funcInfo); - - // Exported functions (start with uppercase) - if (this.isExported(funcName)) { - result.exports.push({ - name: funcName, - type: 'function' - }); - } - } - } - } - - // Type/Struct declarations - if (trimmed.startsWith('type ')) { - const typeMatch = trimmed.match(/^type\s+(\w+)\s+(.+)$/); - if (typeMatch) { - const typeName = typeMatch[1]; - const typeDefinition = typeMatch[2]; - - if (typeDefinition === 'struct {' || typeDefinition.startsWith('struct {')) { - // Start of struct definition - currentStruct = { - name: typeName, - methods: [], - properties: [], - extends: undefined, - implements: [], - startLine: lineNumber, - endLine: this.findStructEndLine(lines, i) - }; - inStructBlock = true; - structIndent = line.length - line.trimStart().length; - - if (this.isExported(typeName)) { - result.exports.push({ - name: typeName, - type: 'class' - }); - } - } else if (typeDefinition.includes('interface')) { - // Interface definition - const interfaceStruct: ClassInfo = { - name: typeName, - methods: [], - properties: [], - extends: undefined, - implements: [], - interface: true, - startLine: lineNumber, - endLine: this.findStructEndLine(lines, i) - }; - - result.classes.push(interfaceStruct); - - if (this.isExported(typeName)) { - result.exports.push({ - name: typeName, - type: 'interface' - }); - } - } else { - // Type alias - result.constants.push({ - name: typeName, - type: 'type', - value: typeDefinition - }); - - if (this.isExported(typeName)) { - result.exports.push({ - name: typeName, - type: 'type' - }); - } - } - } - } - - // Struct fields - if (inStructBlock && currentStruct) { - if (trimmed === '}') { - result.classes.push(currentStruct); - currentStruct = null; - inStructBlock = false; - continue; - } - - const fieldMatch = trimmed.match(/^(\w+)\s+([^`]+)(?:`(.+)`)?/); - if (fieldMatch) { - const fieldName = fieldMatch[1]; - const fieldType = fieldMatch[2].trim(); - const tags = fieldMatch[3]; - - currentStruct.properties.push({ - name: fieldName, - type: fieldType, - visibility: this.isExported(fieldName) ? 'public' : 'private', - static: false, - tags - }); - } - } - - // Constants and variables - if (trimmed.startsWith('const ') || trimmed.startsWith('var ')) { - const isConst = trimmed.startsWith('const '); - const varMatch = trimmed.match(/^(?:const|var)\s+(\w+)(?:\s+([^=]+))?\s*=/); - if (varMatch) { - const varName = varMatch[1]; - const varType = varMatch[2] ? varMatch[2].trim() : 'any'; - - if (isConst) { - result.constants.push({ - name: varName, - type: varType, - value: undefined - }); - } - - if (this.isExported(varName)) { - result.exports.push({ - name: varName, - type: 'variable' - }); - } - } - } - - // Const block - if (trimmed === 'const (') { - // Parse const block - let j = i + 1; - while (j < lines.length && !lines[j].trim().startsWith(')')) { - const constLine = lines[j].trim(); - const constMatch = constLine.match(/^(\w+)(?:\s+([^=]+))?\s*=/); - if (constMatch) { - const constName = constMatch[1]; - const constType = constMatch[2] ? constMatch[2].trim() : 'any'; - - result.constants.push({ - name: constName, - type: constType, - value: undefined - }); - - if (this.isExported(constName)) { - result.exports.push({ - name: constName, - type: 'variable' - }); - } - } - j++; - } - } - } - - // Add any remaining struct - if (currentStruct) { - result.classes.push(currentStruct); - } - - } catch (error: any) { - result.errors.push(`Parse error: ${error.message}`); - } - - return result; - } - - private isExported(name: string): boolean { - // In Go, exported names start with an uppercase letter - return /^[A-Z]/.test(name); - } - - private extractStructName(receiver: string): string | null { - // Extract struct name from receiver like "*StructName" or "StructName" - const match = receiver.match(/\*?(\w+)/); - return match ? match[1] : null; - } - - private parseParameters(paramsStr: string): Array<{ name: string; type: string }> { - if (!paramsStr.trim()) return []; - - const params: Array<{ name: string; type: string }> = []; - const paramList = paramsStr.split(','); - - for (const param of paramList) { - const trimmed = param.trim(); - if (!trimmed) continue; - - // Handle various parameter formats - const parts = trimmed.split(/\s+/); - if (parts.length >= 2) { - // name type format - params.push({ - name: parts[0], - type: parts.slice(1).join(' ') - }); - } else if (parts.length === 1) { - // Just type (anonymous parameter) - params.push({ - name: '_', - type: parts[0] - }); - } - } - - return params; - } - - private parseReturnType(multipleReturns: string | undefined, singleReturn: string | undefined): string { - if (multipleReturns) { - // Multiple return values - return `(${multipleReturns})`; - } - if (singleReturn) { - // Single return value - return singleReturn.trim().replace(/\s*{.*$/, ''); - } - return 'void'; - } - - private findEndLine(lines: string[], startIndex: number): number { - let braceCount = 0; - let started = false; - - for (let i = startIndex; i < lines.length; i++) { - const line = lines[i]; - for (const char of line) { - if (char === '{') { - braceCount++; - started = true; - } else if (char === '}') { - braceCount--; - if (started && braceCount === 0) { - return i + 1; - } - } - } - } - - return lines.length; - } - - private findStructEndLine(lines: string[], startIndex: number): number { - for (let i = startIndex; i < lines.length; i++) { - if (lines[i].includes('}')) { - return i + 1; - } - } - return lines.length; - } +import { ParserResult, FunctionInfo, ClassInfo} from '../types'; +import { Parser } from '../types'; + +export class GoParser implements Parser { + language = 'Go'; + extensions = ['.go']; + + parse(content: string, filePath: string): ParserResult { + const result: ParserResult = { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + errors: [] + }; + + try { + const lines = content.split('\n'); + let inImportBlock = false; + let inStructBlock = false; + let currentStruct: ClassInfo | null = null; + let structIndent = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + const lineNumber = i + 1; + + // Skip comments and empty lines + if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed === '') { + continue; + } + + // Package declaration + if (trimmed.startsWith('package ')) { + const pkgMatch = trimmed.match(/^package\s+(\w+)/); + if (pkgMatch) { + // Store package name for export determination + result.packageName = pkgMatch[1]; + } + } + + // Import statements + if (trimmed === 'import (') { + inImportBlock = true; + continue; + } + + if (inImportBlock) { + if (trimmed === ')') { + inImportBlock = false; + continue; + } + + const importMatch = trimmed.match(/^(?:(\w+)\s+)?"([^"]+)"/); + if (importMatch) { + const alias = importMatch[1]; + const source = importMatch[2]; + + result.imports.push({ + source, + specifiers: [{ name: '*', alias: alias || source.split('/').pop() || source }], + type: 'static' + }); + + if (source.startsWith('.')) { + result.dependencies.push(source); + } + } + } + + // Single import statement + if (trimmed.startsWith('import ') && !inImportBlock) { + const singleImportMatch = trimmed.match(/^import\s+(?:(\w+)\s+)?"([^"]+)"/); + if (singleImportMatch) { + const alias = singleImportMatch[1]; + const source = singleImportMatch[2]; + + result.imports.push({ + source, + specifiers: [{ name: '*', alias: alias || source.split('/').pop() || source }], + type: 'static' + }); + + if (source.startsWith('.')) { + result.dependencies.push(source); + } + } + } + + // Function declarations + if (trimmed.startsWith('func ')) { + const funcMatch = trimmed.match(/^func\s+(?:\(([^)]*)\)\s+)?(\w+)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s+(.+))?/); + if (funcMatch) { + const receiver = funcMatch[1]; + const funcName = funcMatch[2]; + const paramsStr = funcMatch[3]; + const multipleReturns = funcMatch[4]; + const singleReturn = funcMatch[5]; + + const parameters = this.parseParameters(paramsStr); + const returnType = this.parseReturnType(multipleReturns, singleReturn); + + const funcInfo: FunctionInfo = { + name: funcName, + parameters, + returnType, + async: false, // Go doesn't have async/await syntax + generator: false, + startLine: lineNumber, + endLine: this.findEndLine(lines, i), + receiver + }; + + if (receiver) { + // It's a method + const structName = this.extractStructName(receiver); + if (structName) { + // Find or create the struct + let struct = result.classes.find(c => c.name === structName); + if (!struct) { + struct = { + name: structName, + methods: [], + properties: [], + extends: undefined, + implements: [], + startLine: 0, + endLine: 0 + }; + result.classes.push(struct); + } + + struct.methods.push({ + name: funcName, + visibility: this.isExported(funcName) ? 'public' : 'private', + static: false + }); + } + } else { + result.functions.push(funcInfo); + + // Exported functions (start with uppercase) + if (this.isExported(funcName)) { + result.exports.push({ + name: funcName, + type: 'function' + }); + } + } + } + } + + // Type/Struct declarations + if (trimmed.startsWith('type ')) { + const typeMatch = trimmed.match(/^type\s+(\w+)\s+(.+)$/); + if (typeMatch) { + const typeName = typeMatch[1]; + const typeDefinition = typeMatch[2]; + + if (typeDefinition === 'struct {' || typeDefinition.startsWith('struct {')) { + // Start of struct definition + currentStruct = { + name: typeName, + methods: [], + properties: [], + extends: undefined, + implements: [], + startLine: lineNumber, + endLine: this.findStructEndLine(lines, i) + }; + inStructBlock = true; + structIndent = line.length - line.trimStart().length; + + if (this.isExported(typeName)) { + result.exports.push({ + name: typeName, + type: 'class' + }); + } + } else if (typeDefinition.includes('interface')) { + // Interface definition + const interfaceStruct: ClassInfo = { + name: typeName, + methods: [], + properties: [], + extends: undefined, + implements: [], + interface: true, + startLine: lineNumber, + endLine: this.findStructEndLine(lines, i) + }; + + result.classes.push(interfaceStruct); + + if (this.isExported(typeName)) { + result.exports.push({ + name: typeName, + type: 'interface' + }); + } + } else { + // Type alias + result.constants.push({ + name: typeName, + type: 'type', + value: typeDefinition + }); + + if (this.isExported(typeName)) { + result.exports.push({ + name: typeName, + type: 'type' + }); + } + } + } + } + + // Struct fields + if (inStructBlock && currentStruct) { + if (trimmed === '}') { + result.classes.push(currentStruct); + currentStruct = null; + inStructBlock = false; + continue; + } + + const fieldMatch = trimmed.match(/^(\w+)\s+([^`]+)(?:`(.+)`)?/); + if (fieldMatch) { + const fieldName = fieldMatch[1]; + const fieldType = fieldMatch[2].trim(); + const tags = fieldMatch[3]; + + currentStruct.properties.push({ + name: fieldName, + type: fieldType, + visibility: this.isExported(fieldName) ? 'public' : 'private', + static: false, + tags + }); + } + } + + // Constants and variables + if (trimmed.startsWith('const ') || trimmed.startsWith('var ')) { + const isConst = trimmed.startsWith('const '); + const varMatch = trimmed.match(/^(?:const|var)\s+(\w+)(?:\s+([^=]+))?\s*=/); + if (varMatch) { + const varName = varMatch[1]; + const varType = varMatch[2] ? varMatch[2].trim() : 'any'; + + if (isConst) { + result.constants.push({ + name: varName, + type: varType, + value: undefined + }); + } + + if (this.isExported(varName)) { + result.exports.push({ + name: varName, + type: 'variable' + }); + } + } + } + + // Const block + if (trimmed === 'const (') { + // Parse const block + let j = i + 1; + while (j < lines.length && !lines[j].trim().startsWith(')')) { + const constLine = lines[j].trim(); + const constMatch = constLine.match(/^(\w+)(?:\s+([^=]+))?\s*=/); + if (constMatch) { + const constName = constMatch[1]; + const constType = constMatch[2] ? constMatch[2].trim() : 'any'; + + result.constants.push({ + name: constName, + type: constType, + value: undefined + }); + + if (this.isExported(constName)) { + result.exports.push({ + name: constName, + type: 'variable' + }); + } + } + j++; + } + } + } + + // Add any remaining struct + if (currentStruct) { + result.classes.push(currentStruct); + } + + } catch (error: any) { + result.errors.push(`Parse error: ${error.message}`); + } + + return result; + } + + private isExported(name: string): boolean { + // In Go, exported names start with an uppercase letter + return /^[A-Z]/.test(name); + } + + private extractStructName(receiver: string): string | null { + // Extract struct name from receiver like "*StructName" or "StructName" + const match = receiver.match(/\*?(\w+)/); + return match ? match[1] : null; + } + + private parseParameters(paramsStr: string): Array<{ name: string; type: string }> { + if (!paramsStr.trim()) return []; + + const params: Array<{ name: string; type: string }> = []; + const paramList = paramsStr.split(','); + + for (const param of paramList) { + const trimmed = param.trim(); + if (!trimmed) continue; + + // Handle various parameter formats + const parts = trimmed.split(/\s+/); + if (parts.length >= 2) { + // name type format + params.push({ + name: parts[0], + type: parts.slice(1).join(' ') + }); + } else if (parts.length === 1) { + // Just type (anonymous parameter) + params.push({ + name: '_', + type: parts[0] + }); + } + } + + return params; + } + + private parseReturnType(multipleReturns: string | undefined, singleReturn: string | undefined): string { + if (multipleReturns) { + // Multiple return values + return `(${multipleReturns})`; + } + if (singleReturn) { + // Single return value + return singleReturn.trim().replace(/\s*{.*$/, ''); + } + return 'void'; + } + + private findEndLine(lines: string[], startIndex: number): number { + let braceCount = 0; + let started = false; + + for (let i = startIndex; i < lines.length; i++) { + const line = lines[i]; + for (const char of line) { + if (char === '{') { + braceCount++; + started = true; + } else if (char === '}') { + braceCount--; + if (started && braceCount === 0) { + return i + 1; + } + } + } + } + + return lines.length; + } + + private findStructEndLine(lines: string[], startIndex: number): number { + for (let i = startIndex; i < lines.length; i++) { + if (lines[i].includes('}')) { + return i + 1; + } + } + return lines.length; + } } \ No newline at end of file diff --git a/src/parsers/graphql-enhanced.ts b/src/parsers/graphql-enhanced.ts index c0312f2..76c4a5b 100644 --- a/src/parsers/graphql-enhanced.ts +++ b/src/parsers/graphql-enhanced.ts @@ -1,474 +1,474 @@ -/** - * Enhanced GraphQL Parser - * Provides advanced GraphQL parsing with React hook detection and operation metadata extraction - */ - -import { parse, DocumentNode, DefinitionNode, OperationDefinitionNode, FragmentDefinitionNode, FieldNode} from 'graphql'; -import { Parser } from '../types'; -import { ParserResult, FunctionInfo, ClassInfo} from '../types'; - -interface OperationMetadata { - name: string; - type: 'query' | 'mutation' | 'subscription'; - variables: string[]; - fields: string[]; - fragments: string[]; - directives: string[]; - generatedHooks: string[]; -} - -interface FragmentMetadata { - name: string; - onType: string; - fields: string[]; - directives: string[]; -} - -interface SchemaMetadata { - name: string; - kind: string; - fields?: string[]; - values?: string[]; -} - -/** - * Enhanced GraphQL Parser with React hook detection - */ -export class EnhancedGraphQLParser implements Parser { - language = 'GraphQL'; - extensions = ['.graphql', '.gql']; - - /** - * Generate potential React hooks from GraphQL operation - */ - private generateReactHooks(operationName: string, operationType: string): string[] { - const hooks: string[] = []; - - // Ensure proper capitalization - const capitalizedName = operationName.charAt(0).toUpperCase() + operationName.slice(1); - - switch (operationType.toLowerCase()) { - case 'query': - // Standard query hooks - hooks.push(`use${capitalizedName}Query`); - hooks.push(`use${capitalizedName}LazyQuery`); - hooks.push(`use${capitalizedName}SuspenseQuery`); - // Apollo specific - hooks.push(`use${capitalizedName}`); - // Relay style - hooks.push(`use${capitalizedName}QueryLoader`); - break; - - case 'mutation': - hooks.push(`use${capitalizedName}Mutation`); - hooks.push(`use${capitalizedName}`); - break; - - case 'subscription': - hooks.push(`use${capitalizedName}Subscription`); - hooks.push(`use${capitalizedName}`); - break; - } - - return hooks; - } - - /** - * Extract fields from selection set recursively - */ - private extractFields(selectionSet: any): string[] { - const fields: string[] = []; - - if (!selectionSet || !selectionSet.selections) return fields; - - for (const selection of selectionSet.selections) { - switch (selection.kind) { - case 'Field': - const field = selection as FieldNode; - fields.push(field.name.value); - - // Add aliased name if different - if (field.alias) { - fields.push(`${field.alias.value} (alias for ${field.name.value})`); - } - - // Recursively extract nested fields - if (field.selectionSet) { - const nestedFields = this.extractFields(field.selectionSet); - fields.push(...nestedFields.map(f => `${field.name.value}.${f}`)); - } - break; - - case 'FragmentSpread': - fields.push(`...${selection.name.value}`); - break; - - case 'InlineFragment': - const inlineFields = this.extractFields(selection.selectionSet); - const typeCondition = selection.typeCondition?.name.value || 'inline'; - fields.push(...inlineFields.map(f => `${typeCondition}.${f}`)); - break; - } - } - - return fields; - } - - /** - * Extract operation metadata from AST node - */ - private extractOperationMetadata(node: OperationDefinitionNode): OperationMetadata { - const name = node.name?.value || 'Anonymous'; - const type = node.operation as 'query' | 'mutation' | 'subscription'; - - // Extract variables - const variables = (node.variableDefinitions || []).map(varDef => { - const varName = varDef.variable.name.value; - const varType = this.printType(varDef.type); - const defaultValue = varDef.defaultValue ? ` = ${this.printValue(varDef.defaultValue)}` : ''; - return `${varName}: ${varType}${defaultValue}`; - }); - - // Extract fields - const fields = this.extractFields(node.selectionSet); - - // Extract fragments used - const fragments = this.extractFragmentSpreads(node.selectionSet); - - // Extract directives - const directives = (node.directives || []).map(d => d.name.value); - - // Generate potential React hooks - const generatedHooks = this.generateReactHooks(name, type); - - return { - name, - type, - variables, - fields, - fragments, - directives, - generatedHooks - }; - } - - /** - * Extract fragment spreads from selection set - */ - private extractFragmentSpreads(selectionSet: any): string[] { - const fragments: string[] = []; - - if (!selectionSet || !selectionSet.selections) return fragments; - - for (const selection of selectionSet.selections) { - if (selection.kind === 'FragmentSpread') { - fragments.push(selection.name.value); - } else if (selection.selectionSet) { - fragments.push(...this.extractFragmentSpreads(selection.selectionSet)); - } - } - - return [...new Set(fragments)]; - } - - /** - * Print GraphQL type as string - */ - private printType(type: any): string { - if (type.kind === 'NonNullType') { - return `${this.printType(type.type)}!`; - } - if (type.kind === 'ListType') { - return `[${this.printType(type.type)}]`; - } - if (type.kind === 'NamedType') { - return type.name.value; - } - return 'Unknown'; - } - - /** - * Print GraphQL value as string - */ - private printValue(value: any): string { - if (value.kind === 'StringValue') return `"${value.value}"`; - if (value.kind === 'IntValue') return value.value; - if (value.kind === 'FloatValue') return value.value; - if (value.kind === 'BooleanValue') return value.value; - if (value.kind === 'NullValue') return 'null'; - if (value.kind === 'EnumValue') return value.value; - if (value.kind === 'ListValue') { - return `[${value.values.map((v: any) => this.printValue(v)).join(', ')}]`; - } - if (value.kind === 'ObjectValue') { - const fields = value.fields.map((f: any) => - `${f.name.value}: ${this.printValue(f.value)}` - ).join(', '); - return `{${fields}}`; - } - return 'Unknown'; - } - - /** - * Extract fragment metadata - */ - private extractFragmentMetadata(node: FragmentDefinitionNode): FragmentMetadata { - const name = node.name.value; - const onType = node.typeCondition.name.value; - const fields = this.extractFields(node.selectionSet); - const directives = (node.directives || []).map(d => d.name.value); - - return { name, onType, fields, directives }; - } - - /** - * Extract schema/type metadata - */ - private extractSchemaMetadata(node: any): SchemaMetadata { - const name = node.name?.value || 'Schema'; - const kind = node.kind; - - const metadata: SchemaMetadata = { name, kind }; - - // Extract fields for object types - if (node.fields) { - metadata.fields = node.fields.map((f: any) => f.name.value); - } - - // Extract values for enum types - if (node.values) { - metadata.values = node.values.map((v: any) => v.name.value); - } - - return metadata; - } - - /** - * Main parse function - */ - parse(content: string, filePath: string): ParserResult { - const result: ParserResult = { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - errors: [] - }; - - // Store metadata separately for internal use - const metadata = { - operations: [] as OperationMetadata[], - fragments: [] as FragmentMetadata[], - schemas: [] as SchemaMetadata[], - generatedHooks: [] as string[] - }; - - try { - // Parse GraphQL document - const ast: DocumentNode = parse(content); - - // Process each definition - ast.definitions.forEach((definition: DefinitionNode) => { - switch (definition.kind) { - case 'OperationDefinition': { - const metadata = this.extractOperationMetadata(definition); - - // Add as function for compatibility - result.functions.push({ - name: metadata.name, - parameters: metadata.variables.map(v => { - const [name, type] = v.split(':').map(s => s.trim()); - return { name: name || v, type: type || 'any' }; - }), - returnType: metadata.type, - async: false, - generator: false, - startLine: (definition.loc?.startToken.line || 1) - 1, - endLine: (definition.loc?.endToken.line || 1) - 1, - metadata: { - type: 'graphql-operation', - operationType: metadata.type, - fields: metadata.fields, - fragments: metadata.fragments, - directives: metadata.directives, - generatedHooks: metadata.generatedHooks - } - } as FunctionInfo); - - // Export the operation - result.exports.push({ - name: metadata.name, - type: `graphql-${metadata.type}` - }); - - // Also export each generated hook - metadata.generatedHooks.forEach(hook => { - result.exports.push({ - name: hook, - type: 'generated-react-hook' - }); - }); - break; - } - - case 'FragmentDefinition': { - const metadata = this.extractFragmentMetadata(definition); - - // Add as constant - result.constants.push({ - name: metadata.name, - value: `fragment on ${metadata.onType}`, - type: 'graphql-fragment' - }); - - // Export the fragment - result.exports.push({ - name: metadata.name, - type: 'graphql-fragment' - }); - break; - } - - case 'SchemaDefinition': - case 'ScalarTypeDefinition': - case 'ObjectTypeDefinition': - case 'InterfaceTypeDefinition': - case 'UnionTypeDefinition': - case 'EnumTypeDefinition': - case 'InputObjectTypeDefinition': - case 'DirectiveDefinition': { - const metadata = this.extractSchemaMetadata(definition); - - // Add as class for compatibility - result.classes.push({ - name: metadata.name, - methods: [], - properties: metadata.fields?.map(f => ({ - name: f, - type: 'field', - visibility: 'public', - static: false - })) || [], - extends: undefined, - implements: [], - startLine: (definition.loc?.startToken.line || 1) - 1, - endLine: (definition.loc?.endToken.line || 1) - 1, - metadata: { - type: 'graphql-schema', - kind: metadata.kind, - values: metadata.values - } - } as ClassInfo); - - // Export the type - result.exports.push({ - name: metadata.name, - type: `graphql-${metadata.kind.replace('Definition', '').toLowerCase()}` - }); - break; - } - } - }); - - // Extract imports from comments (common pattern in GraphQL files) - const importCommentPattern = /#\s*import\s+(.+)\s+from\s+['"](.+)['"]/gm; - let match; - while ((match = importCommentPattern.exec(content)) !== null) { - const imported = match[1].trim(); - const source = match[2].trim(); - - result.imports.push({ - source, - specifiers: imported.split(',').map(spec => { - const trimmed = spec.trim(); - return { name: trimmed, alias: trimmed }; - }), - type: 'comment-import' - }); - - result.dependencies.push(source); - } - - // Extract schema imports (extend type patterns) - const extendPattern = /extend\s+(type|interface|enum|union|input)\s+(\w+)/gm; - while ((match = extendPattern.exec(content)) !== null) { - const extendedType = match[2]; - result.dependencies.push(`#extended:${extendedType}`); - } - - } catch (error: any) { - // Handle GraphQL parsing errors - result.errors.push({ - message: error.message, - line: error.locations?.[0]?.line || 0 - }); - } - - return result; - } -} - +/** + * Enhanced GraphQL Parser + * Provides advanced GraphQL parsing with React hook detection and operation metadata extraction + */ + +import { parse, DocumentNode, DefinitionNode, OperationDefinitionNode, FragmentDefinitionNode, FieldNode} from 'graphql'; +import { Parser } from '../types'; +import { ParserResult, FunctionInfo, ClassInfo} from '../types'; + +interface OperationMetadata { + name: string; + type: 'query' | 'mutation' | 'subscription'; + variables: string[]; + fields: string[]; + fragments: string[]; + directives: string[]; + generatedHooks: string[]; +} + +interface FragmentMetadata { + name: string; + onType: string; + fields: string[]; + directives: string[]; +} + +interface SchemaMetadata { + name: string; + kind: string; + fields?: string[]; + values?: string[]; +} + +/** + * Enhanced GraphQL Parser with React hook detection + */ +export class EnhancedGraphQLParser implements Parser { + language = 'GraphQL'; + extensions = ['.graphql', '.gql']; + + /** + * Generate potential React hooks from GraphQL operation + */ + private generateReactHooks(operationName: string, operationType: string): string[] { + const hooks: string[] = []; + + // Ensure proper capitalization + const capitalizedName = operationName.charAt(0).toUpperCase() + operationName.slice(1); + + switch (operationType.toLowerCase()) { + case 'query': + // Standard query hooks + hooks.push(`use${capitalizedName}Query`); + hooks.push(`use${capitalizedName}LazyQuery`); + hooks.push(`use${capitalizedName}SuspenseQuery`); + // Apollo specific + hooks.push(`use${capitalizedName}`); + // Relay style + hooks.push(`use${capitalizedName}QueryLoader`); + break; + + case 'mutation': + hooks.push(`use${capitalizedName}Mutation`); + hooks.push(`use${capitalizedName}`); + break; + + case 'subscription': + hooks.push(`use${capitalizedName}Subscription`); + hooks.push(`use${capitalizedName}`); + break; + } + + return hooks; + } + + /** + * Extract fields from selection set recursively + */ + private extractFields(selectionSet: any): string[] { + const fields: string[] = []; + + if (!selectionSet || !selectionSet.selections) return fields; + + for (const selection of selectionSet.selections) { + switch (selection.kind) { + case 'Field': + const field = selection as FieldNode; + fields.push(field.name.value); + + // Add aliased name if different + if (field.alias) { + fields.push(`${field.alias.value} (alias for ${field.name.value})`); + } + + // Recursively extract nested fields + if (field.selectionSet) { + const nestedFields = this.extractFields(field.selectionSet); + fields.push(...nestedFields.map(f => `${field.name.value}.${f}`)); + } + break; + + case 'FragmentSpread': + fields.push(`...${selection.name.value}`); + break; + + case 'InlineFragment': + const inlineFields = this.extractFields(selection.selectionSet); + const typeCondition = selection.typeCondition?.name.value || 'inline'; + fields.push(...inlineFields.map(f => `${typeCondition}.${f}`)); + break; + } + } + + return fields; + } + + /** + * Extract operation metadata from AST node + */ + private extractOperationMetadata(node: OperationDefinitionNode): OperationMetadata { + const name = node.name?.value || 'Anonymous'; + const type = node.operation as 'query' | 'mutation' | 'subscription'; + + // Extract variables + const variables = (node.variableDefinitions || []).map(varDef => { + const varName = varDef.variable.name.value; + const varType = this.printType(varDef.type); + const defaultValue = varDef.defaultValue ? ` = ${this.printValue(varDef.defaultValue)}` : ''; + return `${varName}: ${varType}${defaultValue}`; + }); + + // Extract fields + const fields = this.extractFields(node.selectionSet); + + // Extract fragments used + const fragments = this.extractFragmentSpreads(node.selectionSet); + + // Extract directives + const directives = (node.directives || []).map(d => d.name.value); + + // Generate potential React hooks + const generatedHooks = this.generateReactHooks(name, type); + + return { + name, + type, + variables, + fields, + fragments, + directives, + generatedHooks + }; + } + + /** + * Extract fragment spreads from selection set + */ + private extractFragmentSpreads(selectionSet: any): string[] { + const fragments: string[] = []; + + if (!selectionSet || !selectionSet.selections) return fragments; + + for (const selection of selectionSet.selections) { + if (selection.kind === 'FragmentSpread') { + fragments.push(selection.name.value); + } else if (selection.selectionSet) { + fragments.push(...this.extractFragmentSpreads(selection.selectionSet)); + } + } + + return [...new Set(fragments)]; + } + + /** + * Print GraphQL type as string + */ + private printType(type: any): string { + if (type.kind === 'NonNullType') { + return `${this.printType(type.type)}!`; + } + if (type.kind === 'ListType') { + return `[${this.printType(type.type)}]`; + } + if (type.kind === 'NamedType') { + return type.name.value; + } + return 'Unknown'; + } + + /** + * Print GraphQL value as string + */ + private printValue(value: any): string { + if (value.kind === 'StringValue') return `"${value.value}"`; + if (value.kind === 'IntValue') return value.value; + if (value.kind === 'FloatValue') return value.value; + if (value.kind === 'BooleanValue') return value.value; + if (value.kind === 'NullValue') return 'null'; + if (value.kind === 'EnumValue') return value.value; + if (value.kind === 'ListValue') { + return `[${value.values.map((v: any) => this.printValue(v)).join(', ')}]`; + } + if (value.kind === 'ObjectValue') { + const fields = value.fields.map((f: any) => + `${f.name.value}: ${this.printValue(f.value)}` + ).join(', '); + return `{${fields}}`; + } + return 'Unknown'; + } + + /** + * Extract fragment metadata + */ + private extractFragmentMetadata(node: FragmentDefinitionNode): FragmentMetadata { + const name = node.name.value; + const onType = node.typeCondition.name.value; + const fields = this.extractFields(node.selectionSet); + const directives = (node.directives || []).map(d => d.name.value); + + return { name, onType, fields, directives }; + } + + /** + * Extract schema/type metadata + */ + private extractSchemaMetadata(node: any): SchemaMetadata { + const name = node.name?.value || 'Schema'; + const kind = node.kind; + + const metadata: SchemaMetadata = { name, kind }; + + // Extract fields for object types + if (node.fields) { + metadata.fields = node.fields.map((f: any) => f.name.value); + } + + // Extract values for enum types + if (node.values) { + metadata.values = node.values.map((v: any) => v.name.value); + } + + return metadata; + } + + /** + * Main parse function + */ + parse(content: string, filePath: string): ParserResult { + const result: ParserResult = { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + errors: [] + }; + + // Store metadata separately for internal use + const metadata = { + operations: [] as OperationMetadata[], + fragments: [] as FragmentMetadata[], + schemas: [] as SchemaMetadata[], + generatedHooks: [] as string[] + }; + + try { + // Parse GraphQL document + const ast: DocumentNode = parse(content); + + // Process each definition + ast.definitions.forEach((definition: DefinitionNode) => { + switch (definition.kind) { + case 'OperationDefinition': { + const metadata = this.extractOperationMetadata(definition); + + // Add as function for compatibility + result.functions.push({ + name: metadata.name, + parameters: metadata.variables.map(v => { + const [name, type] = v.split(':').map(s => s.trim()); + return { name: name || v, type: type || 'any' }; + }), + returnType: metadata.type, + async: false, + generator: false, + startLine: (definition.loc?.startToken.line || 1) - 1, + endLine: (definition.loc?.endToken.line || 1) - 1, + metadata: { + type: 'graphql-operation', + operationType: metadata.type, + fields: metadata.fields, + fragments: metadata.fragments, + directives: metadata.directives, + generatedHooks: metadata.generatedHooks + } + } as FunctionInfo); + + // Export the operation + result.exports.push({ + name: metadata.name, + type: `graphql-${metadata.type}` + }); + + // Also export each generated hook + metadata.generatedHooks.forEach(hook => { + result.exports.push({ + name: hook, + type: 'generated-react-hook' + }); + }); + break; + } + + case 'FragmentDefinition': { + const metadata = this.extractFragmentMetadata(definition); + + // Add as constant + result.constants.push({ + name: metadata.name, + value: `fragment on ${metadata.onType}`, + type: 'graphql-fragment' + }); + + // Export the fragment + result.exports.push({ + name: metadata.name, + type: 'graphql-fragment' + }); + break; + } + + case 'SchemaDefinition': + case 'ScalarTypeDefinition': + case 'ObjectTypeDefinition': + case 'InterfaceTypeDefinition': + case 'UnionTypeDefinition': + case 'EnumTypeDefinition': + case 'InputObjectTypeDefinition': + case 'DirectiveDefinition': { + const metadata = this.extractSchemaMetadata(definition); + + // Add as class for compatibility + result.classes.push({ + name: metadata.name, + methods: [], + properties: metadata.fields?.map(f => ({ + name: f, + type: 'field', + visibility: 'public', + static: false + })) || [], + extends: undefined, + implements: [], + startLine: (definition.loc?.startToken.line || 1) - 1, + endLine: (definition.loc?.endToken.line || 1) - 1, + metadata: { + type: 'graphql-schema', + kind: metadata.kind, + values: metadata.values + } + } as ClassInfo); + + // Export the type + result.exports.push({ + name: metadata.name, + type: `graphql-${metadata.kind.replace('Definition', '').toLowerCase()}` + }); + break; + } + } + }); + + // Extract imports from comments (common pattern in GraphQL files) + const importCommentPattern = /#\s*import\s+(.+)\s+from\s+['"](.+)['"]/gm; + let match; + while ((match = importCommentPattern.exec(content)) !== null) { + const imported = match[1].trim(); + const source = match[2].trim(); + + result.imports.push({ + source, + specifiers: imported.split(',').map(spec => { + const trimmed = spec.trim(); + return { name: trimmed, alias: trimmed }; + }), + type: 'comment-import' + }); + + result.dependencies.push(source); + } + + // Extract schema imports (extend type patterns) + const extendPattern = /extend\s+(type|interface|enum|union|input)\s+(\w+)/gm; + while ((match = extendPattern.exec(content)) !== null) { + const extendedType = match[2]; + result.dependencies.push(`#extended:${extendedType}`); + } + + } catch (error: any) { + // Handle GraphQL parsing errors + result.errors.push({ + message: error.message, + line: error.locations?.[0]?.line || 0 + }); + } + + return result; + } +} + /** * Helper function to detect GraphQL hook usage in TypeScript/JavaScript files */ function detectGraphQLHooks(content: string): any[] { - const hooks: any[] = []; - - // Patterns for different GraphQL client libraries - const patterns = [ - // Apollo Client - /use([A-Z]\w+)(Query|Mutation|Subscription|LazyQuery|SuspenseQuery)/g, - // Relay - /use(Preloaded)?Query|useMutation|useSubscription|useLazyLoadQuery/g, - // URQL - /use(Query|Mutation|Subscription)/g, - // Custom patterns - /useGraphQL(Query|Mutation|Subscription)/g - ]; - - const lines = content.split('\n'); - - lines.forEach((line, lineIndex) => { - patterns.forEach(pattern => { - let match; - pattern.lastIndex = 0; // Reset regex state - - while ((match = pattern.exec(line)) !== null) { - const fullMatch = match[0]; - let operationName = ''; - let operationType: 'query' | 'mutation' | 'subscription' = 'query'; - - // Extract operation name and type based on the pattern - if (match[1] && match[2]) { - // Pattern like useUserQuery - operationName = match[1]; - const typeMatch = match[2].toLowerCase(); - if (typeMatch.includes('mutation')) operationType = 'mutation'; - else if (typeMatch.includes('subscription')) operationType = 'subscription'; - else operationType = 'query'; - } else { - // Generic hooks like useQuery - operationName = 'Generic'; - if (fullMatch.includes('Mutation')) operationType = 'mutation'; - else if (fullMatch.includes('Subscription')) operationType = 'subscription'; - } - - hooks.push({ - hookName: fullMatch, - operationName, - operationType, - line: lineIndex + 1 - }); - } - }); - }); - - return hooks; -} - + const hooks: any[] = []; + + // Patterns for different GraphQL client libraries + const patterns = [ + // Apollo Client + /use([A-Z]\w+)(Query|Mutation|Subscription|LazyQuery|SuspenseQuery)/g, + // Relay + /use(Preloaded)?Query|useMutation|useSubscription|useLazyLoadQuery/g, + // URQL + /use(Query|Mutation|Subscription)/g, + // Custom patterns + /useGraphQL(Query|Mutation|Subscription)/g + ]; + + const lines = content.split('\n'); + + lines.forEach((line, lineIndex) => { + patterns.forEach(pattern => { + let match; + pattern.lastIndex = 0; // Reset regex state + + while ((match = pattern.exec(line)) !== null) { + const fullMatch = match[0]; + let operationName = ''; + let operationType: 'query' | 'mutation' | 'subscription' = 'query'; + + // Extract operation name and type based on the pattern + if (match[1] && match[2]) { + // Pattern like useUserQuery + operationName = match[1]; + const typeMatch = match[2].toLowerCase(); + if (typeMatch.includes('mutation')) operationType = 'mutation'; + else if (typeMatch.includes('subscription')) operationType = 'subscription'; + else operationType = 'query'; + } else { + // Generic hooks like useQuery + operationName = 'Generic'; + if (fullMatch.includes('Mutation')) operationType = 'mutation'; + else if (fullMatch.includes('Subscription')) operationType = 'subscription'; + } + + hooks.push({ + hookName: fullMatch, + operationName, + operationType, + line: lineIndex + 1 + }); + } + }); + }); + + return hooks; +} + /** * Analyze GraphQL schema for operation usage @@ -478,12 +478,12 @@ function analyzeSchemaUsage(schema: any): { unusedFields: Map; mostUsedFields: any[]; } { - // Placeholder for future implementation - return { - unusedTypes: [], - unusedFields: new Map(), - mostUsedFields: [] - }; -} - + // Placeholder for future implementation + return { + unusedTypes: [], + unusedFields: new Map(), + mostUsedFields: [] + }; +} + export default EnhancedGraphQLParser; \ No newline at end of file diff --git a/src/parsers/graphql.ts b/src/parsers/graphql.ts index e038654..258e495 100644 --- a/src/parsers/graphql.ts +++ b/src/parsers/graphql.ts @@ -1,414 +1,414 @@ -import { ParserResult, ClassInfo} from '../types'; -import { Parser } from '../types'; - -export class GraphQLParser implements Parser { - language = 'GraphQL'; - extensions = ['.graphql', '.gql']; - - parse(content: string, filePath: string): ParserResult { - const result: ParserResult = { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - errors: [] - }; - - try { - const lines = content.split('\n'); - let inType = false; - let currentType: ClassInfo | null = null; - let typeStartLine = 0; - let braceDepth = 0; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const trimmed = line.trim(); - const lineNumber = i + 1; - - // Skip comments - if (trimmed.startsWith('#')) { - continue; - } - - // Track brace depth - for (const char of trimmed) { - if (char === '{') braceDepth++; - if (char === '}') braceDepth--; - } - - // Check if we're exiting a type definition - if (inType && braceDepth === 0 && trimmed.includes('}')) { - if (currentType) { - currentType.endLine = lineNumber; - result.classes.push(currentType); - currentType = null; - } - inType = false; - } - - // Schema definition - if (trimmed.startsWith('schema')) { - result.constants.push({ - name: 'schema', - type: 'schema', - value: trimmed - }); - } - - // Type definitions (including Query, Mutation, Subscription) - if (trimmed.startsWith('type ')) { - const typeMatch = trimmed.match(/^type\s+(\w+)(?:\s+implements\s+(.+))?\s*{?/); - if (typeMatch) { - const typeName = typeMatch[1]; - const implementsStr = typeMatch[2]; - - currentType = { - name: typeName, - methods: [], - properties: [], - extends: undefined, - implements: implementsStr ? implementsStr.split('&').map(s => s.trim()) : [], - startLine: lineNumber, - endLine: lineNumber, - graphqlType: 'type' - }; - - inType = true; - typeStartLine = lineNumber; - - // Special handling for root types - if (typeName === 'Query' || typeName === 'Mutation' || typeName === 'Subscription') { - currentType.isRootType = true; - } - - result.exports.push({ - name: typeName, - type: 'type' - }); - } - } - - // Interface definitions - if (trimmed.startsWith('interface ')) { - const interfaceMatch = trimmed.match(/^interface\s+(\w+)\s*{?/); - if (interfaceMatch) { - const interfaceName = interfaceMatch[1]; - - currentType = { - name: interfaceName, - methods: [], - properties: [], - extends: undefined, - implements: [], - interface: true, - startLine: lineNumber, - endLine: lineNumber, - graphqlType: 'interface' - }; - - inType = true; - typeStartLine = lineNumber; - - result.exports.push({ - name: interfaceName, - type: 'interface' - }); - } - } - - // Input type definitions - if (trimmed.startsWith('input ')) { - const inputMatch = trimmed.match(/^input\s+(\w+)\s*{?/); - if (inputMatch) { - const inputName = inputMatch[1]; - - currentType = { - name: inputName, - methods: [], - properties: [], - extends: undefined, - implements: [], - startLine: lineNumber, - endLine: lineNumber, - graphqlType: 'input' - }; - - inType = true; - typeStartLine = lineNumber; - - result.exports.push({ - name: inputName, - type: 'input' - }); - } - } - - // Union type definitions - if (trimmed.startsWith('union ')) { - const unionMatch = trimmed.match(/^union\s+(\w+)\s*=\s*(.+)/); - if (unionMatch) { - const unionName = unionMatch[1]; - const unionTypes = unionMatch[2].split('|').map(s => s.trim()); - - result.constants.push({ - name: unionName, - type: 'union', - value: unionTypes.join(' | ') - }); - - result.exports.push({ - name: unionName, - type: 'union' - }); - } - } - - // Enum definitions - if (trimmed.startsWith('enum ')) { - const enumMatch = trimmed.match(/^enum\s+(\w+)\s*{?/); - if (enumMatch) { - const enumName = enumMatch[1]; - const enumValues: string[] = []; - - // Collect enum values - let j = i + 1; - while (j < lines.length && !lines[j].includes('}')) { - const enumLine = lines[j].trim(); - if (enumLine && !enumLine.startsWith('#')) { - enumValues.push(enumLine); - } - j++; - } - - result.constants.push({ - name: enumName, - type: 'enum', - value: enumValues.join(', ') - }); - - result.exports.push({ - name: enumName, - type: 'enum' - }); - } - } - - // Scalar definitions - if (trimmed.startsWith('scalar ')) { - const scalarMatch = trimmed.match(/^scalar\s+(\w+)/); - if (scalarMatch) { - const scalarName = scalarMatch[1]; - - result.constants.push({ - name: scalarName, - type: 'scalar', - value: 'custom' - }); - - result.exports.push({ - name: scalarName, - type: 'scalar' - }); - } - } - - // Directive definitions - if (trimmed.startsWith('directive @')) { - const directiveMatch = trimmed.match(/^directive\s+@(\w+)(?:\(([^)]*)\))?\s+on\s+(.+)/); - if (directiveMatch) { - const directiveName = directiveMatch[1]; - const args = directiveMatch[2]; - const locations = directiveMatch[3]; - - const parameters = args ? this.parseDirectiveArguments(args) : []; - - result.functions.push({ - name: `@${directiveName}`, - parameters, - returnType: 'void', - async: false, - generator: false, - startLine: lineNumber, - endLine: lineNumber, - directiveLocations: locations.split('|').map(s => s.trim()) - }); - - result.exports.push({ - name: `@${directiveName}`, - type: 'directive' - }); - } - } - - // Parse fields within types - if (inType && currentType && braceDepth > 0) { - // Skip the opening brace line - if (trimmed === '{') continue; - - // Parse field definitions - const fieldMatch = trimmed.match(/^(\w+)(?:\(([^)]*)\))?\s*:\s*(.+?)(?:\s+@.*)?$/); - if (fieldMatch) { - const fieldName = fieldMatch[1]; - const argsStr = fieldMatch[2]; - const fieldType = fieldMatch[3]; - - if (argsStr) { - // It's a method/resolver (has arguments) - const parameters = this.parseFieldArguments(argsStr); - - if (currentType.isRootType) { - // For root types (Query, Mutation, Subscription), these are essentially functions - result.functions.push({ - name: `${currentType.name}.${fieldName}`, - parameters, - returnType: fieldType, - async: false, - generator: false, - startLine: lineNumber, - endLine: lineNumber, - resolver: true, - parentType: currentType.name - }); - } - - currentType.methods.push({ - name: fieldName, - visibility: 'public', - static: false, - returnType: fieldType, - arguments: parameters - }); - } else { - // It's a field/property - currentType.properties.push({ - name: fieldName, - type: fieldType, - visibility: 'public', - static: false, - required: !fieldType.includes('?') && !fieldType.endsWith('!') - }); - } - } - } - - // Import statements (for schema stitching/federation) - if (trimmed.startsWith('extend ')) { - const extendMatch = trimmed.match(/^extend\s+(type|interface|input|enum|scalar)\s+(\w+)/); - if (extendMatch) { - const extendType = extendMatch[1]; - const extendName = extendMatch[2]; - - result.imports.push({ - source: extendName, - specifiers: [{ name: extendName, alias: extendName }], - type: 'extend' - }); - - result.dependencies.push(extendName); - } - } - } - - // Add any remaining type - if (currentType) { - result.classes.push(currentType); - } - - // Extract fragment definitions - const fragmentMatches = content.matchAll(/fragment\s+(\w+)\s+on\s+(\w+)\s*{/g); - for (const match of fragmentMatches) { - const fragmentName = match[1]; - const onType = match[2]; - - result.constants.push({ - name: fragmentName, - type: 'fragment', - value: onType - }); - - result.exports.push({ - name: fragmentName, - type: 'fragment' - }); - } - - // Extract operation definitions (queries, mutations, subscriptions) - const operationMatches = content.matchAll(/(query|mutation|subscription)\s+(\w+)(?:\(([^)]*)\))?\s*{/g); - for (const match of operationMatches) { - const operationType = match[1]; - const operationName = match[2]; - const variables = match[3]; - - const parameters = variables ? this.parseOperationVariables(variables) : []; - - result.functions.push({ - name: operationName, - parameters, - returnType: operationType, - async: false, - generator: false, - startLine: 0, - endLine: 0, - operationType - }); - - result.exports.push({ - name: operationName, - type: operationType - }); - } - - } catch (error: any) { - result.errors.push(`Parse error: ${error.message}`); - } - - return result; - } - - private parseFieldArguments(argsStr: string): Array<{ name: string; type: string }> { - if (!argsStr.trim()) return []; - - const args: Array<{ name: string; type: string }> = []; - const argList = argsStr.split(','); - - for (const arg of argList) { - const trimmed = arg.trim(); - const argMatch = trimmed.match(/^(\w+)\s*:\s*(.+)$/); - if (argMatch) { - args.push({ - name: argMatch[1], - type: argMatch[2] - }); - } - } - - return args; - } - - private parseDirectiveArguments(argsStr: string): Array<{ name: string; type: string }> { - return this.parseFieldArguments(argsStr); - } - - private parseOperationVariables(varsStr: string): Array<{ name: string; type: string }> { - if (!varsStr.trim()) return []; - - const vars: Array<{ name: string; type: string }> = []; - const varList = varsStr.split(','); - - for (const varDef of varList) { - const trimmed = varDef.trim(); - const varMatch = trimmed.match(/^\$(\w+)\s*:\s*(.+)$/); - if (varMatch) { - vars.push({ - name: varMatch[1], - type: varMatch[2] - }); - } - } - - return vars; - } +import { ParserResult, ClassInfo} from '../types'; +import { Parser } from '../types'; + +export class GraphQLParser implements Parser { + language = 'GraphQL'; + extensions = ['.graphql', '.gql']; + + parse(content: string, filePath: string): ParserResult { + const result: ParserResult = { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + errors: [] + }; + + try { + const lines = content.split('\n'); + let inType = false; + let currentType: ClassInfo | null = null; + let typeStartLine = 0; + let braceDepth = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + const lineNumber = i + 1; + + // Skip comments + if (trimmed.startsWith('#')) { + continue; + } + + // Track brace depth + for (const char of trimmed) { + if (char === '{') braceDepth++; + if (char === '}') braceDepth--; + } + + // Check if we're exiting a type definition + if (inType && braceDepth === 0 && trimmed.includes('}')) { + if (currentType) { + currentType.endLine = lineNumber; + result.classes.push(currentType); + currentType = null; + } + inType = false; + } + + // Schema definition + if (trimmed.startsWith('schema')) { + result.constants.push({ + name: 'schema', + type: 'schema', + value: trimmed + }); + } + + // Type definitions (including Query, Mutation, Subscription) + if (trimmed.startsWith('type ')) { + const typeMatch = trimmed.match(/^type\s+(\w+)(?:\s+implements\s+(.+))?\s*{?/); + if (typeMatch) { + const typeName = typeMatch[1]; + const implementsStr = typeMatch[2]; + + currentType = { + name: typeName, + methods: [], + properties: [], + extends: undefined, + implements: implementsStr ? implementsStr.split('&').map(s => s.trim()) : [], + startLine: lineNumber, + endLine: lineNumber, + graphqlType: 'type' + }; + + inType = true; + typeStartLine = lineNumber; + + // Special handling for root types + if (typeName === 'Query' || typeName === 'Mutation' || typeName === 'Subscription') { + currentType.isRootType = true; + } + + result.exports.push({ + name: typeName, + type: 'type' + }); + } + } + + // Interface definitions + if (trimmed.startsWith('interface ')) { + const interfaceMatch = trimmed.match(/^interface\s+(\w+)\s*{?/); + if (interfaceMatch) { + const interfaceName = interfaceMatch[1]; + + currentType = { + name: interfaceName, + methods: [], + properties: [], + extends: undefined, + implements: [], + interface: true, + startLine: lineNumber, + endLine: lineNumber, + graphqlType: 'interface' + }; + + inType = true; + typeStartLine = lineNumber; + + result.exports.push({ + name: interfaceName, + type: 'interface' + }); + } + } + + // Input type definitions + if (trimmed.startsWith('input ')) { + const inputMatch = trimmed.match(/^input\s+(\w+)\s*{?/); + if (inputMatch) { + const inputName = inputMatch[1]; + + currentType = { + name: inputName, + methods: [], + properties: [], + extends: undefined, + implements: [], + startLine: lineNumber, + endLine: lineNumber, + graphqlType: 'input' + }; + + inType = true; + typeStartLine = lineNumber; + + result.exports.push({ + name: inputName, + type: 'input' + }); + } + } + + // Union type definitions + if (trimmed.startsWith('union ')) { + const unionMatch = trimmed.match(/^union\s+(\w+)\s*=\s*(.+)/); + if (unionMatch) { + const unionName = unionMatch[1]; + const unionTypes = unionMatch[2].split('|').map(s => s.trim()); + + result.constants.push({ + name: unionName, + type: 'union', + value: unionTypes.join(' | ') + }); + + result.exports.push({ + name: unionName, + type: 'union' + }); + } + } + + // Enum definitions + if (trimmed.startsWith('enum ')) { + const enumMatch = trimmed.match(/^enum\s+(\w+)\s*{?/); + if (enumMatch) { + const enumName = enumMatch[1]; + const enumValues: string[] = []; + + // Collect enum values + let j = i + 1; + while (j < lines.length && !lines[j].includes('}')) { + const enumLine = lines[j].trim(); + if (enumLine && !enumLine.startsWith('#')) { + enumValues.push(enumLine); + } + j++; + } + + result.constants.push({ + name: enumName, + type: 'enum', + value: enumValues.join(', ') + }); + + result.exports.push({ + name: enumName, + type: 'enum' + }); + } + } + + // Scalar definitions + if (trimmed.startsWith('scalar ')) { + const scalarMatch = trimmed.match(/^scalar\s+(\w+)/); + if (scalarMatch) { + const scalarName = scalarMatch[1]; + + result.constants.push({ + name: scalarName, + type: 'scalar', + value: 'custom' + }); + + result.exports.push({ + name: scalarName, + type: 'scalar' + }); + } + } + + // Directive definitions + if (trimmed.startsWith('directive @')) { + const directiveMatch = trimmed.match(/^directive\s+@(\w+)(?:\(([^)]*)\))?\s+on\s+(.+)/); + if (directiveMatch) { + const directiveName = directiveMatch[1]; + const args = directiveMatch[2]; + const locations = directiveMatch[3]; + + const parameters = args ? this.parseDirectiveArguments(args) : []; + + result.functions.push({ + name: `@${directiveName}`, + parameters, + returnType: 'void', + async: false, + generator: false, + startLine: lineNumber, + endLine: lineNumber, + directiveLocations: locations.split('|').map(s => s.trim()) + }); + + result.exports.push({ + name: `@${directiveName}`, + type: 'directive' + }); + } + } + + // Parse fields within types + if (inType && currentType && braceDepth > 0) { + // Skip the opening brace line + if (trimmed === '{') continue; + + // Parse field definitions + const fieldMatch = trimmed.match(/^(\w+)(?:\(([^)]*)\))?\s*:\s*(.+?)(?:\s+@.*)?$/); + if (fieldMatch) { + const fieldName = fieldMatch[1]; + const argsStr = fieldMatch[2]; + const fieldType = fieldMatch[3]; + + if (argsStr) { + // It's a method/resolver (has arguments) + const parameters = this.parseFieldArguments(argsStr); + + if (currentType.isRootType) { + // For root types (Query, Mutation, Subscription), these are essentially functions + result.functions.push({ + name: `${currentType.name}.${fieldName}`, + parameters, + returnType: fieldType, + async: false, + generator: false, + startLine: lineNumber, + endLine: lineNumber, + resolver: true, + parentType: currentType.name + }); + } + + currentType.methods.push({ + name: fieldName, + visibility: 'public', + static: false, + returnType: fieldType, + arguments: parameters + }); + } else { + // It's a field/property + currentType.properties.push({ + name: fieldName, + type: fieldType, + visibility: 'public', + static: false, + required: !fieldType.includes('?') && !fieldType.endsWith('!') + }); + } + } + } + + // Import statements (for schema stitching/federation) + if (trimmed.startsWith('extend ')) { + const extendMatch = trimmed.match(/^extend\s+(type|interface|input|enum|scalar)\s+(\w+)/); + if (extendMatch) { + const extendType = extendMatch[1]; + const extendName = extendMatch[2]; + + result.imports.push({ + source: extendName, + specifiers: [{ name: extendName, alias: extendName }], + type: 'extend' + }); + + result.dependencies.push(extendName); + } + } + } + + // Add any remaining type + if (currentType) { + result.classes.push(currentType); + } + + // Extract fragment definitions + const fragmentMatches = content.matchAll(/fragment\s+(\w+)\s+on\s+(\w+)\s*{/g); + for (const match of fragmentMatches) { + const fragmentName = match[1]; + const onType = match[2]; + + result.constants.push({ + name: fragmentName, + type: 'fragment', + value: onType + }); + + result.exports.push({ + name: fragmentName, + type: 'fragment' + }); + } + + // Extract operation definitions (queries, mutations, subscriptions) + const operationMatches = content.matchAll(/(query|mutation|subscription)\s+(\w+)(?:\(([^)]*)\))?\s*{/g); + for (const match of operationMatches) { + const operationType = match[1]; + const operationName = match[2]; + const variables = match[3]; + + const parameters = variables ? this.parseOperationVariables(variables) : []; + + result.functions.push({ + name: operationName, + parameters, + returnType: operationType, + async: false, + generator: false, + startLine: 0, + endLine: 0, + operationType + }); + + result.exports.push({ + name: operationName, + type: operationType + }); + } + + } catch (error: any) { + result.errors.push(`Parse error: ${error.message}`); + } + + return result; + } + + private parseFieldArguments(argsStr: string): Array<{ name: string; type: string }> { + if (!argsStr.trim()) return []; + + const args: Array<{ name: string; type: string }> = []; + const argList = argsStr.split(','); + + for (const arg of argList) { + const trimmed = arg.trim(); + const argMatch = trimmed.match(/^(\w+)\s*:\s*(.+)$/); + if (argMatch) { + args.push({ + name: argMatch[1], + type: argMatch[2] + }); + } + } + + return args; + } + + private parseDirectiveArguments(argsStr: string): Array<{ name: string; type: string }> { + return this.parseFieldArguments(argsStr); + } + + private parseOperationVariables(varsStr: string): Array<{ name: string; type: string }> { + if (!varsStr.trim()) return []; + + const vars: Array<{ name: string; type: string }> = []; + const varList = varsStr.split(','); + + for (const varDef of varList) { + const trimmed = varDef.trim(); + const varMatch = trimmed.match(/^\$(\w+)\s*:\s*(.+)$/); + if (varMatch) { + vars.push({ + name: varMatch[1], + type: varMatch[2] + }); + } + } + + return vars; + } } \ No newline at end of file diff --git a/src/parsers/index.ts b/src/parsers/index.ts index 05bc539..096a48d 100644 --- a/src/parsers/index.ts +++ b/src/parsers/index.ts @@ -1,51 +1,51 @@ -import { Parser} from '../types'; -import { JavaScriptParser } from './javascript'; -import { TypeScriptParser } from './typescript'; -import { PythonParser } from './python'; -import { GoParser } from './go'; -import { SQLParser } from './sql'; -import { EnhancedGraphQLParser as GraphQLParser } from './graphql-enhanced'; -import { YAMLParser } from './yaml'; -import { AstroParser } from './astro'; - -const parsers: Parser[] = [ - new JavaScriptParser(), - new TypeScriptParser(), - new PythonParser(), // Consolidated Python parser using Tree-sitter - new GoParser(), - new SQLParser(), - new GraphQLParser(), - new YAMLParser(), - new AstroParser() -]; - -const extensionMap = new Map(); - -// Build extension map -for (const parser of parsers) { - for (const ext of parser.extensions) { - extensionMap.set(ext, parser); - } -} - -export function getParser(extension: string): Parser | null { - return extensionMap.get(extension) || null; -} - -export function getSupportedExtensions(): string[] { - return Array.from(extensionMap.keys()); -} - -export function getSupportedLanguages(): string[] { - return Array.from(new Set(parsers.map(p => p.language))); -} - -// Export parser classes -export { JavaScriptParser } from './javascript'; -export { TypeScriptParser } from './typescript'; -export { PythonParser } from './python'; -export { GoParser } from './go'; -export { SQLParser } from './sql'; -export { EnhancedGraphQLParser as GraphQLParser } from './graphql-enhanced'; -export { YAMLParser } from './yaml'; +import { Parser} from '../types'; +import { JavaScriptParser } from './javascript'; +import { TypeScriptParser } from './typescript'; +import { PythonParser } from './python'; +import { GoParser } from './go'; +import { SQLParser } from './sql'; +import { EnhancedGraphQLParser as GraphQLParser } from './graphql-enhanced'; +import { YAMLParser } from './yaml'; +import { AstroParser } from './astro'; + +const parsers: Parser[] = [ + new JavaScriptParser(), + new TypeScriptParser(), + new PythonParser(), // Consolidated Python parser using Tree-sitter + new GoParser(), + new SQLParser(), + new GraphQLParser(), + new YAMLParser(), + new AstroParser() +]; + +const extensionMap = new Map(); + +// Build extension map +for (const parser of parsers) { + for (const ext of parser.extensions) { + extensionMap.set(ext, parser); + } +} + +export function getParser(extension: string): Parser | null { + return extensionMap.get(extension) || null; +} + +export function getSupportedExtensions(): string[] { + return Array.from(extensionMap.keys()); +} + +export function getSupportedLanguages(): string[] { + return Array.from(new Set(parsers.map(p => p.language))); +} + +// Export parser classes +export { JavaScriptParser } from './javascript'; +export { TypeScriptParser } from './typescript'; +export { PythonParser } from './python'; +export { GoParser } from './go'; +export { SQLParser } from './sql'; +export { EnhancedGraphQLParser as GraphQLParser } from './graphql-enhanced'; +export { YAMLParser } from './yaml'; export { AstroParser } from './astro'; \ No newline at end of file diff --git a/src/parsers/javascript.ts b/src/parsers/javascript.ts index 6146844..870fa79 100644 --- a/src/parsers/javascript.ts +++ b/src/parsers/javascript.ts @@ -1,353 +1,353 @@ -import * as parser from '@babel/parser'; -import traverse from '@babel/traverse'; -import * as t from '@babel/types'; -import { ParserResult, FunctionInfo, ClassInfo} from '../types'; -import { Parser } from '../types'; - -export class JavaScriptParser implements Parser { - language = 'JavaScript'; - extensions = ['.js', '.jsx', '.mjs']; - - // For call graph extraction - private allFunctions: Set = new Set(); - private functionCalls: Map> = new Map(); - private currentFunction: string | null = null; - private currentClass: string | null = null; - - parse(content: string, filePath: string): ParserResult { - // Reset call graph state - this.allFunctions.clear(); - this.functionCalls.clear(); - this.currentFunction = null; - this.currentClass = null; - - const result: ParserResult = { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - errors: [] - }; - - try { - const ast = parser.parse(content, { - sourceType: 'unambiguous', - plugins: ['jsx', 'classProperties', 'dynamicImport', 'optionalChaining', 'nullishCoalescingOperator'], - errorRecovery: true - }); - - // First pass: collect all function and method names for call graph - traverse(ast, { - FunctionDeclaration: (path) => { - if (path.node.id) { - this.allFunctions.add(path.node.id.name); - } - }, - FunctionExpression: (path) => { - if (path.node.id) { - this.allFunctions.add(path.node.id.name); - } - }, - ArrowFunctionExpression: (path) => { - const parent = path.parent; - if (t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)) { - this.allFunctions.add(parent.id.name); - } - }, - ClassMethod: (path) => { - if (t.isIdentifier(path.node.key)) { - const methodName = path.node.key.name; - this.allFunctions.add(methodName); - } - } - }); - - const self = this; - - // Second pass: extract everything including call relationships - traverse(ast, { - ImportDeclaration(path) { - const source = path.node.source.value; - const specifiers = path.node.specifiers.map(spec => { - if (spec.type === 'ImportDefaultSpecifier') { - return { name: 'default', alias: spec.local.name }; - } else if (spec.type === 'ImportSpecifier') { - return { - name: spec.imported.type === 'Identifier' ? spec.imported.name : 'default', - alias: spec.local.name - }; - } else { - return { name: '*', alias: spec.local.name }; - } - }); - - result.imports.push({ - source, - specifiers, - type: 'static' - }); - - if (!result.dependencies.includes(source)) { - result.dependencies.push(source); - } - }, - - ExportDefaultDeclaration(path) { - result.exports.push({ - name: 'default', - type: 'default' - }); - }, - - ExportNamedDeclaration(path) { - if (path.node.declaration) { - const decl = path.node.declaration as any; - if (decl.type === 'FunctionDeclaration' && decl.id) { - result.exports.push({ - name: decl.id.name, - type: 'function' - }); - } else if (decl.type === 'ClassDeclaration' && decl.id) { - result.exports.push({ - name: decl.id.name, - type: 'class' - }); - } else if (decl.type === 'VariableDeclaration') { - decl.declarations.forEach((d: any) => { - if (d.id && d.id.type === 'Identifier') { - result.exports.push({ - name: d.id.name, - type: 'variable' - }); - } - }); - } - } else if (path.node.specifiers) { - path.node.specifiers.forEach(spec => { - if (spec.type === 'ExportSpecifier') { - result.exports.push({ - name: spec.exported.type === 'Identifier' ? spec.exported.name : 'default', - type: 'named' - }); - } - }); - } - }, - - FunctionDeclaration(path) { - const func = path.node; - if (func.id) { - self.currentFunction = func.id.name; - self.functionCalls.set(func.id.name, new Set()); - - const funcInfo: FunctionInfo = { - name: func.id.name, - parameters: func.params.map(param => self.extractParam(param)), - returnType: 'any', - async: func.async || false, - generator: func.generator || false, - startLine: path.node.loc?.start.line || 0, - endLine: path.node.loc?.end.line || 0, - calls: [] // Will be populated after traversal - }; - result.functions.push(funcInfo); - } - }, - - FunctionExpression: { - enter(path) { - const func = path.node; - if (func.id) { - self.currentFunction = func.id.name; - self.functionCalls.set(func.id.name, new Set()); - - const funcInfo: FunctionInfo = { - name: func.id.name, - parameters: func.params.map(param => self.extractParam(param)), - returnType: 'any', - async: func.async || false, - generator: func.generator || false, - startLine: path.node.loc?.start.line || 0, - endLine: path.node.loc?.end.line || 0, - calls: [] - }; - result.functions.push(funcInfo); - } - }, - exit() { - self.currentFunction = null; - } - }, - - ArrowFunctionExpression: { - enter(path) { - const parent = path.parent; - if (parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier') { - const funcName = parent.id.name; - self.currentFunction = funcName; - self.functionCalls.set(funcName, new Set()); - - const funcInfo: FunctionInfo = { - name: funcName, - parameters: path.node.params.map(param => self.extractParam(param)), - returnType: 'any', - async: path.node.async || false, - generator: false, - startLine: path.node.loc?.start.line || 0, - endLine: path.node.loc?.end.line || 0, - calls: [] - }; - result.functions.push(funcInfo); - } - }, - exit() { - self.currentFunction = null; - } - }, - - // Track function calls for call graph - CallExpression(path) { - if (self.currentFunction || self.currentClass) { - const calleeName = self.extractCalleeName(path.node.callee); - if (calleeName && self.allFunctions.has(calleeName)) { - const currentContext = self.currentClass && self.currentFunction - ? `${self.currentClass}.${self.currentFunction}` - : self.currentFunction || self.currentClass || ''; - - if (currentContext && self.functionCalls.has(currentContext)) { - self.functionCalls.get(currentContext)!.add(calleeName); - } else if (self.currentFunction && self.functionCalls.has(self.currentFunction)) { - self.functionCalls.get(self.currentFunction)!.add(calleeName); - } - } - } - }, - - ClassDeclaration: { - enter(path) { - const cls = path.node; - if (cls.id) { - self.currentClass = cls.id.name; - - const methods: any[] = []; - const properties: any[] = []; - - if (cls.body && cls.body.body) { - cls.body.body.forEach((member: any) => { - if (member.type === 'MethodDefinition' || member.type === 'ClassMethod') { - const methodName = member.key.type === 'Identifier' ? member.key.name : 'unknown'; - const methodFullName = `${cls.id!.name}.${methodName}`; - self.functionCalls.set(methodFullName, new Set()); - - methods.push({ - name: methodName, - visibility: member.accessibility || 'public', - static: member.static || false, - async: member.async || false, - arguments: member.value ? member.value.params.map((p: any) => self.extractParam(p)) : [], - calls: [] // Will be populated after traversal - }); - } else if (member.type === 'PropertyDefinition' || member.type === 'ClassProperty') { - properties.push({ - name: member.key.type === 'Identifier' ? member.key.name : 'unknown', - type: 'any', - visibility: member.accessibility || 'public', - static: member.static || false - }); - } - }); - } - - const classInfo: ClassInfo = { - name: cls.id.name, - methods, - properties, - extends: cls.superClass && cls.superClass.type === 'Identifier' ? cls.superClass.name : undefined, - implements: [], - startLine: path.node.loc?.start.line || 0, - endLine: path.node.loc?.end.line || 0 - }; - - result.classes.push(classInfo); - } - }, - exit() { - self.currentClass = null; - } - }, - - VariableDeclaration(path) { - path.node.declarations.forEach(decl => { - if (decl.id.type === 'Identifier' && path.node.kind === 'const') { - result.constants.push({ - name: decl.id.name, - type: 'const' - }); - } - }); - } - }); - - // Third pass: populate the calls arrays - result.functions.forEach(func => { - const calls = this.functionCalls.get(func.name); - if (calls && calls.size > 0) { - func.calls = Array.from(calls); - } - }); - - // Also update class methods with their calls - result.classes.forEach(cls => { - cls.methods.forEach(method => { - const methodFullName = `${cls.name}.${method.name}`; - const calls = this.functionCalls.get(methodFullName); - if (calls && calls.size > 0) { - method.calls = Array.from(calls); - } - }); - }); - - } catch (error: any) { - result.errors.push({ - message: error.message || 'Unknown parsing error', - line: error.loc?.line || 0 - }); - } - - return result; - } - - private extractCalleeName(callee: any): string | null { - if (t.isIdentifier(callee)) { - return callee.name; - } else if (t.isMemberExpression(callee)) { - if (t.isIdentifier(callee.property)) { - return callee.property.name; - } else if (t.isStringLiteral(callee.property)) { - return callee.property.value; - } - } - return null; - } - - private extractParam(param: any): { name: string; type: string; default?: string } { - if (param.type === 'Identifier') { - return { name: param.name, type: 'any' }; - } else if (param.type === 'AssignmentPattern') { - return { - name: param.left.type === 'Identifier' ? param.left.name : 'unknown', - type: 'any', - default: 'default' - }; - } else if (param.type === 'RestElement') { - return { name: '...rest', type: 'any[]' }; - } else if (param.type === 'ObjectPattern') { - return { name: '{...}', type: 'object' }; - } else if (param.type === 'ArrayPattern') { - return { name: '[...]', type: 'array' }; - } - return { name: 'unknown', type: 'any' }; - } +import * as parser from '@babel/parser'; +import traverse from '@babel/traverse'; +import * as t from '@babel/types'; +import { ParserResult, FunctionInfo, ClassInfo} from '../types'; +import { Parser } from '../types'; + +export class JavaScriptParser implements Parser { + language = 'JavaScript'; + extensions = ['.js', '.jsx', '.mjs']; + + // For call graph extraction + private allFunctions: Set = new Set(); + private functionCalls: Map> = new Map(); + private currentFunction: string | null = null; + private currentClass: string | null = null; + + parse(content: string, filePath: string): ParserResult { + // Reset call graph state + this.allFunctions.clear(); + this.functionCalls.clear(); + this.currentFunction = null; + this.currentClass = null; + + const result: ParserResult = { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + errors: [] + }; + + try { + const ast = parser.parse(content, { + sourceType: 'unambiguous', + plugins: ['jsx', 'classProperties', 'dynamicImport', 'optionalChaining', 'nullishCoalescingOperator'], + errorRecovery: true + }); + + // First pass: collect all function and method names for call graph + traverse(ast, { + FunctionDeclaration: (path) => { + if (path.node.id) { + this.allFunctions.add(path.node.id.name); + } + }, + FunctionExpression: (path) => { + if (path.node.id) { + this.allFunctions.add(path.node.id.name); + } + }, + ArrowFunctionExpression: (path) => { + const parent = path.parent; + if (t.isVariableDeclarator(parent) && t.isIdentifier(parent.id)) { + this.allFunctions.add(parent.id.name); + } + }, + ClassMethod: (path) => { + if (t.isIdentifier(path.node.key)) { + const methodName = path.node.key.name; + this.allFunctions.add(methodName); + } + } + }); + + const self = this; + + // Second pass: extract everything including call relationships + traverse(ast, { + ImportDeclaration(path) { + const source = path.node.source.value; + const specifiers = path.node.specifiers.map(spec => { + if (spec.type === 'ImportDefaultSpecifier') { + return { name: 'default', alias: spec.local.name }; + } else if (spec.type === 'ImportSpecifier') { + return { + name: spec.imported.type === 'Identifier' ? spec.imported.name : 'default', + alias: spec.local.name + }; + } else { + return { name: '*', alias: spec.local.name }; + } + }); + + result.imports.push({ + source, + specifiers, + type: 'static' + }); + + if (!result.dependencies.includes(source)) { + result.dependencies.push(source); + } + }, + + ExportDefaultDeclaration(path) { + result.exports.push({ + name: 'default', + type: 'default' + }); + }, + + ExportNamedDeclaration(path) { + if (path.node.declaration) { + const decl = path.node.declaration as any; + if (decl.type === 'FunctionDeclaration' && decl.id) { + result.exports.push({ + name: decl.id.name, + type: 'function' + }); + } else if (decl.type === 'ClassDeclaration' && decl.id) { + result.exports.push({ + name: decl.id.name, + type: 'class' + }); + } else if (decl.type === 'VariableDeclaration') { + decl.declarations.forEach((d: any) => { + if (d.id && d.id.type === 'Identifier') { + result.exports.push({ + name: d.id.name, + type: 'variable' + }); + } + }); + } + } else if (path.node.specifiers) { + path.node.specifiers.forEach(spec => { + if (spec.type === 'ExportSpecifier') { + result.exports.push({ + name: spec.exported.type === 'Identifier' ? spec.exported.name : 'default', + type: 'named' + }); + } + }); + } + }, + + FunctionDeclaration(path) { + const func = path.node; + if (func.id) { + self.currentFunction = func.id.name; + self.functionCalls.set(func.id.name, new Set()); + + const funcInfo: FunctionInfo = { + name: func.id.name, + parameters: func.params.map(param => self.extractParam(param)), + returnType: 'any', + async: func.async || false, + generator: func.generator || false, + startLine: path.node.loc?.start.line || 0, + endLine: path.node.loc?.end.line || 0, + calls: [] // Will be populated after traversal + }; + result.functions.push(funcInfo); + } + }, + + FunctionExpression: { + enter(path) { + const func = path.node; + if (func.id) { + self.currentFunction = func.id.name; + self.functionCalls.set(func.id.name, new Set()); + + const funcInfo: FunctionInfo = { + name: func.id.name, + parameters: func.params.map(param => self.extractParam(param)), + returnType: 'any', + async: func.async || false, + generator: func.generator || false, + startLine: path.node.loc?.start.line || 0, + endLine: path.node.loc?.end.line || 0, + calls: [] + }; + result.functions.push(funcInfo); + } + }, + exit() { + self.currentFunction = null; + } + }, + + ArrowFunctionExpression: { + enter(path) { + const parent = path.parent; + if (parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier') { + const funcName = parent.id.name; + self.currentFunction = funcName; + self.functionCalls.set(funcName, new Set()); + + const funcInfo: FunctionInfo = { + name: funcName, + parameters: path.node.params.map(param => self.extractParam(param)), + returnType: 'any', + async: path.node.async || false, + generator: false, + startLine: path.node.loc?.start.line || 0, + endLine: path.node.loc?.end.line || 0, + calls: [] + }; + result.functions.push(funcInfo); + } + }, + exit() { + self.currentFunction = null; + } + }, + + // Track function calls for call graph + CallExpression(path) { + if (self.currentFunction || self.currentClass) { + const calleeName = self.extractCalleeName(path.node.callee); + if (calleeName && self.allFunctions.has(calleeName)) { + const currentContext = self.currentClass && self.currentFunction + ? `${self.currentClass}.${self.currentFunction}` + : self.currentFunction || self.currentClass || ''; + + if (currentContext && self.functionCalls.has(currentContext)) { + self.functionCalls.get(currentContext)!.add(calleeName); + } else if (self.currentFunction && self.functionCalls.has(self.currentFunction)) { + self.functionCalls.get(self.currentFunction)!.add(calleeName); + } + } + } + }, + + ClassDeclaration: { + enter(path) { + const cls = path.node; + if (cls.id) { + self.currentClass = cls.id.name; + + const methods: any[] = []; + const properties: any[] = []; + + if (cls.body && cls.body.body) { + cls.body.body.forEach((member: any) => { + if (member.type === 'MethodDefinition' || member.type === 'ClassMethod') { + const methodName = member.key.type === 'Identifier' ? member.key.name : 'unknown'; + const methodFullName = `${cls.id!.name}.${methodName}`; + self.functionCalls.set(methodFullName, new Set()); + + methods.push({ + name: methodName, + visibility: member.accessibility || 'public', + static: member.static || false, + async: member.async || false, + arguments: member.value ? member.value.params.map((p: any) => self.extractParam(p)) : [], + calls: [] // Will be populated after traversal + }); + } else if (member.type === 'PropertyDefinition' || member.type === 'ClassProperty') { + properties.push({ + name: member.key.type === 'Identifier' ? member.key.name : 'unknown', + type: 'any', + visibility: member.accessibility || 'public', + static: member.static || false + }); + } + }); + } + + const classInfo: ClassInfo = { + name: cls.id.name, + methods, + properties, + extends: cls.superClass && cls.superClass.type === 'Identifier' ? cls.superClass.name : undefined, + implements: [], + startLine: path.node.loc?.start.line || 0, + endLine: path.node.loc?.end.line || 0 + }; + + result.classes.push(classInfo); + } + }, + exit() { + self.currentClass = null; + } + }, + + VariableDeclaration(path) { + path.node.declarations.forEach(decl => { + if (decl.id.type === 'Identifier' && path.node.kind === 'const') { + result.constants.push({ + name: decl.id.name, + type: 'const' + }); + } + }); + } + }); + + // Third pass: populate the calls arrays + result.functions.forEach(func => { + const calls = this.functionCalls.get(func.name); + if (calls && calls.size > 0) { + func.calls = Array.from(calls); + } + }); + + // Also update class methods with their calls + result.classes.forEach(cls => { + cls.methods.forEach(method => { + const methodFullName = `${cls.name}.${method.name}`; + const calls = this.functionCalls.get(methodFullName); + if (calls && calls.size > 0) { + method.calls = Array.from(calls); + } + }); + }); + + } catch (error: any) { + result.errors.push({ + message: error.message || 'Unknown parsing error', + line: error.loc?.line || 0 + }); + } + + return result; + } + + private extractCalleeName(callee: any): string | null { + if (t.isIdentifier(callee)) { + return callee.name; + } else if (t.isMemberExpression(callee)) { + if (t.isIdentifier(callee.property)) { + return callee.property.name; + } else if (t.isStringLiteral(callee.property)) { + return callee.property.value; + } + } + return null; + } + + private extractParam(param: any): { name: string; type: string; default?: string } { + if (param.type === 'Identifier') { + return { name: param.name, type: 'any' }; + } else if (param.type === 'AssignmentPattern') { + return { + name: param.left.type === 'Identifier' ? param.left.name : 'unknown', + type: 'any', + default: 'default' + }; + } else if (param.type === 'RestElement') { + return { name: '...rest', type: 'any[]' }; + } else if (param.type === 'ObjectPattern') { + return { name: '{...}', type: 'object' }; + } else if (param.type === 'ArrayPattern') { + return { name: '[...]', type: 'array' }; + } + return { name: 'unknown', type: 'any' }; + } } \ No newline at end of file diff --git a/src/parsers/python.ts b/src/parsers/python.ts index d19d8ca..fa29175 100644 --- a/src/parsers/python.ts +++ b/src/parsers/python.ts @@ -1,559 +1,559 @@ -const TreeSitterParser = require('tree-sitter'); -const Python = require('tree-sitter-python'); -import { Parser, ParserResult, FunctionInfo, ClassInfo} from '../types'; - -/** - * Python parser using Tree-sitter for accurate AST parsing - * Handles all Python 3 language features including: - * - Type annotations - * - Decorators - * - Async/await - * - Generators - * - Comprehensions - * - Context managers - * - Metaclasses - * - Abstract base classes - * - * This is the consolidated Python parser, combining the best features - * from previous implementations using Tree-sitter for accuracy. - */ -export class PythonParser implements Parser { - language = 'Python'; - extensions = ['.py', '.pyw', '.pyi']; - private parser: any; - - constructor() { - this.parser = new TreeSitterParser(); - this.parser.setLanguage(Python); - } - - parse(content: string, filePath: string): ParserResult { - const result: ParserResult = { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - errors: [] - }; - - try { - const tree = this.parser.parse(content); - const rootNode = tree.rootNode; - - // Check for syntax errors - if (rootNode.hasError) { - result.errors.push('Syntax error detected in Python file'); - } - - // Process all top-level nodes - this.processNode(rootNode, result, content); - - // Extract __all__ exports if present - this.extractAllExports(content, result); - - } catch (error: any) { - result.errors.push(`Parse error: ${error.message}`); - } - - return result; - } - - private processNode( - node: any, - result: ParserResult, - content: string, - parentClass?: ClassInfo - ): void { - switch (node.type) { - case 'module': - // Process all children of the module - for (const child of node.children) { - this.processNode(child, result, content); - } - break; - - case 'import_statement': - this.processImport(node, result, content); - break; - - case 'import_from_statement': - this.processFromImport(node, result, content); - break; - - case 'function_definition': - this.processFunction(node, result, content, parentClass); - break; - - case 'class_definition': - this.processClass(node, result, content); - break; - - case 'decorated_definition': - this.processDecoratedDefinition(node, result, content, parentClass); - break; - - case 'expression_statement': - this.processExpression(node, result, content, parentClass); - break; - - case 'assignment': - this.processAssignment(node, result, content, parentClass); - break; - - default: - // Recursively process children for other node types - for (const child of node.children) { - this.processNode(child, result, content, parentClass); - } - } - } - - private processImport(node: any, result: ParserResult, content: string): void { - const importNode = node.childForFieldName('name'); - if (!importNode) return; - - const moduleName = content.substring(importNode.startIndex, importNode.endIndex); - - // Check for aliases - const aliasNode = node.childForFieldName('alias'); - const alias = aliasNode ? - content.substring(aliasNode.startIndex, aliasNode.endIndex) : - moduleName.split('.').pop() || moduleName; - - result.imports.push({ - source: moduleName, - specifiers: [{ name: '*', alias }], - type: 'static' - }); - - if (moduleName.startsWith('.')) { - result.dependencies.push(moduleName); - } - } - - private processFromImport(node: any, result: ParserResult, content: string): void { - const moduleNode = node.childForFieldName('module_name'); - if (!moduleNode) return; - - const moduleName = content.substring(moduleNode.startIndex, moduleNode.endIndex); - const specifiers: Array<{ name: string; alias: string }> = []; - - // Process imported names - const importList = node.children.find(child => - child.type === 'dotted_name' || - child.type === 'aliased_import' || - child.type === 'identifier' || - child.type === 'import_from_statement' - ); - - if (importList) { - if (importList.type === 'identifier' && content.substring(importList.startIndex, importList.endIndex) === '*') { - specifiers.push({ name: '*', alias: '*' }); - } else { - // Process individual imports - for (const child of node.children) { - if (child.type === 'aliased_import') { - const nameNode = child.childForFieldName('name'); - const aliasNode = child.childForFieldName('alias'); - if (nameNode) { - const name = content.substring(nameNode.startIndex, nameNode.endIndex); - const alias = aliasNode ? - content.substring(aliasNode.startIndex, aliasNode.endIndex) : - name; - specifiers.push({ name, alias }); - } - } else if (child.type === 'dotted_name' || child.type === 'identifier') { - const name = content.substring(child.startIndex, child.endIndex); - if (name !== 'from' && name !== 'import' && name !== moduleName) { - specifiers.push({ name, alias: name }); - } - } - } - } - } - - if (specifiers.length > 0) { - result.imports.push({ - source: moduleName, - specifiers, - type: 'static' - }); - - if (moduleName.startsWith('.')) { - result.dependencies.push(moduleName); - } - } - } - - private processFunction( - node: any, - result: ParserResult, - content: string, - parentClass?: ClassInfo, - decorators: string[] = [] - ): void { - const nameNode = node.childForFieldName('name'); - if (!nameNode) return; - - const funcName = content.substring(nameNode.startIndex, nameNode.endIndex); - - // Extract parameters - const parameters = this.extractParameters(node, content); - - // Extract return type - const returnType = this.extractReturnType(node, content); - - // Check if async - const isAsync = node.children.some(child => - child.type === 'async' || (child.type === 'identifier' && content.substring(child.startIndex, child.endIndex) === 'async') - ); - - // Check if generator - const isGenerator = this.checkIfGenerator(node, content); - - const funcInfo: FunctionInfo = { - name: funcName, - parameters, - returnType, - async: isAsync, - generator: isGenerator, - decorators, - startLine: node.startPosition.row + 1, - endLine: node.endPosition.row + 1 - }; - - if (parentClass) { - // It's a method - const visibility = funcName.startsWith('_') - ? (funcName.startsWith('__') && !funcName.endsWith('__') ? 'private' : 'protected') - : 'public'; - - const isStatic = decorators.includes('staticmethod'); - const isClassMethod = decorators.includes('classmethod'); - const isProperty = decorators.includes('property'); - - parentClass.methods.push({ - name: funcName, - visibility, - static: isStatic, - classmethod: isClassMethod, - property: isProperty, - async: isAsync, - returnType, - decorators - }); - } else { - // It's a module-level function - result.functions.push(funcInfo); - - // Top-level functions are exports - result.exports.push({ - name: funcName, - type: 'function', - line: node.startPosition.row + 1 - }); - } - } - - private processClass(node: any, result: ParserResult, content: string, decorators: string[] = []): void { - const nameNode = node.childForFieldName('name'); - if (!nameNode) return; - - const className = content.substring(nameNode.startIndex, nameNode.endIndex); - - // Extract base classes - const bases = this.extractBaseClasses(node, content); - - // Extract metaclass - const metaclass = this.extractMetaclass(node, content); - - const classInfo: ClassInfo = { - name: className, - methods: [], - properties: [], - extends: bases[0], - implements: bases.slice(1), - decorators, - metaclass, - startLine: node.startPosition.row + 1, - endLine: node.endPosition.row + 1 - }; - - // Process class body - const bodyNode = node.childForFieldName('body'); - if (bodyNode) { - for (const child of bodyNode.children) { - this.processNode(child, result, content, classInfo); - } - } - - result.classes.push(classInfo); - - // Top-level classes are exports - result.exports.push({ - name: className, - type: 'class', - line: node.startPosition.row + 1 - }); - } - - private processDecoratedDefinition( - node: any, - result: ParserResult, - content: string, - parentClass?: ClassInfo - ): void { - // Extract decorators - const decorators: string[] = []; - for (const child of node.children) { - if (child.type === 'decorator') { - const decoratorText = content.substring(child.startIndex + 1, child.endIndex); // Skip @ - decorators.push(decoratorText.trim()); - } - } - - // Process the decorated definition - const definitionNode = node.children.find(child => - child.type === 'function_definition' || child.type === 'class_definition' - ); - - if (definitionNode) { - if (definitionNode.type === 'function_definition') { - this.processFunction(definitionNode, result, content, parentClass, decorators); - } else if (definitionNode.type === 'class_definition') { - this.processClass(definitionNode, result, content, decorators); - } - } - } - - private processExpression( - node: any, - result: ParserResult, - content: string, - parentClass?: ClassInfo - ): void { - // Check for docstrings or other expressions - const child = node.firstChild; - if (child && child.type === 'assignment') { - this.processAssignment(child, result, content, parentClass); - } - } - - private processAssignment( - node: any, - result: ParserResult, - content: string, - parentClass?: ClassInfo - ): void { - const leftNode = node.childForFieldName('left'); - const rightNode = node.childForFieldName('right'); - - if (!leftNode || !rightNode) return; - - const varName = content.substring(leftNode.startIndex, leftNode.endIndex); - const value = content.substring(rightNode.startIndex, rightNode.endIndex); - - // Check for type annotation - let type = 'any'; - const typeNode = node.childForFieldName('type'); - if (typeNode) { - type = content.substring(typeNode.startIndex, typeNode.endIndex); - } - - if (parentClass) { - // It's a class variable - const visibility = varName.startsWith('_') - ? (varName.startsWith('__') ? 'private' : 'protected') - : 'public'; - - parentClass.properties.push({ - name: varName, - type, - visibility, - static: true, // Class-level variables are static - value - }); - } else if (this.isConstant(varName)) { - // It's a module-level constant - result.constants.push({ - name: varName, - type, - value, - line: node.startPosition.row + 1 - }); - - result.exports.push({ - name: varName, - type: 'variable', - line: node.startPosition.row + 1 - }); - } - } - - private extractParameters(node: any, content: string): Array<{ name: string; type: string; default?: string }> { - const params: Array<{ name: string; type: string; default?: string }> = []; - const paramsNode = node.childForFieldName('parameters'); - - if (!paramsNode) return params; - - for (const child of paramsNode.children) { - if (child.type === 'identifier' || child.type === 'typed_parameter' || - child.type === 'default_parameter' || child.type === 'typed_default_parameter' || - child.type === 'list_splat_pattern' || child.type === 'dictionary_splat_pattern') { - - const paramName = this.extractParameterName(child, content); - const paramType = this.extractParameterType(child, content); - const paramDefault = this.extractParameterDefault(child, content); - - if (paramName && paramName !== 'self' && paramName !== 'cls') { - params.push({ - name: paramName, - type: paramType || 'any', - ...(paramDefault && { default: paramDefault }) - }); - } - } - } - - return params; - } - - private extractParameterName(node: any, content: string): string { - if (node.type === 'identifier') { - return content.substring(node.startIndex, node.endIndex); - } - - const nameNode = node.childForFieldName('name') || - node.children.find(child => child.type === 'identifier'); - - if (nameNode) { - return content.substring(nameNode.startIndex, nameNode.endIndex); - } - - // Handle splat patterns - if (node.type === 'list_splat_pattern') { - const identNode = node.children.find(child => child.type === 'identifier'); - if (identNode) { - return '*' + content.substring(identNode.startIndex, identNode.endIndex); - } - } else if (node.type === 'dictionary_splat_pattern') { - const identNode = node.children.find(child => child.type === 'identifier'); - if (identNode) { - return '**' + content.substring(identNode.startIndex, identNode.endIndex); - } - } - - return ''; - } - - private extractParameterType(node: any, content: string): string | undefined { - const typeNode = node.childForFieldName('type'); - if (typeNode) { - return content.substring(typeNode.startIndex, typeNode.endIndex); - } - return undefined; - } - - private extractParameterDefault(node: any, content: string): string | undefined { - const valueNode = node.childForFieldName('value'); - if (valueNode) { - return content.substring(valueNode.startIndex, valueNode.endIndex); - } - return undefined; - } - - private extractReturnType(node: any, content: string): string { - const returnTypeNode = node.childForFieldName('return_type'); - if (returnTypeNode) { - return content.substring(returnTypeNode.startIndex, returnTypeNode.endIndex); - } - return 'any'; - } - - private extractBaseClasses(node: any, content: string): string[] { - const bases: string[] = []; - const superclassesNode = node.childForFieldName('superclasses'); - - if (superclassesNode) { - for (const child of superclassesNode.children) { - if (child.type === 'identifier' || child.type === 'attribute' || child.type === 'subscript') { - const baseClass = content.substring(child.startIndex, child.endIndex); - if (baseClass !== '(' && baseClass !== ')' && baseClass !== ',') { - bases.push(baseClass); - } - } - } - } - - return bases; - } - - private extractMetaclass(node: any, content: string): string | undefined { - const superclassesNode = node.childForFieldName('superclasses'); - - if (superclassesNode) { - for (const child of superclassesNode.children) { - if (child.type === 'keyword_argument') { - const nameNode = child.childForFieldName('name'); - const valueNode = child.childForFieldName('value'); - - if (nameNode && valueNode) { - const name = content.substring(nameNode.startIndex, nameNode.endIndex); - if (name === 'metaclass') { - return content.substring(valueNode.startIndex, valueNode.endIndex); - } - } - } - } - } - - return undefined; - } - - private checkIfGenerator(node: any, content: string): boolean { - const bodyNode = node.childForFieldName('body'); - if (!bodyNode) return false; - - // Recursively check for yield statements - return this.hasYieldStatement(bodyNode); - } - - private hasYieldStatement(node: any): boolean { - if (node.type === 'yield' || node.type === 'yield_statement' || node.type === 'yield_from_statement') { - return true; - } - - for (const child of node.children) { - if (this.hasYieldStatement(child)) { - return true; - } - } - - return false; - } - - private isConstant(name: string): boolean { - return /^[A-Z_][A-Z0-9_]*$/.test(name); - } - - private extractAllExports(content: string, result: ParserResult): void { - // Look for __all__ definition - const allMatch = content.match(/__all__\s*=\s*\[([^\]]+)\]/); - if (allMatch) { - const exports = allMatch[1].match(/['"]([^'"]+)['"]/g); - if (exports) { - for (const exp of exports) { - const name = exp.replace(/['"]/g, ''); - if (!result.exports.find(e => e.name === name)) { - result.exports.push({ - name, - type: 'variable' - }); - } - } - } - } - } +const TreeSitterParser = require('tree-sitter'); +const Python = require('tree-sitter-python'); +import { Parser, ParserResult, FunctionInfo, ClassInfo} from '../types'; + +/** + * Python parser using Tree-sitter for accurate AST parsing + * Handles all Python 3 language features including: + * - Type annotations + * - Decorators + * - Async/await + * - Generators + * - Comprehensions + * - Context managers + * - Metaclasses + * - Abstract base classes + * + * This is the consolidated Python parser, combining the best features + * from previous implementations using Tree-sitter for accuracy. + */ +export class PythonParser implements Parser { + language = 'Python'; + extensions = ['.py', '.pyw', '.pyi']; + private parser: any; + + constructor() { + this.parser = new TreeSitterParser(); + this.parser.setLanguage(Python); + } + + parse(content: string, filePath: string): ParserResult { + const result: ParserResult = { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + errors: [] + }; + + try { + const tree = this.parser.parse(content); + const rootNode = tree.rootNode; + + // Check for syntax errors + if (rootNode.hasError) { + result.errors.push('Syntax error detected in Python file'); + } + + // Process all top-level nodes + this.processNode(rootNode, result, content); + + // Extract __all__ exports if present + this.extractAllExports(content, result); + + } catch (error: any) { + result.errors.push(`Parse error: ${error.message}`); + } + + return result; + } + + private processNode( + node: any, + result: ParserResult, + content: string, + parentClass?: ClassInfo + ): void { + switch (node.type) { + case 'module': + // Process all children of the module + for (const child of node.children) { + this.processNode(child, result, content); + } + break; + + case 'import_statement': + this.processImport(node, result, content); + break; + + case 'import_from_statement': + this.processFromImport(node, result, content); + break; + + case 'function_definition': + this.processFunction(node, result, content, parentClass); + break; + + case 'class_definition': + this.processClass(node, result, content); + break; + + case 'decorated_definition': + this.processDecoratedDefinition(node, result, content, parentClass); + break; + + case 'expression_statement': + this.processExpression(node, result, content, parentClass); + break; + + case 'assignment': + this.processAssignment(node, result, content, parentClass); + break; + + default: + // Recursively process children for other node types + for (const child of node.children) { + this.processNode(child, result, content, parentClass); + } + } + } + + private processImport(node: any, result: ParserResult, content: string): void { + const importNode = node.childForFieldName('name'); + if (!importNode) return; + + const moduleName = content.substring(importNode.startIndex, importNode.endIndex); + + // Check for aliases + const aliasNode = node.childForFieldName('alias'); + const alias = aliasNode ? + content.substring(aliasNode.startIndex, aliasNode.endIndex) : + moduleName.split('.').pop() || moduleName; + + result.imports.push({ + source: moduleName, + specifiers: [{ name: '*', alias }], + type: 'static' + }); + + if (moduleName.startsWith('.')) { + result.dependencies.push(moduleName); + } + } + + private processFromImport(node: any, result: ParserResult, content: string): void { + const moduleNode = node.childForFieldName('module_name'); + if (!moduleNode) return; + + const moduleName = content.substring(moduleNode.startIndex, moduleNode.endIndex); + const specifiers: Array<{ name: string; alias: string }> = []; + + // Process imported names + const importList = node.children.find((child: any) => + child.type === 'dotted_name' || + child.type === 'aliased_import' || + child.type === 'identifier' || + child.type === 'import_from_statement' + ); + + if (importList) { + if (importList.type === 'identifier' && content.substring(importList.startIndex, importList.endIndex) === '*') { + specifiers.push({ name: '*', alias: '*' }); + } else { + // Process individual imports + for (const child of node.children) { + if (child.type === 'aliased_import') { + const nameNode = child.childForFieldName('name'); + const aliasNode = child.childForFieldName('alias'); + if (nameNode) { + const name = content.substring(nameNode.startIndex, nameNode.endIndex); + const alias = aliasNode ? + content.substring(aliasNode.startIndex, aliasNode.endIndex) : + name; + specifiers.push({ name, alias }); + } + } else if (child.type === 'dotted_name' || child.type === 'identifier') { + const name = content.substring(child.startIndex, child.endIndex); + if (name !== 'from' && name !== 'import' && name !== moduleName) { + specifiers.push({ name, alias: name }); + } + } + } + } + } + + if (specifiers.length > 0) { + result.imports.push({ + source: moduleName, + specifiers, + type: 'static' + }); + + if (moduleName.startsWith('.')) { + result.dependencies.push(moduleName); + } + } + } + + private processFunction( + node: any, + result: ParserResult, + content: string, + parentClass?: ClassInfo, + decorators: string[] = [] + ): void { + const nameNode = node.childForFieldName('name'); + if (!nameNode) return; + + const funcName = content.substring(nameNode.startIndex, nameNode.endIndex); + + // Extract parameters + const parameters = this.extractParameters(node, content); + + // Extract return type + const returnType = this.extractReturnType(node, content); + + // Check if async + const isAsync = node.children.some((child: any) => + child.type === 'async' || (child.type === 'identifier' && content.substring(child.startIndex, child.endIndex) === 'async') + ); + + // Check if generator + const isGenerator = this.checkIfGenerator(node, content); + + const funcInfo: FunctionInfo = { + name: funcName, + parameters, + returnType, + async: isAsync, + generator: isGenerator, + decorators, + startLine: node.startPosition.row + 1, + endLine: node.endPosition.row + 1 + }; + + if (parentClass) { + // It's a method + const visibility = funcName.startsWith('_') + ? (funcName.startsWith('__') && !funcName.endsWith('__') ? 'private' : 'protected') + : 'public'; + + const isStatic = decorators.includes('staticmethod'); + const isClassMethod = decorators.includes('classmethod'); + const isProperty = decorators.includes('property'); + + parentClass.methods.push({ + name: funcName, + visibility, + static: isStatic, + classmethod: isClassMethod, + property: isProperty, + async: isAsync, + returnType, + decorators + }); + } else { + // It's a module-level function + result.functions.push(funcInfo); + + // Top-level functions are exports + result.exports.push({ + name: funcName, + type: 'function', + line: node.startPosition.row + 1 + }); + } + } + + private processClass(node: any, result: ParserResult, content: string, decorators: string[] = []): void { + const nameNode = node.childForFieldName('name'); + if (!nameNode) return; + + const className = content.substring(nameNode.startIndex, nameNode.endIndex); + + // Extract base classes + const bases = this.extractBaseClasses(node, content); + + // Extract metaclass + const metaclass = this.extractMetaclass(node, content); + + const classInfo: ClassInfo = { + name: className, + methods: [], + properties: [], + extends: bases[0], + implements: bases.slice(1), + decorators, + metaclass, + startLine: node.startPosition.row + 1, + endLine: node.endPosition.row + 1 + }; + + // Process class body + const bodyNode = node.childForFieldName('body'); + if (bodyNode) { + for (const child of bodyNode.children) { + this.processNode(child, result, content, classInfo); + } + } + + result.classes.push(classInfo); + + // Top-level classes are exports + result.exports.push({ + name: className, + type: 'class', + line: node.startPosition.row + 1 + }); + } + + private processDecoratedDefinition( + node: any, + result: ParserResult, + content: string, + parentClass?: ClassInfo + ): void { + // Extract decorators + const decorators: string[] = []; + for (const child of node.children) { + if (child.type === 'decorator') { + const decoratorText = content.substring(child.startIndex + 1, child.endIndex); // Skip @ + decorators.push(decoratorText.trim()); + } + } + + // Process the decorated definition + const definitionNode = node.children.find((child: any) => + child.type === 'function_definition' || child.type === 'class_definition' + ); + + if (definitionNode) { + if (definitionNode.type === 'function_definition') { + this.processFunction(definitionNode, result, content, parentClass, decorators); + } else if (definitionNode.type === 'class_definition') { + this.processClass(definitionNode, result, content, decorators); + } + } + } + + private processExpression( + node: any, + result: ParserResult, + content: string, + parentClass?: ClassInfo + ): void { + // Check for docstrings or other expressions + const child = node.firstChild; + if (child && child.type === 'assignment') { + this.processAssignment(child, result, content, parentClass); + } + } + + private processAssignment( + node: any, + result: ParserResult, + content: string, + parentClass?: ClassInfo + ): void { + const leftNode = node.childForFieldName('left'); + const rightNode = node.childForFieldName('right'); + + if (!leftNode || !rightNode) return; + + const varName = content.substring(leftNode.startIndex, leftNode.endIndex); + const value = content.substring(rightNode.startIndex, rightNode.endIndex); + + // Check for type annotation + let type = 'any'; + const typeNode = node.childForFieldName('type'); + if (typeNode) { + type = content.substring(typeNode.startIndex, typeNode.endIndex); + } + + if (parentClass) { + // It's a class variable + const visibility = varName.startsWith('_') + ? (varName.startsWith('__') ? 'private' : 'protected') + : 'public'; + + parentClass.properties.push({ + name: varName, + type, + visibility, + static: true, // Class-level variables are static + value + }); + } else if (this.isConstant(varName)) { + // It's a module-level constant + result.constants.push({ + name: varName, + type, + value, + line: node.startPosition.row + 1 + }); + + result.exports.push({ + name: varName, + type: 'variable', + line: node.startPosition.row + 1 + }); + } + } + + private extractParameters(node: any, content: string): Array<{ name: string; type: string; default?: string }> { + const params: Array<{ name: string; type: string; default?: string }> = []; + const paramsNode = node.childForFieldName('parameters'); + + if (!paramsNode) return params; + + for (const child of paramsNode.children) { + if (child.type === 'identifier' || child.type === 'typed_parameter' || + child.type === 'default_parameter' || child.type === 'typed_default_parameter' || + child.type === 'list_splat_pattern' || child.type === 'dictionary_splat_pattern') { + + const paramName = this.extractParameterName(child, content); + const paramType = this.extractParameterType(child, content); + const paramDefault = this.extractParameterDefault(child, content); + + if (paramName && paramName !== 'self' && paramName !== 'cls') { + params.push({ + name: paramName, + type: paramType || 'any', + ...(paramDefault && { default: paramDefault }) + }); + } + } + } + + return params; + } + + private extractParameterName(node: any, content: string): string { + if (node.type === 'identifier') { + return content.substring(node.startIndex, node.endIndex); + } + + const nameNode = node.childForFieldName('name') || + node.children.find((child: any) => child.type === 'identifier'); + + if (nameNode) { + return content.substring(nameNode.startIndex, nameNode.endIndex); + } + + // Handle splat patterns + if (node.type === 'list_splat_pattern') { + const identNode = node.children.find((child: any) => child.type === 'identifier'); + if (identNode) { + return '*' + content.substring(identNode.startIndex, identNode.endIndex); + } + } else if (node.type === 'dictionary_splat_pattern') { + const identNode = node.children.find((child: any) => child.type === 'identifier'); + if (identNode) { + return '**' + content.substring(identNode.startIndex, identNode.endIndex); + } + } + + return ''; + } + + private extractParameterType(node: any, content: string): string | undefined { + const typeNode = node.childForFieldName('type'); + if (typeNode) { + return content.substring(typeNode.startIndex, typeNode.endIndex); + } + return undefined; + } + + private extractParameterDefault(node: any, content: string): string | undefined { + const valueNode = node.childForFieldName('value'); + if (valueNode) { + return content.substring(valueNode.startIndex, valueNode.endIndex); + } + return undefined; + } + + private extractReturnType(node: any, content: string): string { + const returnTypeNode = node.childForFieldName('return_type'); + if (returnTypeNode) { + return content.substring(returnTypeNode.startIndex, returnTypeNode.endIndex); + } + return 'any'; + } + + private extractBaseClasses(node: any, content: string): string[] { + const bases: string[] = []; + const superclassesNode = node.childForFieldName('superclasses'); + + if (superclassesNode) { + for (const child of superclassesNode.children) { + if (child.type === 'identifier' || child.type === 'attribute' || child.type === 'subscript') { + const baseClass = content.substring(child.startIndex, child.endIndex); + if (baseClass !== '(' && baseClass !== ')' && baseClass !== ',') { + bases.push(baseClass); + } + } + } + } + + return bases; + } + + private extractMetaclass(node: any, content: string): string | undefined { + const superclassesNode = node.childForFieldName('superclasses'); + + if (superclassesNode) { + for (const child of superclassesNode.children) { + if (child.type === 'keyword_argument') { + const nameNode = child.childForFieldName('name'); + const valueNode = child.childForFieldName('value'); + + if (nameNode && valueNode) { + const name = content.substring(nameNode.startIndex, nameNode.endIndex); + if (name === 'metaclass') { + return content.substring(valueNode.startIndex, valueNode.endIndex); + } + } + } + } + } + + return undefined; + } + + private checkIfGenerator(node: any, content: string): boolean { + const bodyNode = node.childForFieldName('body'); + if (!bodyNode) return false; + + // Recursively check for yield statements + return this.hasYieldStatement(bodyNode); + } + + private hasYieldStatement(node: any): boolean { + if (node.type === 'yield' || node.type === 'yield_statement' || node.type === 'yield_from_statement') { + return true; + } + + for (const child of node.children) { + if (this.hasYieldStatement(child)) { + return true; + } + } + + return false; + } + + private isConstant(name: string): boolean { + return /^[A-Z_][A-Z0-9_]*$/.test(name); + } + + private extractAllExports(content: string, result: ParserResult): void { + // Look for __all__ definition + const allMatch = content.match(/__all__\s*=\s*\[([^\]]+)\]/); + if (allMatch) { + const exports = allMatch[1].match(/['"]([^'"]+)['"]/g); + if (exports) { + for (const exp of exports) { + const name = exp.replace(/['"]/g, ''); + if (!result.exports.find(e => e.name === name)) { + result.exports.push({ + name, + type: 'variable' + }); + } + } + } + } + } } \ No newline at end of file diff --git a/src/parsers/sql.ts b/src/parsers/sql.ts index a534b76..080cc9e 100644 --- a/src/parsers/sql.ts +++ b/src/parsers/sql.ts @@ -1,359 +1,359 @@ -import { ParserResult, FunctionInfo, ClassInfo} from '../types'; -import { Parser } from '../types'; - -export class SQLParser implements Parser { - language = 'SQL'; - extensions = ['.sql', '.psql', '.mysql', '.pgsql']; - - parse(content: string, filePath: string): ParserResult { - const result: ParserResult = { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - errors: [] - }; - - try { - const lines = content.split('\n'); - const upperContent = content.toUpperCase(); - - // Track current context - let inCreateTable = false; - let currentTable: ClassInfo | null = null; - let tableStartLine = 0; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const trimmed = line.trim(); - const upperTrimmed = trimmed.toUpperCase(); - const lineNumber = i + 1; - - // Skip comments - if (trimmed.startsWith('--') || trimmed.startsWith('/*')) { - continue; - } - - // CREATE TABLE statements - if (upperTrimmed.includes('CREATE TABLE')) { - const tableMatch = line.match(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(\w+)\.)?(\w+)/i); - if (tableMatch) { - const schema = tableMatch[1]; - const tableName = tableMatch[2]; - - currentTable = { - name: schema ? `${schema}.${tableName}` : tableName, - methods: [], // Will store triggers - properties: [], // Will store columns - extends: undefined, - implements: [], - startLine: lineNumber, - endLine: lineNumber, - type: 'table' - }; - - inCreateTable = true; - tableStartLine = lineNumber; - - result.exports.push({ - name: currentTable.name, - type: 'table' - }); - } - } - - // Parse table columns - if (inCreateTable && currentTable) { - // Check for end of CREATE TABLE - if (trimmed.includes(');') || (trimmed === ')' && lines[i + 1]?.trim() === ';')) { - currentTable.endLine = lineNumber; - result.classes.push(currentTable); - currentTable = null; - inCreateTable = false; - continue; - } - - // Parse column definitions - if (!upperTrimmed.startsWith('CREATE') && !upperTrimmed.startsWith('CONSTRAINT') && - !upperTrimmed.startsWith('PRIMARY') && !upperTrimmed.startsWith('FOREIGN') && - !upperTrimmed.startsWith('UNIQUE') && !upperTrimmed.startsWith('CHECK') && - trimmed !== '(' && trimmed !== ')' && trimmed !== ');') { - - const columnMatch = trimmed.match(/^(\w+)\s+([A-Z]+[\w()]*)/i); - if (columnMatch) { - const columnName = columnMatch[1]; - const columnType = columnMatch[2]; - - const isNullable = !upperTrimmed.includes('NOT NULL'); - const isPrimary = upperTrimmed.includes('PRIMARY KEY'); - const isUnique = upperTrimmed.includes('UNIQUE'); - const hasDefault = upperTrimmed.includes('DEFAULT'); - - currentTable.properties.push({ - name: columnName, - type: columnType.toLowerCase(), - visibility: 'public', - static: false, - nullable: isNullable, - primary: isPrimary, - unique: isUnique, - hasDefault - }); - } - } - - // Parse constraints - if (upperTrimmed.startsWith('CONSTRAINT')) { - const constraintMatch = trimmed.match(/CONSTRAINT\s+(\w+)/i); - if (constraintMatch) { - result.constants.push({ - name: constraintMatch[1], - type: 'constraint', - value: trimmed - }); - } - } - } - - // CREATE VIEW statements - if (upperTrimmed.includes('CREATE VIEW') || upperTrimmed.includes('CREATE MATERIALIZED VIEW')) { - const viewMatch = line.match(/CREATE\s+(?:MATERIALIZED\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(\w+)\.)?(\w+)/i); - if (viewMatch) { - const schema = viewMatch[1]; - const viewName = viewMatch[2]; - const fullName = schema ? `${schema}.${viewName}` : viewName; - - const view: ClassInfo = { - name: fullName, - methods: [], - properties: [], - extends: undefined, - implements: [], - startLine: lineNumber, - endLine: this.findStatementEnd(lines, i), - type: 'view' - }; - - result.classes.push(view); - result.exports.push({ - name: fullName, - type: 'view' - }); - } - } - - // CREATE INDEX statements - if (upperTrimmed.includes('CREATE INDEX') || upperTrimmed.includes('CREATE UNIQUE INDEX')) { - const indexMatch = line.match(/CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)/i); - if (indexMatch) { - result.constants.push({ - name: indexMatch[1], - type: 'index', - value: trimmed - }); - } - } - - // CREATE FUNCTION/PROCEDURE statements - if (upperTrimmed.includes('CREATE FUNCTION') || upperTrimmed.includes('CREATE PROCEDURE')) { - const funcMatch = line.match(/CREATE\s+(?:OR\s+REPLACE\s+)?(?:FUNCTION|PROCEDURE)\s+(?:(\w+)\.)?(\w+)\s*\(([^)]*)\)/i); - if (funcMatch) { - const schema = funcMatch[1]; - const funcName = funcMatch[2]; - const paramsStr = funcMatch[3]; - const fullName = schema ? `${schema}.${funcName}` : funcName; - - const parameters = this.parseFunctionParameters(paramsStr); - const returnType = this.extractReturnType(lines, i); - - const funcInfo: FunctionInfo = { - name: fullName, - parameters, - returnType, - async: false, - generator: false, - startLine: lineNumber, - endLine: this.findFunctionEnd(lines, i) - }; - - result.functions.push(funcInfo); - result.exports.push({ - name: fullName, - type: 'function' - }); - } - } - - // CREATE TRIGGER statements - if (upperTrimmed.includes('CREATE TRIGGER')) { - const triggerMatch = line.match(/CREATE\s+TRIGGER\s+(\w+)/i); - if (triggerMatch) { - const triggerName = triggerMatch[1]; - - // Find the table this trigger is associated with - const onTableMatch = upperContent.match(new RegExp(`CREATE\\s+TRIGGER\\s+${triggerName}[^]*?ON\\s+(\\w+)`, 'i')); - const tableName = onTableMatch ? onTableMatch[1] : undefined; - - result.functions.push({ - name: triggerName, - parameters: [], - returnType: 'void', - async: false, - generator: false, - startLine: lineNumber, - endLine: this.findStatementEnd(lines, i), - triggerTable: tableName - }); - - result.exports.push({ - name: triggerName, - type: 'trigger' - }); - } - } - - // CREATE TYPE statements (PostgreSQL) - if (upperTrimmed.includes('CREATE TYPE')) { - const typeMatch = line.match(/CREATE\s+TYPE\s+(?:(\w+)\.)?(\w+)/i); - if (typeMatch) { - const schema = typeMatch[1]; - const typeName = typeMatch[2]; - const fullName = schema ? `${schema}.${typeName}` : typeName; - - result.constants.push({ - name: fullName, - type: 'custom_type', - value: undefined - }); - - result.exports.push({ - name: fullName, - type: 'type' - }); - } - } - - // CREATE SEQUENCE statements - if (upperTrimmed.includes('CREATE SEQUENCE')) { - const seqMatch = line.match(/CREATE\s+SEQUENCE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(\w+)\.)?(\w+)/i); - if (seqMatch) { - const schema = seqMatch[1]; - const seqName = seqMatch[2]; - const fullName = schema ? `${schema}.${seqName}` : seqName; - - result.constants.push({ - name: fullName, - type: 'sequence', - value: undefined - }); - } - } - - // GRANT statements (to track dependencies) - if (upperTrimmed.startsWith('GRANT')) { - const grantMatch = line.match(/GRANT\s+[\w,\s]+\s+ON\s+(?:TABLE\s+)?(?:(\w+)\.)?(\w+)/i); - if (grantMatch) { - const schema = grantMatch[1]; - const objectName = grantMatch[2]; - const fullName = schema ? `${schema}.${objectName}` : objectName; - - // Track as a dependency - result.dependencies.push(fullName); - } - } - - // USE/CONNECT statements (database selection) - if (upperTrimmed.startsWith('USE ') || upperTrimmed.startsWith('\\c ')) { - const dbMatch = line.match(/(?:USE|\\c)\s+(\w+)/i); - if (dbMatch) { - result.imports.push({ - source: dbMatch[1], - specifiers: [], - type: 'database' - }); - } - } - } - - // Add any remaining table - if (currentTable) { - result.classes.push(currentTable); - } - - } catch (error: any) { - result.errors.push(`Parse error: ${error.message}`); - } - - return result; - } - - private parseFunctionParameters(paramsStr: string): Array<{ name: string; type: string }> { - if (!paramsStr.trim()) return []; - - const params: Array<{ name: string; type: string }> = []; - const paramList = paramsStr.split(','); - - for (const param of paramList) { - const trimmed = param.trim(); - if (!trimmed) continue; - - // Handle various parameter formats - const paramMatch = trimmed.match(/(?:(IN|OUT|INOUT)\s+)?(\w+)\s+(.+)/i); - if (paramMatch) { - const direction = paramMatch[1] || 'IN'; - const name = paramMatch[2]; - const type = paramMatch[3]; - - params.push({ - name: `${direction.toLowerCase()}_${name}`, - type: type.toLowerCase() - }); - } - } - - return params; - } - - private extractReturnType(lines: string[], startIndex: number): string { - // Look for RETURNS clause in next few lines - for (let i = startIndex; i < Math.min(startIndex + 5, lines.length); i++) { - const line = lines[i]; - const returnsMatch = line.match(/RETURNS\s+(\w+)/i); - if (returnsMatch) { - return returnsMatch[1].toLowerCase(); - } - } - return 'void'; - } - - private findStatementEnd(lines: string[], startIndex: number): number { - for (let i = startIndex; i < lines.length; i++) { - if (lines[i].trim().endsWith(';')) { - return i + 1; - } - } - return lines.length; - } - - private findFunctionEnd(lines: string[], startIndex: number): number { - let inBody = false; - const upperLines = lines.map(l => l.toUpperCase()); - - for (let i = startIndex; i < lines.length; i++) { - const upperLine = upperLines[i]; - - if (upperLine.includes('BEGIN')) { - inBody = true; - } - - if (inBody && (upperLine.includes('END;') || upperLine.includes('END ') || - upperLine.includes('$$') || upperLine.includes("$$"))) { - return i + 1; - } - } - - return this.findStatementEnd(lines, startIndex); - } +import { ParserResult, FunctionInfo, ClassInfo} from '../types'; +import { Parser } from '../types'; + +export class SQLParser implements Parser { + language = 'SQL'; + extensions = ['.sql', '.psql', '.mysql', '.pgsql']; + + parse(content: string, filePath: string): ParserResult { + const result: ParserResult = { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + errors: [] + }; + + try { + const lines = content.split('\n'); + const upperContent = content.toUpperCase(); + + // Track current context + let inCreateTable = false; + let currentTable: ClassInfo | null = null; + let tableStartLine = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + const upperTrimmed = trimmed.toUpperCase(); + const lineNumber = i + 1; + + // Skip comments + if (trimmed.startsWith('--') || trimmed.startsWith('/*')) { + continue; + } + + // CREATE TABLE statements + if (upperTrimmed.includes('CREATE TABLE')) { + const tableMatch = line.match(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(\w+)\.)?(\w+)/i); + if (tableMatch) { + const schema = tableMatch[1]; + const tableName = tableMatch[2]; + + currentTable = { + name: schema ? `${schema}.${tableName}` : tableName, + methods: [], // Will store triggers + properties: [], // Will store columns + extends: undefined, + implements: [], + startLine: lineNumber, + endLine: lineNumber, + type: 'table' + }; + + inCreateTable = true; + tableStartLine = lineNumber; + + result.exports.push({ + name: currentTable.name, + type: 'table' + }); + } + } + + // Parse table columns + if (inCreateTable && currentTable) { + // Check for end of CREATE TABLE + if (trimmed.includes(');') || (trimmed === ')' && lines[i + 1]?.trim() === ';')) { + currentTable.endLine = lineNumber; + result.classes.push(currentTable); + currentTable = null; + inCreateTable = false; + continue; + } + + // Parse column definitions + if (!upperTrimmed.startsWith('CREATE') && !upperTrimmed.startsWith('CONSTRAINT') && + !upperTrimmed.startsWith('PRIMARY') && !upperTrimmed.startsWith('FOREIGN') && + !upperTrimmed.startsWith('UNIQUE') && !upperTrimmed.startsWith('CHECK') && + trimmed !== '(' && trimmed !== ')' && trimmed !== ');') { + + const columnMatch = trimmed.match(/^(\w+)\s+([A-Z]+[\w()]*)/i); + if (columnMatch) { + const columnName = columnMatch[1]; + const columnType = columnMatch[2]; + + const isNullable = !upperTrimmed.includes('NOT NULL'); + const isPrimary = upperTrimmed.includes('PRIMARY KEY'); + const isUnique = upperTrimmed.includes('UNIQUE'); + const hasDefault = upperTrimmed.includes('DEFAULT'); + + currentTable.properties.push({ + name: columnName, + type: columnType.toLowerCase(), + visibility: 'public', + static: false, + nullable: isNullable, + primary: isPrimary, + unique: isUnique, + hasDefault + }); + } + } + + // Parse constraints + if (upperTrimmed.startsWith('CONSTRAINT')) { + const constraintMatch = trimmed.match(/CONSTRAINT\s+(\w+)/i); + if (constraintMatch) { + result.constants.push({ + name: constraintMatch[1], + type: 'constraint', + value: trimmed + }); + } + } + } + + // CREATE VIEW statements + if (upperTrimmed.includes('CREATE VIEW') || upperTrimmed.includes('CREATE MATERIALIZED VIEW')) { + const viewMatch = line.match(/CREATE\s+(?:MATERIALIZED\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(\w+)\.)?(\w+)/i); + if (viewMatch) { + const schema = viewMatch[1]; + const viewName = viewMatch[2]; + const fullName = schema ? `${schema}.${viewName}` : viewName; + + const view: ClassInfo = { + name: fullName, + methods: [], + properties: [], + extends: undefined, + implements: [], + startLine: lineNumber, + endLine: this.findStatementEnd(lines, i), + type: 'view' + }; + + result.classes.push(view); + result.exports.push({ + name: fullName, + type: 'view' + }); + } + } + + // CREATE INDEX statements + if (upperTrimmed.includes('CREATE INDEX') || upperTrimmed.includes('CREATE UNIQUE INDEX')) { + const indexMatch = line.match(/CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)/i); + if (indexMatch) { + result.constants.push({ + name: indexMatch[1], + type: 'index', + value: trimmed + }); + } + } + + // CREATE FUNCTION/PROCEDURE statements + if (upperTrimmed.includes('CREATE FUNCTION') || upperTrimmed.includes('CREATE PROCEDURE')) { + const funcMatch = line.match(/CREATE\s+(?:OR\s+REPLACE\s+)?(?:FUNCTION|PROCEDURE)\s+(?:(\w+)\.)?(\w+)\s*\(([^)]*)\)/i); + if (funcMatch) { + const schema = funcMatch[1]; + const funcName = funcMatch[2]; + const paramsStr = funcMatch[3]; + const fullName = schema ? `${schema}.${funcName}` : funcName; + + const parameters = this.parseFunctionParameters(paramsStr); + const returnType = this.extractReturnType(lines, i); + + const funcInfo: FunctionInfo = { + name: fullName, + parameters, + returnType, + async: false, + generator: false, + startLine: lineNumber, + endLine: this.findFunctionEnd(lines, i) + }; + + result.functions.push(funcInfo); + result.exports.push({ + name: fullName, + type: 'function' + }); + } + } + + // CREATE TRIGGER statements + if (upperTrimmed.includes('CREATE TRIGGER')) { + const triggerMatch = line.match(/CREATE\s+TRIGGER\s+(\w+)/i); + if (triggerMatch) { + const triggerName = triggerMatch[1]; + + // Find the table this trigger is associated with + const onTableMatch = upperContent.match(new RegExp(`CREATE\\s+TRIGGER\\s+${triggerName}[^]*?ON\\s+(\\w+)`, 'i')); + const tableName = onTableMatch ? onTableMatch[1] : undefined; + + result.functions.push({ + name: triggerName, + parameters: [], + returnType: 'void', + async: false, + generator: false, + startLine: lineNumber, + endLine: this.findStatementEnd(lines, i), + triggerTable: tableName + }); + + result.exports.push({ + name: triggerName, + type: 'trigger' + }); + } + } + + // CREATE TYPE statements (PostgreSQL) + if (upperTrimmed.includes('CREATE TYPE')) { + const typeMatch = line.match(/CREATE\s+TYPE\s+(?:(\w+)\.)?(\w+)/i); + if (typeMatch) { + const schema = typeMatch[1]; + const typeName = typeMatch[2]; + const fullName = schema ? `${schema}.${typeName}` : typeName; + + result.constants.push({ + name: fullName, + type: 'custom_type', + value: undefined + }); + + result.exports.push({ + name: fullName, + type: 'type' + }); + } + } + + // CREATE SEQUENCE statements + if (upperTrimmed.includes('CREATE SEQUENCE')) { + const seqMatch = line.match(/CREATE\s+SEQUENCE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(\w+)\.)?(\w+)/i); + if (seqMatch) { + const schema = seqMatch[1]; + const seqName = seqMatch[2]; + const fullName = schema ? `${schema}.${seqName}` : seqName; + + result.constants.push({ + name: fullName, + type: 'sequence', + value: undefined + }); + } + } + + // GRANT statements (to track dependencies) + if (upperTrimmed.startsWith('GRANT')) { + const grantMatch = line.match(/GRANT\s+[\w,\s]+\s+ON\s+(?:TABLE\s+)?(?:(\w+)\.)?(\w+)/i); + if (grantMatch) { + const schema = grantMatch[1]; + const objectName = grantMatch[2]; + const fullName = schema ? `${schema}.${objectName}` : objectName; + + // Track as a dependency + result.dependencies.push(fullName); + } + } + + // USE/CONNECT statements (database selection) + if (upperTrimmed.startsWith('USE ') || upperTrimmed.startsWith('\\c ')) { + const dbMatch = line.match(/(?:USE|\\c)\s+(\w+)/i); + if (dbMatch) { + result.imports.push({ + source: dbMatch[1], + specifiers: [], + type: 'database' + }); + } + } + } + + // Add any remaining table + if (currentTable) { + result.classes.push(currentTable); + } + + } catch (error: any) { + result.errors.push(`Parse error: ${error.message}`); + } + + return result; + } + + private parseFunctionParameters(paramsStr: string): Array<{ name: string; type: string }> { + if (!paramsStr.trim()) return []; + + const params: Array<{ name: string; type: string }> = []; + const paramList = paramsStr.split(','); + + for (const param of paramList) { + const trimmed = param.trim(); + if (!trimmed) continue; + + // Handle various parameter formats + const paramMatch = trimmed.match(/(?:(IN|OUT|INOUT)\s+)?(\w+)\s+(.+)/i); + if (paramMatch) { + const direction = paramMatch[1] || 'IN'; + const name = paramMatch[2]; + const type = paramMatch[3]; + + params.push({ + name: `${direction.toLowerCase()}_${name}`, + type: type.toLowerCase() + }); + } + } + + return params; + } + + private extractReturnType(lines: string[], startIndex: number): string { + // Look for RETURNS clause in next few lines + for (let i = startIndex; i < Math.min(startIndex + 5, lines.length); i++) { + const line = lines[i]; + const returnsMatch = line.match(/RETURNS\s+(\w+)/i); + if (returnsMatch) { + return returnsMatch[1].toLowerCase(); + } + } + return 'void'; + } + + private findStatementEnd(lines: string[], startIndex: number): number { + for (let i = startIndex; i < lines.length; i++) { + if (lines[i].trim().endsWith(';')) { + return i + 1; + } + } + return lines.length; + } + + private findFunctionEnd(lines: string[], startIndex: number): number { + let inBody = false; + const upperLines = lines.map(l => l.toUpperCase()); + + for (let i = startIndex; i < lines.length; i++) { + const upperLine = upperLines[i]; + + if (upperLine.includes('BEGIN')) { + inBody = true; + } + + if (inBody && (upperLine.includes('END;') || upperLine.includes('END ') || + upperLine.includes('$$') || upperLine.includes("$$"))) { + return i + 1; + } + } + + return this.findStatementEnd(lines, startIndex); + } } \ No newline at end of file diff --git a/src/parsers/typescript.ts b/src/parsers/typescript.ts index d22b1f6..23479fb 100644 --- a/src/parsers/typescript.ts +++ b/src/parsers/typescript.ts @@ -1,365 +1,365 @@ -import * as parser from '@babel/parser'; -import traverse from '@babel/traverse'; -import { ParserResult} from '../types'; -import { Parser } from '../types'; - -export class TypeScriptParser implements Parser { - language = 'TypeScript'; - extensions = ['.ts', '.tsx', '.mts', '.cts']; - - parse(content: string, filePath: string): ParserResult { - const result: ParserResult = { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - errors: [] - }; - - try { - const ast = parser.parse(content, { - sourceType: 'unambiguous', - plugins: [ - 'typescript', - 'jsx', - 'classProperties', - 'dynamicImport', - 'optionalChaining', - 'nullishCoalescingOperator', - 'decorators-legacy' - ], - errorRecovery: true - }); - - const self = this; - traverse(ast, { - ImportDeclaration(path) { - const source = path.node.source.value; - const specifiers = path.node.specifiers.map(spec => { - if (spec.type === 'ImportDefaultSpecifier') { - return { name: 'default', alias: spec.local.name }; - } else if (spec.type === 'ImportSpecifier') { - return { - name: spec.imported.type === 'Identifier' ? spec.imported.name : 'default', - alias: spec.local.name - }; - } else { - return { name: '*', alias: spec.local.name }; - } - }); - - // Check if it's a type-only import - const isTypeOnly = path.node.importKind === 'type'; - - result.imports.push({ - source, - specifiers, - type: isTypeOnly ? 'type' : 'static' - }); - - if (source.startsWith('.')) { - result.dependencies.push(source); - } - }, - - TSImportEqualsDeclaration(path) { - if (path.node.moduleReference.type === 'TSExternalModuleReference' && - path.node.moduleReference.expression.type === 'StringLiteral') { - result.imports.push({ - source: path.node.moduleReference.expression.value, - specifiers: [{ name: '*', alias: path.node.id.name }], - type: 'namespace' - }); - } - }, - - CallExpression(path) { - // Dynamic imports - if (path.node.callee.type === 'Import' && path.node.arguments.length > 0) { - const arg = path.node.arguments[0]; - if (arg.type === 'StringLiteral') { - result.imports.push({ - source: arg.value, - specifiers: [], - type: 'dynamic' - }); - } - } - }, - - ExportNamedDeclaration(path) { - if (path.node.declaration) { - if (path.node.declaration.type === 'FunctionDeclaration') { - const func = path.node.declaration; - if (func.id) { - result.exports.push({ - name: func.id.name, - type: 'function' - }); - } - } else if (path.node.declaration.type === 'ClassDeclaration') { - const cls = path.node.declaration; - if (cls.id) { - result.exports.push({ - name: cls.id.name, - type: 'class' - }); - } - } else if (path.node.declaration.type === 'VariableDeclaration') { - for (const decl of path.node.declaration.declarations) { - if (decl.id.type === 'Identifier') { - result.exports.push({ - name: decl.id.name, - type: 'variable' - }); - } - } - } else if (path.node.declaration.type === 'TSInterfaceDeclaration') { - result.exports.push({ - name: path.node.declaration.id.name, - type: 'interface' - }); - } else if (path.node.declaration.type === 'TSTypeAliasDeclaration') { - result.exports.push({ - name: path.node.declaration.id.name, - type: 'type' - }); - } else if (path.node.declaration.type === 'TSEnumDeclaration') { - result.exports.push({ - name: path.node.declaration.id.name, - type: 'enum' - }); - } - } else if (path.node.specifiers) { - for (const spec of path.node.specifiers) { - if (spec.type === 'ExportSpecifier' && spec.exported.type === 'Identifier') { - result.exports.push({ - name: spec.exported.name, - type: path.node.exportKind === 'type' ? 'type' : 'named' - }); - } - } - } - }, - - ExportDefaultDeclaration(path) { - result.exports.push({ - name: 'default', - type: 'default' - }); - }, - - FunctionDeclaration(path) { - if (path.node.id) { - const params = path.node.params.map(param => { - if (param.type === 'Identifier') { - return { - name: param.name, - type: param.typeAnnotation ? self.getTypeString(param.typeAnnotation) : 'any' - }; - } - return { name: 'unknown', type: 'any' }; - }); - - const returnType = path.node.returnType - ? self.getTypeString(path.node.returnType) - : 'any'; - - result.functions.push({ - name: path.node.id.name, - parameters: params, - returnType, - async: path.node.async || false, - generator: path.node.generator || false, - startLine: path.node.loc?.start.line || 0, - endLine: path.node.loc?.end.line || 0 - }); - } - }, - - ClassDeclaration(path) { - if (path.node.id) { - const methods: any[] = []; - const properties: any[] = []; - - path.node.body.body.forEach(member => { - if (member.type === 'ClassMethod' || member.type === 'TSDeclareMethod') { - const visibility = self.getVisibility(member); - methods.push({ - name: member.key.type === 'Identifier' ? member.key.name : 'unknown', - visibility, - static: member.static || false - }); - } else if (member.type === 'ClassProperty') { - const visibility = self.getVisibility(member); - properties.push({ - name: member.key.type === 'Identifier' ? member.key.name : 'unknown', - type: member.typeAnnotation ? self.getTypeString(member.typeAnnotation) : 'any', - visibility, - static: member.static || false, - readonly: member.readonly || false - }); - } - }); - - const extendsClause = path.node.superClass && path.node.superClass.type === 'Identifier' - ? path.node.superClass.name - : undefined; - - const implementsClauses: string[] = []; - if (path.node.implements) { - path.node.implements.forEach(impl => { - if (impl.type === 'TSExpressionWithTypeArguments' && - impl.expression.type === 'Identifier') { - implementsClauses.push(impl.expression.name); - } - }); - } - - result.classes.push({ - name: path.node.id.name, - methods, - properties, - extends: extendsClause, - implements: implementsClauses, - abstract: path.node.abstract || false, - startLine: path.node.loc?.start.line || 0, - endLine: path.node.loc?.end.line || 0 - }); - } - }, - - TSInterfaceDeclaration(path) { - const methods: any[] = []; - const properties: any[] = []; - - path.node.body.body.forEach(member => { - if (member.type === 'TSMethodSignature' && member.key.type === 'Identifier') { - methods.push({ - name: member.key.name, - visibility: 'public', - static: false - }); - } else if (member.type === 'TSPropertySignature' && member.key.type === 'Identifier') { - properties.push({ - name: member.key.name, - type: member.typeAnnotation ? self.getTypeString(member.typeAnnotation) : 'any', - visibility: 'public', - static: false, - readonly: member.readonly || false - }); - } - }); - - // Store interfaces as a special type of class - result.classes.push({ - name: path.node.id.name, - methods, - properties, - extends: undefined, - implements: [], - interface: true, - startLine: path.node.loc?.start.line || 0, - endLine: path.node.loc?.end.line || 0 - }); - }, - - TSTypeAliasDeclaration(path) { - result.constants.push({ - name: path.node.id.name, - type: 'type', - value: self.getTypeString(path.node.typeAnnotation) - }); - }, - - TSEnumDeclaration(path) { - const members = path.node.members.map(member => { - if (member.id.type === 'Identifier') { - return member.id.name; - } - return 'unknown'; - }); - - result.constants.push({ - name: path.node.id.name, - type: 'enum', - value: `{${members.join(', ')}}` - }); - }, - - VariableDeclaration(path) { - for (const decl of path.node.declarations) { - if (decl.id.type === 'Identifier') { - const isConst = path.node.kind === 'const'; - const typeStr = decl.id.typeAnnotation - ? self.getTypeString(decl.id.typeAnnotation) - : 'any'; - - if (isConst) { - result.constants.push({ - name: decl.id.name, - type: typeStr, - value: decl.init ? self.getValueString(decl.init) : undefined - }); - } - } - } - } - }); - } catch (error: any) { - result.errors.push(`Parse error: ${error.message}`); - } - - return result; - } - - private getVisibility(member: any): string { - if (member.accessibility) { - return member.accessibility; - } - return 'public'; - } - - private getTypeString(typeNode: any): string { - if (!typeNode) return 'any'; - - if (typeNode.type === 'TSTypeAnnotation') { - return this.getTypeString(typeNode.typeAnnotation); - } - - switch (typeNode.type) { - case 'TSStringKeyword': return 'string'; - case 'TSNumberKeyword': return 'number'; - case 'TSBooleanKeyword': return 'boolean'; - case 'TSAnyKeyword': return 'any'; - case 'TSUnknownKeyword': return 'unknown'; - case 'TSVoidKeyword': return 'void'; - case 'TSNullKeyword': return 'null'; - case 'TSUndefinedKeyword': return 'undefined'; - case 'TSTypeReference': - if (typeNode.typeName && typeNode.typeName.type === 'Identifier') { - return typeNode.typeName.name; - } - break; - case 'TSArrayType': - return `${this.getTypeString(typeNode.elementType)}[]`; - case 'TSUnionType': - return typeNode.types.map((t: any) => this.getTypeString(t)).join(' | '); - case 'TSIntersectionType': - return typeNode.types.map((t: any) => this.getTypeString(t)).join(' & '); - } - - return 'any'; - } - - private getValueString(node: any): string | undefined { - if (node.type === 'StringLiteral') return node.value; - if (node.type === 'NumericLiteral') return String(node.value); - if (node.type === 'BooleanLiteral') return String(node.value); - if (node.type === 'NullLiteral') return 'null'; - if (node.type === 'Identifier') return node.name; - return undefined; - } +import * as parser from '@babel/parser'; +import traverse from '@babel/traverse'; +import { ParserResult} from '../types'; +import { Parser } from '../types'; + +export class TypeScriptParser implements Parser { + language = 'TypeScript'; + extensions = ['.ts', '.tsx', '.mts', '.cts']; + + parse(content: string, filePath: string): ParserResult { + const result: ParserResult = { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + errors: [] + }; + + try { + const ast = parser.parse(content, { + sourceType: 'unambiguous', + plugins: [ + 'typescript', + 'jsx', + 'classProperties', + 'dynamicImport', + 'optionalChaining', + 'nullishCoalescingOperator', + 'decorators-legacy' + ], + errorRecovery: true + }); + + const self = this; + traverse(ast, { + ImportDeclaration(path) { + const source = path.node.source.value; + const specifiers = path.node.specifiers.map(spec => { + if (spec.type === 'ImportDefaultSpecifier') { + return { name: 'default', alias: spec.local.name }; + } else if (spec.type === 'ImportSpecifier') { + return { + name: spec.imported.type === 'Identifier' ? spec.imported.name : 'default', + alias: spec.local.name + }; + } else { + return { name: '*', alias: spec.local.name }; + } + }); + + // Check if it's a type-only import + const isTypeOnly = path.node.importKind === 'type'; + + result.imports.push({ + source, + specifiers, + type: isTypeOnly ? 'type' : 'static' + }); + + if (source.startsWith('.')) { + result.dependencies.push(source); + } + }, + + TSImportEqualsDeclaration(path) { + if (path.node.moduleReference.type === 'TSExternalModuleReference' && + path.node.moduleReference.expression.type === 'StringLiteral') { + result.imports.push({ + source: path.node.moduleReference.expression.value, + specifiers: [{ name: '*', alias: path.node.id.name }], + type: 'namespace' + }); + } + }, + + CallExpression(path) { + // Dynamic imports + if (path.node.callee.type === 'Import' && path.node.arguments.length > 0) { + const arg = path.node.arguments[0]; + if (arg.type === 'StringLiteral') { + result.imports.push({ + source: arg.value, + specifiers: [], + type: 'dynamic' + }); + } + } + }, + + ExportNamedDeclaration(path) { + if (path.node.declaration) { + if (path.node.declaration.type === 'FunctionDeclaration') { + const func = path.node.declaration; + if (func.id) { + result.exports.push({ + name: func.id.name, + type: 'function' + }); + } + } else if (path.node.declaration.type === 'ClassDeclaration') { + const cls = path.node.declaration; + if (cls.id) { + result.exports.push({ + name: cls.id.name, + type: 'class' + }); + } + } else if (path.node.declaration.type === 'VariableDeclaration') { + for (const decl of path.node.declaration.declarations) { + if (decl.id.type === 'Identifier') { + result.exports.push({ + name: decl.id.name, + type: 'variable' + }); + } + } + } else if (path.node.declaration.type === 'TSInterfaceDeclaration') { + result.exports.push({ + name: path.node.declaration.id.name, + type: 'interface' + }); + } else if (path.node.declaration.type === 'TSTypeAliasDeclaration') { + result.exports.push({ + name: path.node.declaration.id.name, + type: 'type' + }); + } else if (path.node.declaration.type === 'TSEnumDeclaration') { + result.exports.push({ + name: path.node.declaration.id.name, + type: 'enum' + }); + } + } else if (path.node.specifiers) { + for (const spec of path.node.specifiers) { + if (spec.type === 'ExportSpecifier' && spec.exported.type === 'Identifier') { + result.exports.push({ + name: spec.exported.name, + type: path.node.exportKind === 'type' ? 'type' : 'named' + }); + } + } + } + }, + + ExportDefaultDeclaration(path) { + result.exports.push({ + name: 'default', + type: 'default' + }); + }, + + FunctionDeclaration(path) { + if (path.node.id) { + const params = path.node.params.map(param => { + if (param.type === 'Identifier') { + return { + name: param.name, + type: param.typeAnnotation ? self.getTypeString(param.typeAnnotation) : 'any' + }; + } + return { name: 'unknown', type: 'any' }; + }); + + const returnType = path.node.returnType + ? self.getTypeString(path.node.returnType) + : 'any'; + + result.functions.push({ + name: path.node.id.name, + parameters: params, + returnType, + async: path.node.async || false, + generator: path.node.generator || false, + startLine: path.node.loc?.start.line || 0, + endLine: path.node.loc?.end.line || 0 + }); + } + }, + + ClassDeclaration(path) { + if (path.node.id) { + const methods: any[] = []; + const properties: any[] = []; + + path.node.body.body.forEach(member => { + if (member.type === 'ClassMethod' || member.type === 'TSDeclareMethod') { + const visibility = self.getVisibility(member); + methods.push({ + name: member.key.type === 'Identifier' ? member.key.name : 'unknown', + visibility, + static: member.static || false + }); + } else if (member.type === 'ClassProperty') { + const visibility = self.getVisibility(member); + properties.push({ + name: member.key.type === 'Identifier' ? member.key.name : 'unknown', + type: member.typeAnnotation ? self.getTypeString(member.typeAnnotation) : 'any', + visibility, + static: member.static || false, + readonly: member.readonly || false + }); + } + }); + + const extendsClause = path.node.superClass && path.node.superClass.type === 'Identifier' + ? path.node.superClass.name + : undefined; + + const implementsClauses: string[] = []; + if (path.node.implements) { + path.node.implements.forEach(impl => { + if (impl.type === 'TSExpressionWithTypeArguments' && + impl.expression.type === 'Identifier') { + implementsClauses.push(impl.expression.name); + } + }); + } + + result.classes.push({ + name: path.node.id.name, + methods, + properties, + extends: extendsClause, + implements: implementsClauses, + abstract: path.node.abstract || false, + startLine: path.node.loc?.start.line || 0, + endLine: path.node.loc?.end.line || 0 + }); + } + }, + + TSInterfaceDeclaration(path) { + const methods: any[] = []; + const properties: any[] = []; + + path.node.body.body.forEach(member => { + if (member.type === 'TSMethodSignature' && member.key.type === 'Identifier') { + methods.push({ + name: member.key.name, + visibility: 'public', + static: false + }); + } else if (member.type === 'TSPropertySignature' && member.key.type === 'Identifier') { + properties.push({ + name: member.key.name, + type: member.typeAnnotation ? self.getTypeString(member.typeAnnotation) : 'any', + visibility: 'public', + static: false, + readonly: member.readonly || false + }); + } + }); + + // Store interfaces as a special type of class + result.classes.push({ + name: path.node.id.name, + methods, + properties, + extends: undefined, + implements: [], + interface: true, + startLine: path.node.loc?.start.line || 0, + endLine: path.node.loc?.end.line || 0 + }); + }, + + TSTypeAliasDeclaration(path) { + result.constants.push({ + name: path.node.id.name, + type: 'type', + value: self.getTypeString(path.node.typeAnnotation) + }); + }, + + TSEnumDeclaration(path) { + const members = path.node.members.map(member => { + if (member.id.type === 'Identifier') { + return member.id.name; + } + return 'unknown'; + }); + + result.constants.push({ + name: path.node.id.name, + type: 'enum', + value: `{${members.join(', ')}}` + }); + }, + + VariableDeclaration(path) { + for (const decl of path.node.declarations) { + if (decl.id.type === 'Identifier') { + const isConst = path.node.kind === 'const'; + const typeStr = decl.id.typeAnnotation + ? self.getTypeString(decl.id.typeAnnotation) + : 'any'; + + if (isConst) { + result.constants.push({ + name: decl.id.name, + type: typeStr, + value: decl.init ? self.getValueString(decl.init) : undefined + }); + } + } + } + } + }); + } catch (error: any) { + result.errors.push(`Parse error: ${error.message}`); + } + + return result; + } + + private getVisibility(member: any): string { + if (member.accessibility) { + return member.accessibility; + } + return 'public'; + } + + private getTypeString(typeNode: any): string { + if (!typeNode) return 'any'; + + if (typeNode.type === 'TSTypeAnnotation') { + return this.getTypeString(typeNode.typeAnnotation); + } + + switch (typeNode.type) { + case 'TSStringKeyword': return 'string'; + case 'TSNumberKeyword': return 'number'; + case 'TSBooleanKeyword': return 'boolean'; + case 'TSAnyKeyword': return 'any'; + case 'TSUnknownKeyword': return 'unknown'; + case 'TSVoidKeyword': return 'void'; + case 'TSNullKeyword': return 'null'; + case 'TSUndefinedKeyword': return 'undefined'; + case 'TSTypeReference': + if (typeNode.typeName && typeNode.typeName.type === 'Identifier') { + return typeNode.typeName.name; + } + break; + case 'TSArrayType': + return `${this.getTypeString(typeNode.elementType)}[]`; + case 'TSUnionType': + return typeNode.types.map((t: any) => this.getTypeString(t)).join(' | '); + case 'TSIntersectionType': + return typeNode.types.map((t: any) => this.getTypeString(t)).join(' & '); + } + + return 'any'; + } + + private getValueString(node: any): string | undefined { + if (node.type === 'StringLiteral') return node.value; + if (node.type === 'NumericLiteral') return String(node.value); + if (node.type === 'BooleanLiteral') return String(node.value); + if (node.type === 'NullLiteral') return 'null'; + if (node.type === 'Identifier') return node.name; + return undefined; + } } \ No newline at end of file diff --git a/src/parsers/wasm/base-wasm-parser.ts b/src/parsers/wasm/base-wasm-parser.ts index 8680345..9b6d864 100644 --- a/src/parsers/wasm/base-wasm-parser.ts +++ b/src/parsers/wasm/base-wasm-parser.ts @@ -1,221 +1,220 @@ -/** - * Base WebAssembly Parser - * Provides common functionality for all WASM-based parsers - */ - -import { ParserResult, Parser } from '../../types'; -import { EventEmitter } from 'events'; -import * as fs from 'fs'; -import * as path from 'path'; -import logger from '../../../utils/logger'; - -export abstract class BaseWASMParser extends EventEmitter implements Parser { - abstract language: string; - abstract extensions: string[]; - - protected wasmModule: WebAssembly.Module | null = null; - protected wasmInstance: WebAssembly.Instance | null = null; - protected wasmMemory: WebAssembly.Memory; - protected wasmExports: any; - - // Performance tracking - protected parseCount = 0; - protected totalParseTime = 0; - - constructor() { - super(); - - // Initialize WASM memory with 256 pages (16MB initial, can grow to 1GB) - this.wasmMemory = new WebAssembly.Memory({ - initial: 256, - maximum: 16384 - }); - } - - /** - * Initialize WASM module - */ - async initialize(wasmPath: string): Promise { - try { - const wasmBuffer = fs.readFileSync(wasmPath); - - // Compile WASM module - this.wasmModule = await WebAssembly.compile(wasmBuffer); - - // Create import object for WASM - const importObject = this.getImportObject(); - - // Instantiate WASM module - this.wasmInstance = await WebAssembly.instantiate(this.wasmModule, importObject); - this.wasmExports = this.wasmInstance.exports; - - // Initialize the parser in WASM - if (this.wasmExports.initialize) { - this.wasmExports.initialize(); - } - - this.emit('initialized', { language: this.language }); - } catch (error) { - this.emit('initialization-error', { language: this.language, error }); - throw new Error(`Failed to initialize WASM parser for ${this.language}: ${error}`); - } - } - - /** - * Get import object for WASM module - */ - protected getImportObject(): WebAssembly.Imports { - return { - env: { - memory: this.wasmMemory, - abort: (msg: number, file: number, line: number, column: number) => { - logger.error(`WASM abort at ${file}:${line}:${column} - ${msg}`); - }, - log: (ptr: number, len: number) => { - const msg = this.readString(ptr, len); - logger.info(`[WASM ${this.language}]:`, msg); - }, - emscripten_memcpy_big: (dest: number, src: number, num: number) => { - const memory = new Uint8Array(this.wasmMemory.buffer); - memory.copyWithin(dest, src, src + num); - }, - emscripten_resize_heap: (size: number) => { - const pages = Math.ceil(size / 65536); - try { - this.wasmMemory.grow(pages); - return 1; - } catch { - return 0; - } - } - }, - wasi_snapshot_preview1: { - proc_exit: (code: number) => { - throw new Error(`WASM process exit with code ${code}`); - }, - fd_close: () => 0, - fd_seek: () => 0, - fd_write: () => 0, - fd_read: () => 0 - } - }; - } - - /** - * Parse content using WASM - */ - async parse(content: string, filePath: string): Promise { - if (!this.wasmInstance) { - throw new Error(`WASM parser for ${this.language} not initialized`); - } - - const startTime = performance.now(); - - try { - // Write content to WASM memory - const contentPtr = this.writeString(content); - const filePathPtr = this.writeString(filePath); - - // Call WASM parse function - const resultPtr = this.wasmExports.parse(contentPtr, content.length, filePathPtr, filePath.length); - - // Read result from WASM memory - const result = this.readParseResult(resultPtr); - - // Free memory - this.wasmExports.free(contentPtr); - this.wasmExports.free(filePathPtr); - this.wasmExports.free(resultPtr); - - // Track performance - const parseTime = performance.now() - startTime; - this.totalParseTime += parseTime; - this.parseCount++; - - this.emit('parse-complete', { - language: this.language, - file: filePath, - time: parseTime, - avgTime: this.totalParseTime / this.parseCount - }); - - return result; - } catch (error) { - this.emit('parse-error', { language: this.language, file: filePath, error }); - - return { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - errors: [{ - message: `WASM parse error: ${error}`, - line: 0, - column: 0 - }] - }; - } - } - - /** - * Write string to WASM memory - */ - protected writeString(str: string): number { - const encoder = new TextEncoder(); - const encoded = encoder.encode(str); - - // Allocate memory in WASM - const ptr = this.wasmExports.malloc(encoded.length + 1); - - // Write to memory - const memory = new Uint8Array(this.wasmMemory.buffer); - memory.set(encoded, ptr); - memory[ptr + encoded.length] = 0; // Null terminator - - return ptr; - } - - /** - * Read string from WASM memory - */ - protected readString(ptr: number, len: number): string { - const memory = new Uint8Array(this.wasmMemory.buffer); - const bytes = memory.slice(ptr, ptr + len); - const decoder = new TextDecoder(); - return decoder.decode(bytes); - } - - /** - * Read parse result from WASM memory - */ - protected abstract readParseResult(ptr: number): ParserResult; - - /** - * Get performance statistics - */ - getStats() { - return { - language: this.language, - parseCount: this.parseCount, - totalParseTime: this.totalParseTime, - averageParseTime: this.parseCount > 0 ? this.totalParseTime / this.parseCount : 0 - }; - } - - /** - * Cleanup WASM resources - */ - cleanup(): void { - if (this.wasmExports && this.wasmExports.cleanup) { - this.wasmExports.cleanup(); - } - - this.wasmModule = null; - this.wasmInstance = null; - this.wasmExports = null; - - this.emit('cleanup', { language: this.language }); - } +/** + * Base WebAssembly Parser + * Provides common functionality for all WASM-based parsers + */ + +import { ParserResult, Parser } from '../../types'; +import { EventEmitter } from 'events'; +import * as fs from 'fs'; +import * as path from 'path'; +import logger from '../../utils/logger'; + +export abstract class BaseWASMParser extends EventEmitter /* implements Parser */ { + abstract language: string; + abstract extensions: string[]; + + protected wasmModule: WebAssembly.Module | null = null; + protected wasmInstance: WebAssembly.Instance | null = null; + protected wasmMemory: WebAssembly.Memory; + protected wasmExports: any; + + // Performance tracking + protected parseCount = 0; + protected totalParseTime = 0; + + constructor() { + super(); + + // Initialize WASM memory with 256 pages (16MB initial, can grow to 1GB) + this.wasmMemory = new WebAssembly.Memory({ + initial: 256, + maximum: 16384 + }); + } + + /** + * Initialize WASM module + */ + async initialize(wasmPath: string): Promise { + try { + const wasmBuffer = fs.readFileSync(wasmPath); + + // Compile WASM module + this.wasmModule = await WebAssembly.compile(wasmBuffer); + + // Create import object for WASM + const importObject = this.getImportObject(); + + // Instantiate WASM module + this.wasmInstance = await WebAssembly.instantiate(this.wasmModule, importObject); + this.wasmExports = this.wasmInstance.exports; + + // Initialize the parser in WASM + if (this.wasmExports.initialize) { + this.wasmExports.initialize(); + } + + this.emit('initialized', { language: this.language }); + } catch (error) { + this.emit('initialization-error', { language: this.language, error }); + throw new Error(`Failed to initialize WASM parser for ${this.language}: ${error}`); + } + } + + /** + * Get import object for WASM module + */ + protected getImportObject(): WebAssembly.Imports { + return { + env: { + memory: this.wasmMemory, + abort: (msg: number, file: number, line: number, column: number) => { + logger.error(`WASM abort at ${file}:${line}:${column} - ${msg}`); + }, + log: (ptr: number, len: number) => { + const msg = this.readString(ptr, len); + logger.info(`[WASM ${this.language}]:`, msg); + }, + emscripten_memcpy_big: (dest: number, src: number, num: number) => { + const memory = new Uint8Array(this.wasmMemory.buffer); + memory.copyWithin(dest, src, src + num); + }, + emscripten_resize_heap: (size: number) => { + const pages = Math.ceil(size / 65536); + try { + this.wasmMemory.grow(pages); + return 1; + } catch { + return 0; + } + } + }, + wasi_snapshot_preview1: { + proc_exit: (code: number) => { + throw new Error(`WASM process exit with code ${code}`); + }, + fd_close: () => 0, + fd_seek: () => 0, + fd_write: () => 0, + fd_read: () => 0 + } + }; + } + + /** + * Parse content using WASM + */ + async parse(content: string, filePath: string): Promise { + if (!this.wasmInstance) { + throw new Error(`WASM parser for ${this.language} not initialized`); + } + + const startTime = performance.now(); + + try { + // Write content to WASM memory + const contentPtr = this.writeString(content); + const filePathPtr = this.writeString(filePath); + + // Call WASM parse function + const resultPtr = this.wasmExports.parse(contentPtr, content.length, filePathPtr, filePath.length); + + // Read result from WASM memory + const result = this.readParseResult(resultPtr); + + // Free memory + this.wasmExports.free(contentPtr); + this.wasmExports.free(filePathPtr); + this.wasmExports.free(resultPtr); + + // Track performance + const parseTime = performance.now() - startTime; + this.totalParseTime += parseTime; + this.parseCount++; + + this.emit('parse-complete', { + language: this.language, + file: filePath, + time: parseTime, + avgTime: this.totalParseTime / this.parseCount + }); + + return result; + } catch (error) { + this.emit('parse-error', { language: this.language, file: filePath, error }); + + return { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + errors: [{ + message: `WASM parse error: ${error}`, + line: 0 + }] + }; + } + } + + /** + * Write string to WASM memory + */ + protected writeString(str: string): number { + const encoder = new TextEncoder(); + const encoded = encoder.encode(str); + + // Allocate memory in WASM + const ptr = this.wasmExports.malloc(encoded.length + 1); + + // Write to memory + const memory = new Uint8Array(this.wasmMemory.buffer); + memory.set(encoded, ptr); + memory[ptr + encoded.length] = 0; // Null terminator + + return ptr; + } + + /** + * Read string from WASM memory + */ + protected readString(ptr: number, len: number): string { + const memory = new Uint8Array(this.wasmMemory.buffer); + const bytes = memory.slice(ptr, ptr + len); + const decoder = new TextDecoder(); + return decoder.decode(bytes); + } + + /** + * Read parse result from WASM memory + */ + protected abstract readParseResult(ptr: number): ParserResult; + + /** + * Get performance statistics + */ + getStats() { + return { + language: this.language, + parseCount: this.parseCount, + totalParseTime: this.totalParseTime, + averageParseTime: this.parseCount > 0 ? this.totalParseTime / this.parseCount : 0 + }; + } + + /** + * Cleanup WASM resources + */ + cleanup(): void { + if (this.wasmExports && this.wasmExports.cleanup) { + this.wasmExports.cleanup(); + } + + this.wasmModule = null; + this.wasmInstance = null; + this.wasmExports = null; + + this.emit('cleanup', { language: this.language }); + } } \ No newline at end of file diff --git a/src/parsers/wasm/wasm-parser-factory.ts b/src/parsers/wasm/wasm-parser-factory.ts index 7684795..dfc5859 100644 --- a/src/parsers/wasm/wasm-parser-factory.ts +++ b/src/parsers/wasm/wasm-parser-factory.ts @@ -1,397 +1,397 @@ -/** - * WebAssembly Parser Factory - * Manages WASM parser initialization and selection - */ - -import { Parser, ParserResult } from '../../types'; -import { BaseWASMParser } from './base-wasm-parser'; -import { JavaScriptWASMParser } from './javascript-wasm-parser'; -import { PythonWASMParser } from './python-wasm-parser'; -import { GoWASMParser } from './go-wasm-parser'; -import * as path from 'path'; -import * as fs from 'fs'; -import { EventEmitter } from 'events'; -import logger from '../../../utils/logger'; - -export interface WASMParserConfig { - enabled: boolean; - wasmDir: string; - fallbackToNative: boolean; - preloadParsers: boolean; - maxMemory: number; // MB - cacheParsedResults: boolean; -} - -export class WASMParserFactory extends EventEmitter { - private parsers: Map = new Map(); - private nativeParsers: Map = new Map(); - private config: WASMParserConfig; - private initialized = false; - private initPromise: Promise | null = null; - private parseCache: Map = new Map(); - private readonly CACHE_TTL = 60000; // 1 minute - - constructor(config: Partial = {}) { - super(); - - this.config = { - enabled: true, - wasmDir: path.join(__dirname, '../../../wasm'), - fallbackToNative: true, - preloadParsers: true, - maxMemory: 512, // MB - cacheParsedResults: true, - ...config - }; - } - - /** - * Initialize WASM parser factory - */ - async initialize(nativeParsers?: Map): Promise { - if (this.initialized) return; - if (this.initPromise) return this.initPromise; - - this.initPromise = this._initialize(nativeParsers); - await this.initPromise; - this.initialized = true; - } - - private async _initialize(nativeParsers?: Map): Promise { - // Store native parsers for fallback - if (nativeParsers) { - this.nativeParsers = nativeParsers; - } - - if (!this.config.enabled) { - logger.info('WASM parsers disabled, using native parsers only'); - return; - } - - // Check if WASM directory exists - if (!fs.existsSync(this.config.wasmDir)) { - logger.warn(`WASM directory not found: ${this.config.wasmDir}`); - if (!this.config.fallbackToNative) { - throw new Error('WASM directory not found and fallback disabled'); - } - return; - } - - // Create parser instances - const parserInstances = [ - { parser: new JavaScriptWASMParser(), wasmFile: 'javascript.wasm' }, - { parser: new PythonWASMParser(), wasmFile: 'python.wasm' }, - { parser: new GoWASMParser(), wasmFile: 'go.wasm' } - ]; - - // Initialize parsers - const initPromises: Promise[] = []; - - for (const { parser, wasmFile } of parserInstances) { - const wasmPath = path.join(this.config.wasmDir, wasmFile); - - if (!fs.existsSync(wasmPath)) { - logger.warn(`WASM file not found: ${wasmPath}`); - continue; - } - - // Register parser for its extensions - for (const ext of parser.extensions) { - this.parsers.set(ext, parser); - } - - // Initialize if preloading is enabled - if (this.config.preloadParsers) { - initPromises.push( - parser.initialize(wasmPath).catch(error => { - logger.error(`Failed to initialize ${parser.language} WASM parser:`, error); - // Remove from parsers map if initialization fails - for (const ext of parser.extensions) { - this.parsers.delete(ext); - } - }) - ); - } - } - - if (initPromises.length > 0) { - await Promise.all(initPromises); - logger.info(`Initialized ${initPromises.length} WASM parsers`); - } - - // Set up memory monitoring - this.setupMemoryMonitoring(); - - // Set up cache cleanup - if (this.config.cacheParsedResults) { - this.setupCacheCleanup(); - } - - this.emit('initialized', { - wasmParsers: Array.from(this.parsers.keys()), - nativeParsers: Array.from(this.nativeParsers.keys()) - }); - } - - /** - * Get parser for file - */ - async getParser(filePath: string): Promise { - const ext = path.extname(filePath); - - // Try WASM parser first - if (this.config.enabled && this.parsers.has(ext)) { - const parser = this.parsers.get(ext)!; - - // Check if parser is initialized - if (!this.config.preloadParsers) { - const wasmFile = `${parser.language.toLowerCase()}.wasm`; - const wasmPath = path.join(this.config.wasmDir, wasmFile); - - if (fs.existsSync(wasmPath)) { - try { - await parser.initialize(wasmPath); - } catch (error) { - logger.error(`Failed to initialize ${parser.language} WASM parser:`, error); - this.parsers.delete(ext); - } - } - } - - if (this.parsers.has(ext)) { - return this.createCachedParser(this.parsers.get(ext)!); - } - } - - // Fall back to native parser - if (this.config.fallbackToNative && this.nativeParsers.has(ext)) { - return this.nativeParsers.get(ext)!; - } - - return null; - } - - /** - * Create cached parser wrapper - */ - private createCachedParser(parser: BaseWASMParser): Parser { - if (!this.config.cacheParsedResults) { - return parser; - } - - return { - language: parser.language, - extensions: parser.extensions, - parse: async (content: string, filePath: string) => { - const cacheKey = `${filePath}:${content.length}:${this.hashContent(content)}`; - - // Check cache - const cached = this.parseCache.get(cacheKey); - if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) { - this.emit('cache-hit', { file: filePath }); - return cached.result; - } - - // Parse with WASM - const result = await parser.parse(content, filePath); - - // Cache result - this.parseCache.set(cacheKey, { - result, - timestamp: Date.now() - }); - - return result; - } - }; - } - - /** - * Simple hash function for content - */ - private hashContent(content: string): number { - let hash = 0; - for (let i = 0; i < Math.min(content.length, 1000); i++) { - hash = ((hash << 5) - hash) + content.charCodeAt(i); - hash = hash & hash; // Convert to 32bit integer - } - return hash; - } - - /** - * Parse file with automatic parser selection - */ - async parse(filePath: string, content: string): Promise { - const parser = await this.getParser(filePath); - - if (!parser) { - return null; - } - - try { - return await parser.parse(content, filePath); - } catch (error) { - this.emit('parse-error', { file: filePath, error }); - - // Try fallback if available - if (this.config.fallbackToNative) { - const ext = path.extname(filePath); - const nativeParser = this.nativeParsers.get(ext); - - if (nativeParser && nativeParser !== parser) { - logger.info(`Falling back to native parser for ${filePath}`); - return await nativeParser.parse(content, filePath); - } - } - - throw error; - } - } - - /** - * Get performance statistics - */ - getStats() { - const stats: any = { - initialized: this.initialized, - wasmEnabled: this.config.enabled, - cacheEnabled: this.config.cacheParsedResults, - cacheSize: this.parseCache.size, - parsers: {} - }; - - for (const [ext, parser] of this.parsers) { - if (!stats.parsers[parser.language]) { - stats.parsers[parser.language] = parser.getStats(); - } - } - - return stats; - } - - /** - * Setup memory monitoring - */ - private setupMemoryMonitoring(): void { - setInterval(() => { - const usage = process.memoryUsage(); - const heapUsedMB = usage.heapUsed / 1024 / 1024; - - if (heapUsedMB > this.config.maxMemory) { - logger.warn(`Memory usage (${heapUsedMB.toFixed(2)}MB) exceeds limit (${this.config.maxMemory}MB)`); - - // Clear cache to free memory - if (this.config.cacheParsedResults) { - const oldSize = this.parseCache.size; - this.parseCache.clear(); - logger.info(`Cleared parse cache (${oldSize} entries) to free memory`); - } - - // Force garbage collection if available - if (global.gc) { - global.gc(); - } - - this.emit('memory-pressure', { usage: heapUsedMB, limit: this.config.maxMemory }); - } - }, 30000); // Check every 30 seconds - } - - /** - * Setup cache cleanup - */ - private setupCacheCleanup(): void { - setInterval(() => { - const now = Date.now(); - const expired: string[] = []; - - for (const [key, entry] of this.parseCache) { - if (now - entry.timestamp > this.CACHE_TTL) { - expired.push(key); - } - } - - for (const key of expired) { - this.parseCache.delete(key); - } - - if (expired.length > 0) { - this.emit('cache-cleanup', { removed: expired.length, remaining: this.parseCache.size }); - } - }, 60000); // Cleanup every minute - } - - /** - * Benchmark WASM vs native parsers - */ - async benchmark(testFiles: { path: string; content: string }[]): Promise { - const results: any = { - wasm: {}, - native: {}, - comparison: {} - }; - - for (const file of testFiles) { - const ext = path.extname(file.path); - - // Benchmark WASM parser - const wasmParser = this.parsers.get(ext); - if (wasmParser) { - const wasmStart = performance.now(); - await wasmParser.parse(file.content, file.path); - const wasmTime = performance.now() - wasmStart; - - if (!results.wasm[wasmParser.language]) { - results.wasm[wasmParser.language] = []; - } - results.wasm[wasmParser.language].push(wasmTime); - } - - // Benchmark native parser - const nativeParser = this.nativeParsers.get(ext); - if (nativeParser) { - const nativeStart = performance.now(); - await nativeParser.parse(file.content, file.path); - const nativeTime = performance.now() - nativeStart; - - if (!results.native[nativeParser.language]) { - results.native[nativeParser.language] = []; - } - results.native[nativeParser.language].push(nativeTime); - } - } - - // Calculate averages and speedup - for (const language of Object.keys(results.wasm)) { - const wasmAvg = results.wasm[language].reduce((a: number, b: number) => a + b, 0) / results.wasm[language].length; - const nativeAvg = results.native[language]?.reduce((a: number, b: number) => a + b, 0) / results.native[language]?.length || 0; - - results.comparison[language] = { - wasmAverage: wasmAvg, - nativeAverage: nativeAvg, - speedup: nativeAvg / wasmAvg, - improvement: `${((1 - wasmAvg / nativeAvg) * 100).toFixed(1)}%` - }; - } - - return results; - } - - /** - * Cleanup resources - */ - cleanup(): void { - for (const parser of this.parsers.values()) { - parser.cleanup(); - } - - this.parsers.clear(); - this.parseCache.clear(); - this.initialized = false; - - this.emit('cleanup'); - } -} - -// Export singleton instance +/** + * WebAssembly Parser Factory + * Manages WASM parser initialization and selection + */ + +import { Parser, ParserResult } from '../../types'; +import { BaseWASMParser } from './base-wasm-parser'; +// import { JavaScriptWASMParser } from './javascript-wasm-parser'; // Missing file +// import { PythonWASMParser } from './python-wasm-parser'; // Missing file +// import { GoWASMParser } from './go-wasm-parser'; // Missing file +import * as path from 'path'; +import * as fs from 'fs'; +import { EventEmitter } from 'events'; +import logger from '../../utils/logger'; + +export interface WASMParserConfig { + enabled: boolean; + wasmDir: string; + fallbackToNative: boolean; + preloadParsers: boolean; + maxMemory: number; // MB + cacheParsedResults: boolean; +} + +export class WASMParserFactory extends EventEmitter { + private parsers: Map = new Map(); + private nativeParsers: Map = new Map(); + private config: WASMParserConfig; + private initialized = false; + private initPromise: Promise | null = null; + private parseCache: Map = new Map(); + private readonly CACHE_TTL = 60000; // 1 minute + + constructor(config: Partial = {}) { + super(); + + this.config = { + enabled: true, + wasmDir: path.join(__dirname, '../../../wasm'), + fallbackToNative: true, + preloadParsers: true, + maxMemory: 512, // MB + cacheParsedResults: true, + ...config + }; + } + + /** + * Initialize WASM parser factory + */ + async initialize(nativeParsers?: Map): Promise { + if (this.initialized) return; + if (this.initPromise) return this.initPromise; + + this.initPromise = this._initialize(nativeParsers); + await this.initPromise; + this.initialized = true; + } + + private async _initialize(nativeParsers?: Map): Promise { + // Store native parsers for fallback + if (nativeParsers) { + this.nativeParsers = nativeParsers; + } + + if (!this.config.enabled) { + logger.info('WASM parsers disabled, using native parsers only'); + return; + } + + // Check if WASM directory exists + if (!fs.existsSync(this.config.wasmDir)) { + logger.warn(`WASM directory not found: ${this.config.wasmDir}`); + if (!this.config.fallbackToNative) { + throw new Error('WASM directory not found and fallback disabled'); + } + return; + } + + // Create parser instances + const parserInstances: Array<{ parser: BaseWASMParser; wasmFile: string }> = [ + // { parser: new JavaScriptWASMParser(), wasmFile: 'javascript.wasm' }, // Missing class + // { parser: new PythonWASMParser(), wasmFile: 'python.wasm' }, // Missing class + // { parser: new GoWASMParser(), wasmFile: 'go.wasm' } // Missing class + ]; + + // Initialize parsers + const initPromises: Promise[] = []; + + for (const { parser, wasmFile } of parserInstances) { + const wasmPath = path.join(this.config.wasmDir, wasmFile); + + if (!fs.existsSync(wasmPath)) { + logger.warn(`WASM file not found: ${wasmPath}`); + continue; + } + + // Register parser for its extensions + for (const ext of parser.extensions) { + this.parsers.set(ext, parser); + } + + // Initialize if preloading is enabled + if (this.config.preloadParsers) { + initPromises.push( + parser.initialize(wasmPath).catch(error => { + logger.error(`Failed to initialize ${parser.language} WASM parser:`, error); + // Remove from parsers map if initialization fails + for (const ext of parser.extensions) { + this.parsers.delete(ext); + } + }) + ); + } + } + + if (initPromises.length > 0) { + await Promise.all(initPromises); + logger.info(`Initialized ${initPromises.length} WASM parsers`); + } + + // Set up memory monitoring + this.setupMemoryMonitoring(); + + // Set up cache cleanup + if (this.config.cacheParsedResults) { + this.setupCacheCleanup(); + } + + this.emit('initialized', { + wasmParsers: Array.from(this.parsers.keys()), + nativeParsers: Array.from(this.nativeParsers.keys()) + }); + } + + /** + * Get parser for file + */ + async getParser(filePath: string): Promise { + const ext = path.extname(filePath); + + // Try WASM parser first + if (this.config.enabled && this.parsers.has(ext)) { + const parser = this.parsers.get(ext)!; + + // Check if parser is initialized + if (!this.config.preloadParsers) { + const wasmFile = `${parser.language.toLowerCase()}.wasm`; + const wasmPath = path.join(this.config.wasmDir, wasmFile); + + if (fs.existsSync(wasmPath)) { + try { + await parser.initialize(wasmPath); + } catch (error) { + logger.error(`Failed to initialize ${parser.language} WASM parser:`, error); + this.parsers.delete(ext); + } + } + } + + if (this.parsers.has(ext)) { + return this.createCachedParser(this.parsers.get(ext)!); + } + } + + // Fall back to native parser + if (this.config.fallbackToNative && this.nativeParsers.has(ext)) { + return this.nativeParsers.get(ext)!; + } + + return null; + } + + /** + * Create cached parser wrapper + */ + private createCachedParser(parser: BaseWASMParser): any /* Parser */ { + if (!this.config.cacheParsedResults) { + return parser as any; + } + + return { + language: parser.language, + extensions: parser.extensions, + parse: async (content: string, filePath: string): Promise => { + const cacheKey = `${filePath}:${content.length}:${this.hashContent(content)}`; + + // Check cache + const cached = this.parseCache.get(cacheKey); + if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) { + this.emit('cache-hit', { file: filePath }); + return cached.result; + } + + // Parse with WASM + const result = await parser.parse(content, filePath); + + // Cache result + this.parseCache.set(cacheKey, { + result, + timestamp: Date.now() + }); + + return result; + } + }; + } + + /** + * Simple hash function for content + */ + private hashContent(content: string): number { + let hash = 0; + for (let i = 0; i < Math.min(content.length, 1000); i++) { + hash = ((hash << 5) - hash) + content.charCodeAt(i); + hash = hash & hash; // Convert to 32bit integer + } + return hash; + } + + /** + * Parse file with automatic parser selection + */ + async parse(filePath: string, content: string): Promise { + const parser = await this.getParser(filePath); + + if (!parser) { + return null; + } + + try { + return await parser.parse(content, filePath); + } catch (error) { + this.emit('parse-error', { file: filePath, error }); + + // Try fallback if available + if (this.config.fallbackToNative) { + const ext = path.extname(filePath); + const nativeParser = this.nativeParsers.get(ext); + + if (nativeParser && nativeParser !== parser) { + logger.info(`Falling back to native parser for ${filePath}`); + return await nativeParser.parse(content, filePath); + } + } + + throw error; + } + } + + /** + * Get performance statistics + */ + getStats() { + const stats: any = { + initialized: this.initialized, + wasmEnabled: this.config.enabled, + cacheEnabled: this.config.cacheParsedResults, + cacheSize: this.parseCache.size, + parsers: {} + }; + + for (const [ext, parser] of this.parsers) { + if (!stats.parsers[parser.language]) { + stats.parsers[parser.language] = parser.getStats(); + } + } + + return stats; + } + + /** + * Setup memory monitoring + */ + private setupMemoryMonitoring(): void { + setInterval(() => { + const usage = process.memoryUsage(); + const heapUsedMB = usage.heapUsed / 1024 / 1024; + + if (heapUsedMB > this.config.maxMemory) { + logger.warn(`Memory usage (${heapUsedMB.toFixed(2)}MB) exceeds limit (${this.config.maxMemory}MB)`); + + // Clear cache to free memory + if (this.config.cacheParsedResults) { + const oldSize = this.parseCache.size; + this.parseCache.clear(); + logger.info(`Cleared parse cache (${oldSize} entries) to free memory`); + } + + // Force garbage collection if available + if (global.gc) { + global.gc(); + } + + this.emit('memory-pressure', { usage: heapUsedMB, limit: this.config.maxMemory }); + } + }, 30000); // Check every 30 seconds + } + + /** + * Setup cache cleanup + */ + private setupCacheCleanup(): void { + setInterval(() => { + const now = Date.now(); + const expired: string[] = []; + + for (const [key, entry] of this.parseCache) { + if (now - entry.timestamp > this.CACHE_TTL) { + expired.push(key); + } + } + + for (const key of expired) { + this.parseCache.delete(key); + } + + if (expired.length > 0) { + this.emit('cache-cleanup', { removed: expired.length, remaining: this.parseCache.size }); + } + }, 60000); // Cleanup every minute + } + + /** + * Benchmark WASM vs native parsers + */ + async benchmark(testFiles: { path: string; content: string }[]): Promise { + const results: any = { + wasm: {}, + native: {}, + comparison: {} + }; + + for (const file of testFiles) { + const ext = path.extname(file.path); + + // Benchmark WASM parser + const wasmParser = this.parsers.get(ext); + if (wasmParser) { + const wasmStart = performance.now(); + await wasmParser.parse(file.content, file.path); + const wasmTime = performance.now() - wasmStart; + + if (!results.wasm[wasmParser.language]) { + results.wasm[wasmParser.language] = []; + } + results.wasm[wasmParser.language].push(wasmTime); + } + + // Benchmark native parser + const nativeParser = this.nativeParsers.get(ext); + if (nativeParser) { + const nativeStart = performance.now(); + await nativeParser.parse(file.content, file.path); + const nativeTime = performance.now() - nativeStart; + + if (!results.native[nativeParser.language]) { + results.native[nativeParser.language] = []; + } + results.native[nativeParser.language].push(nativeTime); + } + } + + // Calculate averages and speedup + for (const language of Object.keys(results.wasm)) { + const wasmAvg = results.wasm[language].reduce((a: number, b: number) => a + b, 0) / results.wasm[language].length; + const nativeAvg = results.native[language]?.reduce((a: number, b: number) => a + b, 0) / results.native[language]?.length || 0; + + results.comparison[language] = { + wasmAverage: wasmAvg, + nativeAverage: nativeAvg, + speedup: nativeAvg / wasmAvg, + improvement: `${((1 - wasmAvg / nativeAvg) * 100).toFixed(1)}%` + }; + } + + return results; + } + + /** + * Cleanup resources + */ + cleanup(): void { + for (const parser of this.parsers.values()) { + parser.cleanup(); + } + + this.parsers.clear(); + this.parseCache.clear(); + this.initialized = false; + + this.emit('cleanup'); + } +} + +// Export singleton instance export const wasmParserFactory = new WASMParserFactory(); \ No newline at end of file diff --git a/src/parsers/yaml.ts b/src/parsers/yaml.ts index d039a8c..b87cd87 100644 --- a/src/parsers/yaml.ts +++ b/src/parsers/yaml.ts @@ -1,619 +1,619 @@ -import { ParserResult, FunctionInfo, ClassInfo} from '../types'; -import { Parser } from '../types'; - -export class YAMLParser implements Parser { - language = 'YAML'; - extensions = ['.yaml', '.yml']; - - parse(content: string, filePath: string): ParserResult { - const result: ParserResult = { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - errors: [] - }; - - try { - const lines = content.split('\n'); - const structure = this.parseYAMLStructure(lines); - - // Detect special YAML file types - const fileType = this.detectFileType(structure, filePath); - - switch (fileType) { - case 'docker-compose': - this.parseDockerCompose(structure, result); - break; - case 'kubernetes': - this.parseKubernetes(structure, result); - break; - case 'github-actions': - this.parseGitHubActions(structure, result); - break; - case 'openapi': - this.parseOpenAPI(structure, result); - break; - case 'serverless': - this.parseServerless(structure, result); - break; - case 'ansible': - this.parseAnsible(structure, result); - break; - default: - this.parseGenericYAML(structure, result); - } - - } catch (error: any) { - result.errors.push(`Parse error: ${error.message}`); - } - - return result; - } - - private parseYAMLStructure(lines: string[]): any { - const structure: any = {}; - const stack: any[] = [{ obj: structure, indent: -1 }]; - let currentList: any[] | null = null; - let currentListIndent = -1; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const trimmed = line.trim(); - - // Skip comments and empty lines - if (trimmed.startsWith('#') || trimmed === '') { - continue; - } - - const indent = line.length - line.trimStart().length; - - // Handle list items - if (trimmed.startsWith('- ')) { - const value = trimmed.substring(2).trim(); - - // Find the right parent based on indentation - while (stack.length > 1 && stack[stack.length - 1].indent >= indent) { - stack.pop(); - } - - const parent = stack[stack.length - 1].obj; - const lastKey = stack[stack.length - 1].lastKey; - - if (lastKey && parent[lastKey]) { - if (!Array.isArray(parent[lastKey])) { - parent[lastKey] = [parent[lastKey]]; - } - - if (value.includes(':')) { - // List item is an object - const obj: any = {}; - parent[lastKey].push(obj); - stack.push({ obj, indent }); - - // Parse the key-value in the list item - const [key, val] = value.split(':').map(s => s.trim()); - if (key && val) { - obj[key] = this.parseValue(val); - } - } else { - parent[lastKey].push(this.parseValue(value)); - } - } - continue; - } - - // Handle key-value pairs - const keyValueMatch = trimmed.match(/^([^:]+):\s*(.*)$/); - if (keyValueMatch) { - const key = keyValueMatch[1].trim(); - const value = keyValueMatch[2].trim(); - - // Find the right parent based on indentation - while (stack.length > 1 && stack[stack.length - 1].indent >= indent) { - stack.pop(); - } - - const parent = stack[stack.length - 1].obj; - - if (value) { - // Direct value - parent[key] = this.parseValue(value); - } else { - // Nested object or array (value will come in next lines) - parent[key] = {}; - stack.push({ obj: parent[key], indent, lastKey: key }); - } - - // Update lastKey for potential list items - stack[stack.length - 1].lastKey = key; - } - } - - return structure; - } - - private parseValue(value: string): any { - // Remove quotes if present - if ((value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'"))) { - return value.slice(1, -1); - } - - // Boolean values - if (value === 'true' || value === 'yes' || value === 'on') return true; - if (value === 'false' || value === 'no' || value === 'off') return false; - - // Null values - if (value === 'null' || value === '~') return null; - - // Numbers - if (/^-?\d+$/.test(value)) return parseInt(value, 10); - if (/^-?\d+\.\d+$/.test(value)) return parseFloat(value); - - // Arrays (inline) - if (value.startsWith('[') && value.endsWith(']')) { - return value.slice(1, -1).split(',').map(v => this.parseValue(v.trim())); - } - - // Objects (inline) - if (value.startsWith('{') && value.endsWith('}')) { - const obj: any = {}; - const pairs = value.slice(1, -1).split(','); - for (const pair of pairs) { - const [k, v] = pair.split(':').map(s => s.trim()); - if (k && v) { - obj[k] = this.parseValue(v); - } - } - return obj; - } - - return value; - } - - private detectFileType(structure: any, filePath: string): string { - // Docker Compose - if (structure.version && structure.services) { - return 'docker-compose'; - } - - // Kubernetes - if (structure.apiVersion && structure.kind) { - return 'kubernetes'; - } - - // GitHub Actions - if (filePath.includes('.github/workflows') || structure.on || structure.jobs) { - return 'github-actions'; - } - - // OpenAPI/Swagger - if (structure.openapi || structure.swagger) { - return 'openapi'; - } - - // Serverless Framework - if (structure.service && structure.provider && structure.functions) { - return 'serverless'; - } - - // Ansible - if (Array.isArray(structure) && structure[0]?.tasks) { - return 'ansible'; - } - - return 'generic'; - } - - private parseDockerCompose(structure: any, result: ParserResult): void { - // Extract services as classes - if (structure.services) { - for (const [serviceName, serviceConfig] of Object.entries(structure.services)) { - const service: ClassInfo = { - name: serviceName, - methods: [], - properties: [], - extends: (serviceConfig as any).extends, - implements: [], - startLine: 0, - endLine: 0, - type: 'docker-service' - }; - - // Add image as a dependency - if ((serviceConfig as any).image) { - result.dependencies.push((serviceConfig as any).image); - } - - // Add properties for important configs - if ((serviceConfig as any).ports) { - service.properties.push({ - name: 'ports', - type: 'array', - visibility: 'public', - static: false, - value: (serviceConfig as any).ports - }); - } - - if ((serviceConfig as any).environment) { - service.properties.push({ - name: 'environment', - type: 'object', - visibility: 'public', - static: false - }); - } - - result.classes.push(service); - result.exports.push({ - name: serviceName, - type: 'service' - }); - } - } - - // Extract networks - if (structure.networks) { - for (const networkName of Object.keys(structure.networks)) { - result.constants.push({ - name: `network_${networkName}`, - type: 'network', - value: networkName - }); - } - } - - // Extract volumes - if (structure.volumes) { - for (const volumeName of Object.keys(structure.volumes)) { - result.constants.push({ - name: `volume_${volumeName}`, - type: 'volume', - value: volumeName - }); - } - } - } - - private parseKubernetes(structure: any, result: ParserResult): void { - const kind = structure.kind; - const name = structure.metadata?.name || 'unnamed'; - - // Create a class for the Kubernetes resource - const resource: ClassInfo = { - name: `${kind}_${name}`, - methods: [], - properties: [], - extends: undefined, - implements: [], - startLine: 0, - endLine: 0, - type: `k8s-${kind.toLowerCase()}` - }; - - // Add metadata as properties - if (structure.metadata) { - for (const [key, value] of Object.entries(structure.metadata)) { - resource.properties.push({ - name: key, - type: typeof value, - visibility: 'public', - static: false, - value: String(value) - }); - } - } - - // Handle specific kinds - if (kind === 'Deployment' || kind === 'StatefulSet' || kind === 'DaemonSet') { - if (structure.spec?.template?.spec?.containers) { - for (const container of structure.spec.template.spec.containers) { - result.dependencies.push(container.image); - } - } - } - - if (kind === 'Service') { - if (structure.spec?.ports) { - resource.properties.push({ - name: 'ports', - type: 'array', - visibility: 'public', - static: false, - value: structure.spec.ports - }); - } - } - - result.classes.push(resource); - result.exports.push({ - name: resource.name, - type: kind.toLowerCase() - }); - } - - private parseGitHubActions(structure: any, result: ParserResult): void { - const workflowName = structure.name || 'workflow'; - - // Extract triggers - if (structure.on) { - result.constants.push({ - name: 'triggers', - type: 'triggers', - value: JSON.stringify(structure.on) - }); - } - - // Extract jobs as functions - if (structure.jobs) { - for (const [jobName, jobConfig] of Object.entries(structure.jobs)) { - const job: FunctionInfo = { - name: jobName, - parameters: [], - returnType: 'void', - async: true, - generator: false, - startLine: 0, - endLine: 0 - }; - - // Extract steps - if ((jobConfig as any).steps) { - for (const step of (jobConfig as any).steps) { - if (step.uses) { - // GitHub Action dependency - result.dependencies.push(step.uses); - } - } - } - - result.functions.push(job); - result.exports.push({ - name: jobName, - type: 'job' - }); - } - } - - // Extract environment variables - if (structure.env) { - for (const [key, value] of Object.entries(structure.env)) { - result.constants.push({ - name: key, - type: 'env', - value: String(value) - }); - } - } - } - - private parseOpenAPI(structure: any, result: ParserResult): void { - // Extract API info - if (structure.info) { - result.constants.push({ - name: 'api_info', - type: 'info', - value: JSON.stringify(structure.info) - }); - } - - // Extract paths as functions - if (structure.paths) { - for (const [path, pathConfig] of Object.entries(structure.paths)) { - for (const [method, operation] of Object.entries(pathConfig as any)) { - if (typeof operation === 'object' && operation !== null) { - const op = operation as any; - const operationId = op.operationId || `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`; - - const func: FunctionInfo = { - name: operationId, - parameters: [], - returnType: 'response', - async: true, - generator: false, - startLine: 0, - endLine: 0, - httpMethod: method.toUpperCase(), - httpPath: path - }; - - // Extract parameters - if (op.parameters && Array.isArray(op.parameters)) { - for (const param of op.parameters) { - func.parameters.push({ - name: param.name, - type: param.type || 'any' - }); - } - } - - result.functions.push(func); - result.exports.push({ - name: operationId, - type: 'endpoint' - }); - } - } - } - } - - // Extract schemas/components as classes - const schemas = structure.components?.schemas || structure.definitions; - if (schemas) { - for (const [schemaName, schemaConfig] of Object.entries(schemas)) { - const schema: ClassInfo = { - name: schemaName, - methods: [], - properties: [], - extends: undefined, - implements: [], - startLine: 0, - endLine: 0, - type: 'schema' - }; - - if ((schemaConfig as any).properties) { - for (const [propName, propConfig] of Object.entries((schemaConfig as any).properties)) { - schema.properties.push({ - name: propName, - type: (propConfig as any).type || 'any', - visibility: 'public', - static: false, - required: (schemaConfig as any).required?.includes(propName) - }); - } - } - - result.classes.push(schema); - result.exports.push({ - name: schemaName, - type: 'schema' - }); - } - } - } - - private parseServerless(structure: any, result: ParserResult): void { - // Extract service name - if (structure.service) { - result.constants.push({ - name: 'service_name', - type: 'service', - value: structure.service - }); - } - - // Extract functions - if (structure.functions) { - for (const [funcName, funcConfig] of Object.entries(structure.functions)) { - const func: FunctionInfo = { - name: funcName, - parameters: [], - returnType: 'any', - async: true, - generator: false, - startLine: 0, - endLine: 0, - handler: (funcConfig as any).handler - }; - - // Extract events - if ((funcConfig as any).events) { - for (const event of (funcConfig as any).events) { - if (event.http) { - func.httpMethod = event.http.method; - func.httpPath = event.http.path; - } - } - } - - result.functions.push(func); - result.exports.push({ - name: funcName, - type: 'function' - }); - } - } - - // Extract resources - if (structure.resources?.Resources) { - for (const [resourceName, resourceConfig] of Object.entries(structure.resources.Resources)) { - result.constants.push({ - name: resourceName, - type: (resourceConfig as any).Type, - value: JSON.stringify((resourceConfig as any).Properties) - }); - } - } - } - - private parseAnsible(structure: any, result: ParserResult): void { - // Handle array of plays - const plays = Array.isArray(structure) ? structure : [structure]; - - for (const play of plays) { - if (play.name) { - const playClass: ClassInfo = { - name: play.name.replace(/[^a-zA-Z0-9]/g, '_'), - methods: [], - properties: [], - extends: undefined, - implements: [], - startLine: 0, - endLine: 0, - type: 'ansible-play' - }; - - // Extract tasks as methods - if (play.tasks) { - for (const task of play.tasks) { - if (task.name) { - playClass.methods.push({ - name: task.name.replace(/[^a-zA-Z0-9]/g, '_'), - visibility: 'public', - static: false, - module: Object.keys(task).find(k => k !== 'name' && k !== 'register' && k !== 'when') - }); - } - } - } - - result.classes.push(playClass); - result.exports.push({ - name: playClass.name, - type: 'playbook' - }); - } - } - } - - private parseGenericYAML(structure: any, result: ParserResult): void { - // Extract top-level keys as constants - for (const [key, value] of Object.entries(structure)) { - if (typeof value === 'object' && value !== null && !Array.isArray(value)) { - // Complex object - treat as a class - const cls: ClassInfo = { - name: key, - methods: [], - properties: [], - extends: undefined, - implements: [], - startLine: 0, - endLine: 0 - }; - - // Add nested keys as properties - for (const [propKey, propValue] of Object.entries(value as Record)) { - cls.properties.push({ - name: propKey, - type: Array.isArray(propValue) ? 'array' : typeof propValue, - visibility: 'public', - static: false - }); - } - - result.classes.push(cls); - result.exports.push({ - name: key, - type: 'object' - }); - } else { - // Simple value - treat as constant - result.constants.push({ - name: key, - type: Array.isArray(value) ? 'array' : typeof value, - value: Array.isArray(value) ? `[${value.length} items]` : String(value) - }); - - result.exports.push({ - name: key, - type: 'constant' - }); - } - } - } +import { ParserResult, FunctionInfo, ClassInfo} from '../types'; +import { Parser } from '../types'; + +export class YAMLParser implements Parser { + language = 'YAML'; + extensions = ['.yaml', '.yml']; + + parse(content: string, filePath: string): ParserResult { + const result: ParserResult = { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + errors: [] + }; + + try { + const lines = content.split('\n'); + const structure = this.parseYAMLStructure(lines); + + // Detect special YAML file types + const fileType = this.detectFileType(structure, filePath); + + switch (fileType) { + case 'docker-compose': + this.parseDockerCompose(structure, result); + break; + case 'kubernetes': + this.parseKubernetes(structure, result); + break; + case 'github-actions': + this.parseGitHubActions(structure, result); + break; + case 'openapi': + this.parseOpenAPI(structure, result); + break; + case 'serverless': + this.parseServerless(structure, result); + break; + case 'ansible': + this.parseAnsible(structure, result); + break; + default: + this.parseGenericYAML(structure, result); + } + + } catch (error: any) { + result.errors.push(`Parse error: ${error.message}`); + } + + return result; + } + + private parseYAMLStructure(lines: string[]): any { + const structure: any = {}; + const stack: any[] = [{ obj: structure, indent: -1 }]; + let currentList: any[] | null = null; + let currentListIndent = -1; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + + // Skip comments and empty lines + if (trimmed.startsWith('#') || trimmed === '') { + continue; + } + + const indent = line.length - line.trimStart().length; + + // Handle list items + if (trimmed.startsWith('- ')) { + const value = trimmed.substring(2).trim(); + + // Find the right parent based on indentation + while (stack.length > 1 && stack[stack.length - 1].indent >= indent) { + stack.pop(); + } + + const parent = stack[stack.length - 1].obj; + const lastKey = stack[stack.length - 1].lastKey; + + if (lastKey && parent[lastKey]) { + if (!Array.isArray(parent[lastKey])) { + parent[lastKey] = [parent[lastKey]]; + } + + if (value.includes(':')) { + // List item is an object + const obj: any = {}; + parent[lastKey].push(obj); + stack.push({ obj, indent }); + + // Parse the key-value in the list item + const [key, val] = value.split(':').map(s => s.trim()); + if (key && val) { + obj[key] = this.parseValue(val); + } + } else { + parent[lastKey].push(this.parseValue(value)); + } + } + continue; + } + + // Handle key-value pairs + const keyValueMatch = trimmed.match(/^([^:]+):\s*(.*)$/); + if (keyValueMatch) { + const key = keyValueMatch[1].trim(); + const value = keyValueMatch[2].trim(); + + // Find the right parent based on indentation + while (stack.length > 1 && stack[stack.length - 1].indent >= indent) { + stack.pop(); + } + + const parent = stack[stack.length - 1].obj; + + if (value) { + // Direct value + parent[key] = this.parseValue(value); + } else { + // Nested object or array (value will come in next lines) + parent[key] = {}; + stack.push({ obj: parent[key], indent, lastKey: key }); + } + + // Update lastKey for potential list items + stack[stack.length - 1].lastKey = key; + } + } + + return structure; + } + + private parseValue(value: string): any { + // Remove quotes if present + if ((value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'"))) { + return value.slice(1, -1); + } + + // Boolean values + if (value === 'true' || value === 'yes' || value === 'on') return true; + if (value === 'false' || value === 'no' || value === 'off') return false; + + // Null values + if (value === 'null' || value === '~') return null; + + // Numbers + if (/^-?\d+$/.test(value)) return parseInt(value, 10); + if (/^-?\d+\.\d+$/.test(value)) return parseFloat(value); + + // Arrays (inline) + if (value.startsWith('[') && value.endsWith(']')) { + return value.slice(1, -1).split(',').map(v => this.parseValue(v.trim())); + } + + // Objects (inline) + if (value.startsWith('{') && value.endsWith('}')) { + const obj: any = {}; + const pairs = value.slice(1, -1).split(','); + for (const pair of pairs) { + const [k, v] = pair.split(':').map(s => s.trim()); + if (k && v) { + obj[k] = this.parseValue(v); + } + } + return obj; + } + + return value; + } + + private detectFileType(structure: any, filePath: string): string { + // Docker Compose + if (structure.version && structure.services) { + return 'docker-compose'; + } + + // Kubernetes + if (structure.apiVersion && structure.kind) { + return 'kubernetes'; + } + + // GitHub Actions + if (filePath.includes('.github/workflows') || structure.on || structure.jobs) { + return 'github-actions'; + } + + // OpenAPI/Swagger + if (structure.openapi || structure.swagger) { + return 'openapi'; + } + + // Serverless Framework + if (structure.service && structure.provider && structure.functions) { + return 'serverless'; + } + + // Ansible + if (Array.isArray(structure) && structure[0]?.tasks) { + return 'ansible'; + } + + return 'generic'; + } + + private parseDockerCompose(structure: any, result: ParserResult): void { + // Extract services as classes + if (structure.services) { + for (const [serviceName, serviceConfig] of Object.entries(structure.services)) { + const service: ClassInfo = { + name: serviceName, + methods: [], + properties: [], + extends: (serviceConfig as any).extends, + implements: [], + startLine: 0, + endLine: 0, + type: 'docker-service' + }; + + // Add image as a dependency + if ((serviceConfig as any).image) { + result.dependencies.push((serviceConfig as any).image); + } + + // Add properties for important configs + if ((serviceConfig as any).ports) { + service.properties.push({ + name: 'ports', + type: 'array', + visibility: 'public', + static: false, + value: (serviceConfig as any).ports + }); + } + + if ((serviceConfig as any).environment) { + service.properties.push({ + name: 'environment', + type: 'object', + visibility: 'public', + static: false + }); + } + + result.classes.push(service); + result.exports.push({ + name: serviceName, + type: 'service' + }); + } + } + + // Extract networks + if (structure.networks) { + for (const networkName of Object.keys(structure.networks)) { + result.constants.push({ + name: `network_${networkName}`, + type: 'network', + value: networkName + }); + } + } + + // Extract volumes + if (structure.volumes) { + for (const volumeName of Object.keys(structure.volumes)) { + result.constants.push({ + name: `volume_${volumeName}`, + type: 'volume', + value: volumeName + }); + } + } + } + + private parseKubernetes(structure: any, result: ParserResult): void { + const kind = structure.kind; + const name = structure.metadata?.name || 'unnamed'; + + // Create a class for the Kubernetes resource + const resource: ClassInfo = { + name: `${kind}_${name}`, + methods: [], + properties: [], + extends: undefined, + implements: [], + startLine: 0, + endLine: 0, + type: `k8s-${kind.toLowerCase()}` + }; + + // Add metadata as properties + if (structure.metadata) { + for (const [key, value] of Object.entries(structure.metadata)) { + resource.properties.push({ + name: key, + type: typeof value, + visibility: 'public', + static: false, + value: String(value) + }); + } + } + + // Handle specific kinds + if (kind === 'Deployment' || kind === 'StatefulSet' || kind === 'DaemonSet') { + if (structure.spec?.template?.spec?.containers) { + for (const container of structure.spec.template.spec.containers) { + result.dependencies.push(container.image); + } + } + } + + if (kind === 'Service') { + if (structure.spec?.ports) { + resource.properties.push({ + name: 'ports', + type: 'array', + visibility: 'public', + static: false, + value: structure.spec.ports + }); + } + } + + result.classes.push(resource); + result.exports.push({ + name: resource.name, + type: kind.toLowerCase() + }); + } + + private parseGitHubActions(structure: any, result: ParserResult): void { + const workflowName = structure.name || 'workflow'; + + // Extract triggers + if (structure.on) { + result.constants.push({ + name: 'triggers', + type: 'triggers', + value: JSON.stringify(structure.on) + }); + } + + // Extract jobs as functions + if (structure.jobs) { + for (const [jobName, jobConfig] of Object.entries(structure.jobs)) { + const job: FunctionInfo = { + name: jobName, + parameters: [], + returnType: 'void', + async: true, + generator: false, + startLine: 0, + endLine: 0 + }; + + // Extract steps + if ((jobConfig as any).steps) { + for (const step of (jobConfig as any).steps) { + if (step.uses) { + // GitHub Action dependency + result.dependencies.push(step.uses); + } + } + } + + result.functions.push(job); + result.exports.push({ + name: jobName, + type: 'job' + }); + } + } + + // Extract environment variables + if (structure.env) { + for (const [key, value] of Object.entries(structure.env)) { + result.constants.push({ + name: key, + type: 'env', + value: String(value) + }); + } + } + } + + private parseOpenAPI(structure: any, result: ParserResult): void { + // Extract API info + if (structure.info) { + result.constants.push({ + name: 'api_info', + type: 'info', + value: JSON.stringify(structure.info) + }); + } + + // Extract paths as functions + if (structure.paths) { + for (const [path, pathConfig] of Object.entries(structure.paths)) { + for (const [method, operation] of Object.entries(pathConfig as any)) { + if (typeof operation === 'object' && operation !== null) { + const op = operation as any; + const operationId = op.operationId || `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`; + + const func: FunctionInfo = { + name: operationId, + parameters: [], + returnType: 'response', + async: true, + generator: false, + startLine: 0, + endLine: 0, + httpMethod: method.toUpperCase(), + httpPath: path + }; + + // Extract parameters + if (op.parameters && Array.isArray(op.parameters)) { + for (const param of op.parameters) { + func.parameters.push({ + name: param.name, + type: param.type || 'any' + }); + } + } + + result.functions.push(func); + result.exports.push({ + name: operationId, + type: 'endpoint' + }); + } + } + } + } + + // Extract schemas/components as classes + const schemas = structure.components?.schemas || structure.definitions; + if (schemas) { + for (const [schemaName, schemaConfig] of Object.entries(schemas)) { + const schema: ClassInfo = { + name: schemaName, + methods: [], + properties: [], + extends: undefined, + implements: [], + startLine: 0, + endLine: 0, + type: 'schema' + }; + + if ((schemaConfig as any).properties) { + for (const [propName, propConfig] of Object.entries((schemaConfig as any).properties)) { + schema.properties.push({ + name: propName, + type: (propConfig as any).type || 'any', + visibility: 'public', + static: false, + required: (schemaConfig as any).required?.includes(propName) + }); + } + } + + result.classes.push(schema); + result.exports.push({ + name: schemaName, + type: 'schema' + }); + } + } + } + + private parseServerless(structure: any, result: ParserResult): void { + // Extract service name + if (structure.service) { + result.constants.push({ + name: 'service_name', + type: 'service', + value: structure.service + }); + } + + // Extract functions + if (structure.functions) { + for (const [funcName, funcConfig] of Object.entries(structure.functions)) { + const func: FunctionInfo = { + name: funcName, + parameters: [], + returnType: 'any', + async: true, + generator: false, + startLine: 0, + endLine: 0, + handler: (funcConfig as any).handler + }; + + // Extract events + if ((funcConfig as any).events) { + for (const event of (funcConfig as any).events) { + if (event.http) { + func.httpMethod = event.http.method; + func.httpPath = event.http.path; + } + } + } + + result.functions.push(func); + result.exports.push({ + name: funcName, + type: 'function' + }); + } + } + + // Extract resources + if (structure.resources?.Resources) { + for (const [resourceName, resourceConfig] of Object.entries(structure.resources.Resources)) { + result.constants.push({ + name: resourceName, + type: (resourceConfig as any).Type, + value: JSON.stringify((resourceConfig as any).Properties) + }); + } + } + } + + private parseAnsible(structure: any, result: ParserResult): void { + // Handle array of plays + const plays = Array.isArray(structure) ? structure : [structure]; + + for (const play of plays) { + if (play.name) { + const playClass: ClassInfo = { + name: play.name.replace(/[^a-zA-Z0-9]/g, '_'), + methods: [], + properties: [], + extends: undefined, + implements: [], + startLine: 0, + endLine: 0, + type: 'ansible-play' + }; + + // Extract tasks as methods + if (play.tasks) { + for (const task of play.tasks) { + if (task.name) { + playClass.methods.push({ + name: task.name.replace(/[^a-zA-Z0-9]/g, '_'), + visibility: 'public', + static: false, + module: Object.keys(task).find(k => k !== 'name' && k !== 'register' && k !== 'when') + }); + } + } + } + + result.classes.push(playClass); + result.exports.push({ + name: playClass.name, + type: 'playbook' + }); + } + } + } + + private parseGenericYAML(structure: any, result: ParserResult): void { + // Extract top-level keys as constants + for (const [key, value] of Object.entries(structure)) { + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + // Complex object - treat as a class + const cls: ClassInfo = { + name: key, + methods: [], + properties: [], + extends: undefined, + implements: [], + startLine: 0, + endLine: 0 + }; + + // Add nested keys as properties + for (const [propKey, propValue] of Object.entries(value as Record)) { + cls.properties.push({ + name: propKey, + type: Array.isArray(propValue) ? 'array' : typeof propValue, + visibility: 'public', + static: false + }); + } + + result.classes.push(cls); + result.exports.push({ + name: key, + type: 'object' + }); + } else { + // Simple value - treat as constant + result.constants.push({ + name: key, + type: Array.isArray(value) ? 'array' : typeof value, + value: Array.isArray(value) ? `[${value.length} items]` : String(value) + }); + + result.exports.push({ + name: key, + type: 'constant' + }); + } + } + } } \ No newline at end of file diff --git a/src/search/fuzzy-matcher.ts b/src/search/fuzzy-matcher.ts index 2954939..a6520a3 100644 --- a/src/search/fuzzy-matcher.ts +++ b/src/search/fuzzy-matcher.ts @@ -1,211 +1,211 @@ -/** - * Fuzzy Pattern Matching Engine - * Provides intelligent, non-exact matching with high accuracy - */ - -export interface FuzzyMatchOptions { - threshold?: number; // Similarity threshold (0-1) - caseSensitive?: boolean; // Case sensitivity - tokenize?: boolean; // Break into tokens (camelCase, snake_case) - semantic?: boolean; // Use semantic aliases -} - -/** - * Calculate Levenshtein distance between two strings - */ -function levenshtein(str1: string, str2: string): number { - const matrix: number[][] = []; - - for (let i = 0; i <= str2.length; i++) { - matrix[i] = [i]; - } - - for (let j = 0; j <= str1.length; j++) { - matrix[0][j] = j; - } - - for (let i = 1; i <= str2.length; i++) { - for (let j = 1; j <= str1.length; j++) { - if (str2.charAt(i - 1) === str1.charAt(j - 1)) { - matrix[i][j] = matrix[i - 1][j - 1]; - } else { - matrix[i][j] = Math.min( - matrix[i - 1][j - 1] + 1, // substitution - matrix[i][j - 1] + 1, // insertion - matrix[i - 1][j] + 1 // deletion - ); - } - } - } - - return matrix[str2.length][str1.length]; -} - -/** - * Tokenize camelCase, PascalCase, snake_case, kebab-case - */ -function tokenize(str: string): string[] { - return str - .replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase - .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2') // PascalCase - .replace(/[_-]/g, ' ') // snake_case, kebab-case - .toLowerCase() - .split(/\s+/) - .filter(Boolean); -} - -/** - * Semantic alias maps for intelligent matching - */ -const SEMANTIC_ALIASES: Record = { - // Calendar/Time - calendar: ['schedule', 'appointment', 'meeting', 'event', 'booking', 'availability'], - schedule: ['calendar', 'appointment', 'meeting', 'timetable', 'agenda'], - - // Skills/Functions - skill: ['function', 'capability', 'action', 'behavior', 'operation', 'ability'], - run: ['execute', 'invoke', 'call', 'trigger', 'launch', 'start'], - - // Data Operations - get: ['fetch', 'retrieve', 'load', 'read', 'query', 'find'], - create: ['add', 'new', 'insert', 'generate', 'make', 'build'], - update: ['edit', 'modify', 'change', 'patch', 'alter', 'revise'], - delete: ['remove', 'destroy', 'drop', 'clear', 'purge', 'erase'], - - // GraphQL - query: ['get', 'fetch', 'retrieve', 'search', 'find', 'list'], - mutation: ['create', 'update', 'delete', 'modify', 'change'], - - // Users - user: ['member', 'person', 'account', 'profile', 'participant', 'client'], - admin: ['administrator', 'superuser', 'root', 'manager', 'owner'], - - // Status - active: ['enabled', 'on', 'running', 'live', 'online'], - inactive: ['disabled', 'off', 'stopped', 'offline', 'paused'], -}; - -/** - * Get semantic aliases for a word - */ -function getSemanticAliases(word: string): string[] { - const lowerWord = word.toLowerCase(); - const aliases: string[] = []; - - // Direct aliases - if (SEMANTIC_ALIASES[lowerWord]) { - aliases.push(...SEMANTIC_ALIASES[lowerWord]); - } - - // Reverse lookup - for (const [key, values] of Object.entries(SEMANTIC_ALIASES)) { - if (values.includes(lowerWord)) { - aliases.push(key, ...values.filter(v => v !== lowerWord)); - } - } - - return [...new Set(aliases)]; -} - -/** - * Calculate similarity score between two strings - */ -function calculateSimilarity(str1: string, str2: string): number { - if (str1 === str2) return 1; - - const distance = levenshtein(str1.toLowerCase(), str2.toLowerCase()); - const maxLength = Math.max(str1.length, str2.length); - - return 1 - (distance / maxLength); -} - -/** - * Main fuzzy matching function - */ -export function fuzzyMatch( - query: string, - target: string, - options: FuzzyMatchOptions = {} -): { match: boolean; score: number; reason?: string } { - const { - threshold = 0.7, - caseSensitive = false, - tokenize: shouldTokenize = true, - semantic = true - } = options; - - // Prepare strings - const q = caseSensitive ? query : query.toLowerCase(); - const t = caseSensitive ? target : target.toLowerCase(); - - // Exact match - if (q === t) { - return { match: true, score: 1, reason: 'exact' }; - } - - // Substring match - if (t.includes(q) || q.includes(t)) { - const score = Math.min(q.length, t.length) / Math.max(q.length, t.length); - if (score >= threshold) { - return { match: true, score, reason: 'substring' }; - } - } - - // Token matching - if (shouldTokenize) { - const queryTokens = tokenize(query); - const targetTokens = tokenize(target); - - // Check if all query tokens are in target - const matchedTokens = queryTokens.filter(qt => - targetTokens.some(tt => tt.includes(qt) || qt.includes(tt)) - ); - - if (matchedTokens.length > 0) { - const score = matchedTokens.length / queryTokens.length; - if (score >= threshold) { - return { match: true, score, reason: 'tokens' }; - } - } - } - - // Semantic matching - if (semantic) { - const queryAliases = [query, ...getSemanticAliases(query)]; - const targetAliases = [target, ...getSemanticAliases(target)]; - - for (const qa of queryAliases) { - for (const ta of targetAliases) { - const similarity = calculateSimilarity(qa, ta); - if (similarity >= threshold) { - return { match: true, score: similarity, reason: 'semantic' }; - } - } - } - } - - // Levenshtein distance - const similarity = calculateSimilarity(query, target); - if (similarity >= threshold) { - return { match: true, score: similarity, reason: 'fuzzy' }; - } - - return { match: false, score: similarity }; -} - -/** - * Batch fuzzy matching for performance - */ -export function fuzzyMatchBatch( - query: string, - targets: string[], - options: FuzzyMatchOptions = {} -): Array<{ target: string; match: boolean; score: number; reason?: string }> { - return targets - .map(target => ({ - target, - ...fuzzyMatch(query, target, options) - })) - .filter(result => result.match) - .sort((a, b) => b.score - a.score); +/** + * Fuzzy Pattern Matching Engine + * Provides intelligent, non-exact matching with high accuracy + */ + +export interface FuzzyMatchOptions { + threshold?: number; // Similarity threshold (0-1) + caseSensitive?: boolean; // Case sensitivity + tokenize?: boolean; // Break into tokens (camelCase, snake_case) + semantic?: boolean; // Use semantic aliases +} + +/** + * Calculate Levenshtein distance between two strings + */ +function levenshtein(str1: string, str2: string): number { + const matrix: number[][] = []; + + for (let i = 0; i <= str2.length; i++) { + matrix[i] = [i]; + } + + for (let j = 0; j <= str1.length; j++) { + matrix[0][j] = j; + } + + for (let i = 1; i <= str2.length; i++) { + for (let j = 1; j <= str1.length; j++) { + if (str2.charAt(i - 1) === str1.charAt(j - 1)) { + matrix[i][j] = matrix[i - 1][j - 1]; + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j - 1] + 1, // substitution + matrix[i][j - 1] + 1, // insertion + matrix[i - 1][j] + 1 // deletion + ); + } + } + } + + return matrix[str2.length][str1.length]; +} + +/** + * Tokenize camelCase, PascalCase, snake_case, kebab-case + */ +function tokenize(str: string): string[] { + return str + .replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase + .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2') // PascalCase + .replace(/[_-]/g, ' ') // snake_case, kebab-case + .toLowerCase() + .split(/\s+/) + .filter(Boolean); +} + +/** + * Semantic alias maps for intelligent matching + */ +const SEMANTIC_ALIASES: Record = { + // Calendar/Time + calendar: ['schedule', 'appointment', 'meeting', 'event', 'booking', 'availability'], + schedule: ['calendar', 'appointment', 'meeting', 'timetable', 'agenda'], + + // Skills/Functions + skill: ['function', 'capability', 'action', 'behavior', 'operation', 'ability'], + run: ['execute', 'invoke', 'call', 'trigger', 'launch', 'start'], + + // Data Operations + get: ['fetch', 'retrieve', 'load', 'read', 'query', 'find'], + create: ['add', 'new', 'insert', 'generate', 'make', 'build'], + update: ['edit', 'modify', 'change', 'patch', 'alter', 'revise'], + delete: ['remove', 'destroy', 'drop', 'clear', 'purge', 'erase'], + + // GraphQL + query: ['get', 'fetch', 'retrieve', 'search', 'find', 'list'], + mutation: ['create', 'update', 'delete', 'modify', 'change'], + + // Users + user: ['member', 'person', 'account', 'profile', 'participant', 'client'], + admin: ['administrator', 'superuser', 'root', 'manager', 'owner'], + + // Status + active: ['enabled', 'on', 'running', 'live', 'online'], + inactive: ['disabled', 'off', 'stopped', 'offline', 'paused'], +}; + +/** + * Get semantic aliases for a word + */ +function getSemanticAliases(word: string): string[] { + const lowerWord = word.toLowerCase(); + const aliases: string[] = []; + + // Direct aliases + if (SEMANTIC_ALIASES[lowerWord]) { + aliases.push(...SEMANTIC_ALIASES[lowerWord]); + } + + // Reverse lookup + for (const [key, values] of Object.entries(SEMANTIC_ALIASES)) { + if (values.includes(lowerWord)) { + aliases.push(key, ...values.filter(v => v !== lowerWord)); + } + } + + return [...new Set(aliases)]; +} + +/** + * Calculate similarity score between two strings + */ +function calculateSimilarity(str1: string, str2: string): number { + if (str1 === str2) return 1; + + const distance = levenshtein(str1.toLowerCase(), str2.toLowerCase()); + const maxLength = Math.max(str1.length, str2.length); + + return 1 - (distance / maxLength); +} + +/** + * Main fuzzy matching function + */ +export function fuzzyMatch( + query: string, + target: string, + options: FuzzyMatchOptions = {} +): { match: boolean; score: number; reason?: string } { + const { + threshold = 0.7, + caseSensitive = false, + tokenize: shouldTokenize = true, + semantic = true + } = options; + + // Prepare strings + const q = caseSensitive ? query : query.toLowerCase(); + const t = caseSensitive ? target : target.toLowerCase(); + + // Exact match + if (q === t) { + return { match: true, score: 1, reason: 'exact' }; + } + + // Substring match + if (t.includes(q) || q.includes(t)) { + const score = Math.min(q.length, t.length) / Math.max(q.length, t.length); + if (score >= threshold) { + return { match: true, score, reason: 'substring' }; + } + } + + // Token matching + if (shouldTokenize) { + const queryTokens = tokenize(query); + const targetTokens = tokenize(target); + + // Check if all query tokens are in target + const matchedTokens = queryTokens.filter(qt => + targetTokens.some(tt => tt.includes(qt) || qt.includes(tt)) + ); + + if (matchedTokens.length > 0) { + const score = matchedTokens.length / queryTokens.length; + if (score >= threshold) { + return { match: true, score, reason: 'tokens' }; + } + } + } + + // Semantic matching + if (semantic) { + const queryAliases = [query, ...getSemanticAliases(query)]; + const targetAliases = [target, ...getSemanticAliases(target)]; + + for (const qa of queryAliases) { + for (const ta of targetAliases) { + const similarity = calculateSimilarity(qa, ta); + if (similarity >= threshold) { + return { match: true, score: similarity, reason: 'semantic' }; + } + } + } + } + + // Levenshtein distance + const similarity = calculateSimilarity(query, target); + if (similarity >= threshold) { + return { match: true, score: similarity, reason: 'fuzzy' }; + } + + return { match: false, score: similarity }; +} + +/** + * Batch fuzzy matching for performance + */ +export function fuzzyMatchBatch( + query: string, + targets: string[], + options: FuzzyMatchOptions = {} +): Array<{ target: string; match: boolean; score: number; reason?: string }> { + return targets + .map(target => ({ + target, + ...fuzzyMatch(query, target, options) + })) + .filter(result => result.match) + .sort((a, b) => b.score - a.score); } \ No newline at end of file diff --git a/src/types/external.d.ts b/src/types/external.d.ts index 702db47..ac12fe0 100644 --- a/src/types/external.d.ts +++ b/src/types/external.d.ts @@ -1,52 +1,52 @@ -// Type declarations for external modules without TypeScript definitions - -declare module 'node-dogstatsd' { - export class StatsD { - constructor(options?: { - host?: string; - port?: number; - prefix?: string; - globalTags?: string[]; - }); - - histogram(name: string, value: number, tags?: string[]): void; - gauge(name: string, value: number, tags?: string[]): void; - increment(name: string, value?: number, tags?: string[]): void; - decrement(name: string, value?: number, tags?: string[]): void; - timing(name: string, value: number, tags?: string[]): void; - set(name: string, value: number, tags?: string[]): void; - event(title: string, text: string, options?: { - alert_type?: 'info' | 'warning' | 'error' | 'success'; - tags?: string[]; - aggregation_key?: string; - }): void; - close(callback?: () => void): void; - } -} - -declare module '@slack/web-api' { - export class WebClient { - constructor(token?: string, options?: any); - chat: { - postMessage(options: any): Promise; - }; - conversations: { - list(options?: any): Promise; - }; - users: { - info(options: any): Promise; - }; - } -} - -declare module '@slack/events-api' { - export function createEventAdapter(signingSecret: string, options?: any): any; -} - -declare module '@linear/sdk' { - export class LinearClient { - constructor(options: { apiKey: string }); - teams(): Promise; - createIssue(input: any): Promise; - } +// Type declarations for external modules without TypeScript definitions + +declare module 'node-dogstatsd' { + export class StatsD { + constructor(options?: { + host?: string; + port?: number; + prefix?: string; + globalTags?: string[]; + }); + + histogram(name: string, value: number, tags?: string[]): void; + gauge(name: string, value: number, tags?: string[]): void; + increment(name: string, value?: number, tags?: string[]): void; + decrement(name: string, value?: number, tags?: string[]): void; + timing(name: string, value: number, tags?: string[]): void; + set(name: string, value: number, tags?: string[]): void; + event(title: string, text: string, options?: { + alert_type?: 'info' | 'warning' | 'error' | 'success'; + tags?: string[]; + aggregation_key?: string; + }): void; + close(callback?: () => void): void; + } +} + +declare module '@slack/web-api' { + export class WebClient { + constructor(token?: string, options?: any); + chat: { + postMessage(options: any): Promise; + }; + conversations: { + list(options?: any): Promise; + }; + users: { + info(options: any): Promise; + }; + } +} + +declare module '@slack/events-api' { + export function createEventAdapter(signingSecret: string, options?: any): any; +} + +declare module '@linear/sdk' { + export class LinearClient { + constructor(options: { apiKey: string }); + teams(): Promise; + createIssue(input: any): Promise; + } } \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 55ac6cf..8a8301d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,281 +1,281 @@ -export interface FunctionInfo { - name: string; - parameters: Array<{ name: string; type: string; default?: string }>; - returnType: string; - async: boolean; - generator: boolean; - startLine: number; - endLine: number; - line?: number; // Backward compatibility - alias for startLine - params?: Array<{ name: string; type: string }>; // Backward compatibility - alias for parameters - // Call graph information - calls?: string[]; // Functions this function calls - calledBy?: string[]; // Functions that call this function - // Optional fields for specific parsers - receiver?: string; - handler?: string; - triggerTable?: string; - httpMethod?: string; - httpPath?: string; - directiveLocations?: string[]; - resolver?: boolean; - parentType?: string; - operationType?: string; - isClientSide?: boolean; - decorators?: string[]; -} - -export interface ClassInfo { - name: string; - methods: Array<{ - name: string; - visibility: string; - static: boolean; - classmethod?: boolean; - property?: boolean; - async?: boolean; - returnType?: string; - arguments?: Array<{ name: string; type: string }>; - module?: string; - decorators?: string[]; - // Call graph information - calls?: string[]; // Functions/methods this method calls - calledBy?: string[]; // Functions/methods that call this method - }>; - properties: Array<{ - name: string; - type: string; - visibility: string; - static: boolean; - readonly?: boolean; - required?: boolean; - nullable?: boolean; - primary?: boolean; - unique?: boolean; - hasDefault?: boolean; - tags?: string; - value?: any; - }>; - extends?: string; - implements: string[]; - startLine: number; - endLine: number; - line?: number; // Backward compatibility - alias for startLine - // Optional fields for specific parsers - abstract?: boolean; - interface?: boolean; - type?: string; - isRootType?: boolean; - graphqlType?: string; - decorators?: string[]; - metaclass?: string; -} - -export interface ImportInfo { - source: string; - specifiers: Array<{ name: string; alias: string }>; - type: string; -} - -export interface ExportInfo { - name: string; - type: string; - line?: number; -} - -export interface ConstantInfo { - name: string; - type: string; - value?: any; - line?: number; -} - -export interface VariableInfo { - name: string; - type: string; - value?: any; -} - -export interface FileIndex { - imports: ImportInfo[]; - exports: ExportInfo[]; - functions: FunctionInfo[]; - classes: ClassInfo[]; - constants: ConstantInfo[]; - dependencies: string[]; - lastModified: string; - language: string; - size?: number; - complexity?: number; -} - -// Alias for compatibility -export type FileInfo = FileIndex; - -export interface ProjectIndex { - version: string; - timestamp: string; - projectRoot: string; - files: Record; - dependencyGraph: Record; - callGraph?: { - calls: Record; // Function -> functions it calls - calledBy: Record; // Function -> functions that call it - entryPoints: string[]; // Functions not called by anyone (likely entry points) - deadCode: string[]; // Functions never called and not entry points - circularDependencies?: string[][]; // Circular dependency chains - }; - statistics: { - totalFiles: number; - totalFunctions: number; - totalClasses: number; - totalConstants: number; - languages: Record; - avgComplexity?: number; - callGraphStats?: { - totalCalls: number; - averageCallsPerFunction: number; - maxCallDepth: number; - deadCodeCount: number; - circularDependencyCount: number; - }; - }; - monorepo?: { - services: Record; - crossServiceDependencies: Array<{ - from: string; - to: string; - type: string; - files?: string[]; - }>; - }; -} - -export interface ServiceInfo { - name: string; - path: string; - type?: string; - files: number; - language: string; - dependencies: string[]; -} - -// Multi-Repository Knowledge Graph Types -export interface RepoNode { - name: string; - path: string; - type: 'frontend' | 'backend' | 'skills' | 'data-ops' | 'marketing' | 'library'; - fileCount: number; - language: string; - indexPath?: string; -} - -export interface RepoConnection { - from: string; - to: string; - type: 'api' | 'graphql' | 'event' | 'import'; - count?: number; - details?: { - endpoint?: string; - operation?: string; - event?: string; - files?: string[]; - }; -} - -export interface MultiRepoGraph { - repositories: RepoNode[]; - connections: RepoConnection[]; - timestamp: string; -} - -export interface RepoConfig { - name: string; - path: string; - type: 'frontend' | 'backend' | 'skills' | 'data-ops' | 'marketing' | 'library'; - indexPath?: string; -} - -export interface CrossRepoConnection { - from: string; - to: string; - type: 'api' | 'graphql' | 'event' | 'import'; - fromFile: string; - toFile?: string; - details?: any; -} - -export interface ParserResult { - imports: ImportInfo[]; - exports: ExportInfo[]; - functions: FunctionInfo[]; - classes: ClassInfo[]; - constants: ConstantInfo[]; - dependencies: string[]; - errors: Array; - packageName?: string; -} - -export interface Parser { - language: string; - extensions: string[]; - parse(content: string, filePath: string): ParserResult; -} - -export interface Exporter { - export(index: ProjectIndex, outputPath: string, options?: TOptions): Promise; -} - -export interface IndexerConfig { - ignore?: string[]; - include?: string[]; - maxFileSize?: number; - maxFiles?: number; - parallel?: boolean; - workers?: number; - cache?: boolean; - cacheDir?: string; - monorepo?: boolean; - serviceDetection?: { - patterns: Record; - }; - indexDepth?: Record; - export?: { - outputDirectory?: string; - formats?: { - json?: { path?: string }; - markdown?: { path?: string }; - graphviz?: { path?: string }; - mermaid?: { path?: string }; - ascii?: { path?: string }; - }; - }; -} - -export interface IndexerOptions { - mode?: 'quick' | 'full' | 'deep'; - watch?: boolean; - incremental?: boolean; - force?: boolean; - parallel?: number; - output?: string; - quiet?: boolean; - verbose?: boolean; - config?: string; - stats?: boolean; - onProgress?: (processed: number, total: number, filesPerSecond: number) => void; - useWorkers?: boolean; - includeCallGraph?: boolean; -} - -export interface QueryOptions { - type?: 'function' | 'class' | 'import' | 'constant' | 'all'; - json?: boolean; - tree?: boolean; - unused?: boolean; - circular?: boolean; - fuzzy?: boolean; - semantic?: boolean; - limit?: number; - service?: string; +export interface FunctionInfo { + name: string; + parameters: Array<{ name: string; type: string; default?: string }>; + returnType: string; + async: boolean; + generator: boolean; + startLine: number; + endLine: number; + line?: number; // Backward compatibility - alias for startLine + params?: Array<{ name: string; type: string }>; // Backward compatibility - alias for parameters + // Call graph information + calls?: string[]; // Functions this function calls + calledBy?: string[]; // Functions that call this function + // Optional fields for specific parsers + receiver?: string; + handler?: string; + triggerTable?: string; + httpMethod?: string; + httpPath?: string; + directiveLocations?: string[]; + resolver?: boolean; + parentType?: string; + operationType?: string; + isClientSide?: boolean; + decorators?: string[]; +} + +export interface ClassInfo { + name: string; + methods: Array<{ + name: string; + visibility: string; + static: boolean; + classmethod?: boolean; + property?: boolean; + async?: boolean; + returnType?: string; + arguments?: Array<{ name: string; type: string }>; + module?: string; + decorators?: string[]; + // Call graph information + calls?: string[]; // Functions/methods this method calls + calledBy?: string[]; // Functions/methods that call this method + }>; + properties: Array<{ + name: string; + type: string; + visibility: string; + static: boolean; + readonly?: boolean; + required?: boolean; + nullable?: boolean; + primary?: boolean; + unique?: boolean; + hasDefault?: boolean; + tags?: string; + value?: any; + }>; + extends?: string; + implements: string[]; + startLine: number; + endLine: number; + line?: number; // Backward compatibility - alias for startLine + // Optional fields for specific parsers + abstract?: boolean; + interface?: boolean; + type?: string; + isRootType?: boolean; + graphqlType?: string; + decorators?: string[]; + metaclass?: string; +} + +export interface ImportInfo { + source: string; + specifiers: Array<{ name: string; alias: string }>; + type: string; +} + +export interface ExportInfo { + name: string; + type: string; + line?: number; +} + +export interface ConstantInfo { + name: string; + type: string; + value?: any; + line?: number; +} + +export interface VariableInfo { + name: string; + type: string; + value?: any; +} + +export interface FileIndex { + imports: ImportInfo[]; + exports: ExportInfo[]; + functions: FunctionInfo[]; + classes: ClassInfo[]; + constants: ConstantInfo[]; + dependencies: string[]; + lastModified: string; + language: string; + size?: number; + complexity?: number; +} + +// Alias for compatibility +export type FileInfo = FileIndex; + +export interface ProjectIndex { + version: string; + timestamp: string; + projectRoot: string; + files: Record; + dependencyGraph: Record; + callGraph?: { + calls: Record; // Function -> functions it calls + calledBy: Record; // Function -> functions that call it + entryPoints: string[]; // Functions not called by anyone (likely entry points) + deadCode: string[]; // Functions never called and not entry points + circularDependencies?: string[][]; // Circular dependency chains + }; + statistics: { + totalFiles: number; + totalFunctions: number; + totalClasses: number; + totalConstants: number; + languages: Record; + avgComplexity?: number; + callGraphStats?: { + totalCalls: number; + averageCallsPerFunction: number; + maxCallDepth: number; + deadCodeCount: number; + circularDependencyCount: number; + }; + }; + monorepo?: { + services: Record; + crossServiceDependencies: Array<{ + from: string; + to: string; + type: string; + files?: string[]; + }>; + }; +} + +export interface ServiceInfo { + name: string; + path: string; + type?: string; + files: number; + language: string; + dependencies: string[]; +} + +// Multi-Repository Knowledge Graph Types +export interface RepoNode { + name: string; + path: string; + type: 'frontend' | 'backend' | 'skills' | 'data-ops' | 'marketing' | 'library'; + fileCount: number; + language: string; + indexPath?: string; +} + +export interface RepoConnection { + from: string; + to: string; + type: 'api' | 'graphql' | 'event' | 'import'; + count?: number; + details?: { + endpoint?: string; + operation?: string; + event?: string; + files?: string[]; + }; +} + +export interface MultiRepoGraph { + repositories: RepoNode[]; + connections: RepoConnection[]; + timestamp: string; +} + +export interface RepoConfig { + name: string; + path: string; + type: 'frontend' | 'backend' | 'skills' | 'data-ops' | 'marketing' | 'library'; + indexPath?: string; +} + +export interface CrossRepoConnection { + from: string; + to: string; + type: 'api' | 'graphql' | 'event' | 'import'; + fromFile: string; + toFile?: string; + details?: any; +} + +export interface ParserResult { + imports: ImportInfo[]; + exports: ExportInfo[]; + functions: FunctionInfo[]; + classes: ClassInfo[]; + constants: ConstantInfo[]; + dependencies: string[]; + errors: Array; + packageName?: string; +} + +export interface Parser { + language: string; + extensions: string[]; + parse(content: string, filePath: string): ParserResult; +} + +export interface Exporter { + export(index: ProjectIndex, outputPath: string, options?: TOptions): Promise; +} + +export interface IndexerConfig { + ignore?: string[]; + include?: string[]; + maxFileSize?: number; + maxFiles?: number; + parallel?: boolean; + workers?: number; + cache?: boolean; + cacheDir?: string; + monorepo?: boolean; + serviceDetection?: { + patterns: Record; + }; + indexDepth?: Record; + export?: { + outputDirectory?: string; + formats?: { + json?: { path?: string }; + markdown?: { path?: string }; + graphviz?: { path?: string }; + mermaid?: { path?: string }; + ascii?: { path?: string }; + }; + }; +} + +export interface IndexerOptions { + mode?: 'quick' | 'full' | 'deep'; + watch?: boolean; + incremental?: boolean; + force?: boolean; + parallel?: number; + output?: string; + quiet?: boolean; + verbose?: boolean; + config?: string; + stats?: boolean; + onProgress?: (processed: number, total: number, filesPerSecond: number) => void; + useWorkers?: boolean; + includeCallGraph?: boolean; +} + +export interface QueryOptions { + type?: 'function' | 'class' | 'import' | 'constant' | 'all'; + json?: boolean; + tree?: boolean; + unused?: boolean; + circular?: boolean; + fuzzy?: boolean; + semantic?: boolean; + limit?: number; + service?: string; } \ No newline at end of file diff --git a/src/utils/env-validator.ts b/src/utils/env-validator.ts index cb5bd30..d37e636 100644 --- a/src/utils/env-validator.ts +++ b/src/utils/env-validator.ts @@ -1,339 +1,339 @@ -import logger from './logger'; - -/** - * Environment Variable Validator - * Validates required and optional environment variables at startup - * Provides clear error messages and defaults for optional variables - */ - -export interface EnvConfig { - // Core Configuration - NODE_ENV: 'development' | 'production' | 'test'; - LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error' | 'silent'; - - // Performance Configuration - INDEXER_PARALLEL: boolean; - INDEXER_WORKERS: number; - INDEXER_CACHE_DIR: string; - INDEXER_MAX_MEMORY: number; - - // AI Features (optional) - ANTHROPIC_API_KEY?: string; - CLAUDE_MODEL?: string; - - // Integrations (optional) - SLACK_BOT_TOKEN?: string; - SLACK_SIGNING_SECRET?: string; - LINEAR_API_KEY?: string; - DD_AGENT_HOST?: string; - DD_DOGSTATSD_PORT?: number; - GITHUB_TOKEN?: string; - - // API Server (optional) - API_PORT?: number; - API_HOST?: string; - JWT_SECRET?: string; -} - -interface EnvVariable { - name: keyof EnvConfig; - required: boolean; - type: 'string' | 'number' | 'boolean' | 'enum'; - default?: any; - validate?: (value: any) => boolean; - enumValues?: string[]; - description: string; -} - -const ENV_SCHEMA: EnvVariable[] = [ - // Core Configuration - { - name: 'NODE_ENV', - required: false, - type: 'enum', - enumValues: ['development', 'production', 'test'], - default: 'development', - description: 'Node environment' - }, - { - name: 'LOG_LEVEL', - required: false, - type: 'enum', - enumValues: ['debug', 'info', 'warn', 'error', 'silent'], - default: 'info', - description: 'Logging level' - }, - - // Performance Configuration - { - name: 'INDEXER_PARALLEL', - required: false, - type: 'boolean', - default: true, - description: 'Enable parallel processing' - }, - { - name: 'INDEXER_WORKERS', - required: false, - type: 'number', - default: 4, - validate: (val) => val > 0 && val <= 16, - description: 'Number of worker threads (1-16)' - }, - { - name: 'INDEXER_CACHE_DIR', - required: false, - type: 'string', - default: '.indexer-cache', - description: 'Cache directory path' - }, - { - name: 'INDEXER_MAX_MEMORY', - required: false, - type: 'number', - default: 500, - validate: (val) => val > 0 && val <= 2000, - description: 'Maximum memory in MB (1-2000)' - }, - - // AI Features - { - name: 'ANTHROPIC_API_KEY', - required: false, - type: 'string', - validate: (val) => !val || val.startsWith('sk-ant-'), - description: 'Anthropic API key for AI features' - }, - { - name: 'CLAUDE_MODEL', - required: false, - type: 'string', - default: 'claude-opus-4-1-20250805', - description: 'Claude model to use' - }, - - // Slack Integration - { - name: 'SLACK_BOT_TOKEN', - required: false, - type: 'string', - validate: (val) => !val || val.startsWith('xoxb-'), - description: 'Slack bot token' - }, - { - name: 'SLACK_SIGNING_SECRET', - required: false, - type: 'string', - description: 'Slack signing secret for webhook verification' - }, - - // Linear Integration - { - name: 'LINEAR_API_KEY', - required: false, - type: 'string', - validate: (val) => !val || val.startsWith('lin_api_'), - description: 'Linear API key for ticket creation' - }, - - // Datadog Integration - { - name: 'DD_AGENT_HOST', - required: false, - type: 'string', - default: 'localhost', - description: 'Datadog agent host' - }, - { - name: 'DD_DOGSTATSD_PORT', - required: false, - type: 'number', - default: 8125, - description: 'Datadog StatsD port' - }, - - // GitHub Integration - { - name: 'GITHUB_TOKEN', - required: false, - type: 'string', - validate: (val) => !val || val.startsWith('ghp_') || val.startsWith('github_pat_'), - description: 'GitHub personal access token' - }, - - // API Server - { - name: 'API_PORT', - required: false, - type: 'number', - default: 4000, - validate: (val) => val > 0 && val <= 65535, - description: 'API server port' - }, - { - name: 'API_HOST', - required: false, - type: 'string', - default: 'localhost', - description: 'API server host' - }, - { - name: 'JWT_SECRET', - required: false, - type: 'string', - validate: (val) => !val || val.length >= 32, - description: 'JWT secret for API authentication (min 32 chars)' - } -]; - -export class EnvValidator { - private errors: string[] = []; - private warnings: string[] = []; - private config: Partial = {}; - - /** - * Validate all environment variables according to schema - */ - public validate(): { valid: boolean; config: EnvConfig; errors: string[]; warnings: string[] } { - this.errors = []; - this.warnings = []; - this.config = {}; - - for (const variable of ENV_SCHEMA) { - this.validateVariable(variable); - } - - return { - valid: this.errors.length === 0, - config: this.config as EnvConfig, - errors: this.errors, - warnings: this.warnings - }; - } - - private validateVariable(variable: EnvVariable): void { - const rawValue = process.env[variable.name]; - - // Check if required variable is missing - if (variable.required && !rawValue) { - this.errors.push(`Missing required environment variable: ${variable.name} - ${variable.description}`); - return; - } - - // Use default if not provided - if (!rawValue) { - if (variable.default !== undefined) { - this.config[variable.name] = variable.default; - } - return; - } - - // Parse and validate the value - let parsedValue: any = rawValue; - - switch (variable.type) { - case 'number': - parsedValue = parseInt(rawValue, 10); - if (isNaN(parsedValue)) { - this.errors.push(`Invalid number for ${variable.name}: ${rawValue}`); - return; - } - break; - - case 'boolean': - parsedValue = rawValue.toLowerCase() === 'true' || rawValue === '1'; - break; - - case 'enum': - if (variable.enumValues && !variable.enumValues.includes(rawValue)) { - this.errors.push( - `Invalid value for ${variable.name}: ${rawValue}. Must be one of: ${variable.enumValues.join(', ')}` - ); - return; - } - break; - } - - // Run custom validation if provided - if (variable.validate && !variable.validate(parsedValue)) { - this.errors.push(`Invalid value for ${variable.name}: ${rawValue} - ${variable.description}`); - return; - } - - this.config[variable.name] = parsedValue; - } - - /** - * Check if AI features are available - */ - public static hasAIFeatures(): boolean { - return !!process.env.ANTHROPIC_API_KEY; - } - - /** - * Check if Slack integration is configured - */ - public static hasSlackIntegration(): boolean { - return !!process.env.SLACK_BOT_TOKEN && !!process.env.SLACK_SIGNING_SECRET; - } - - /** - * Check if Linear integration is configured - */ - public static hasLinearIntegration(): boolean { - return !!process.env.LINEAR_API_KEY; - } - - /** - * Check if Datadog monitoring is configured - */ - public static hasDatadogMonitoring(): boolean { - return !!process.env.DD_AGENT_HOST; - } - - /** - * Get validated configuration or exit on error - */ - public static getConfig(): EnvConfig { - const validator = new EnvValidator(); - const result = validator.validate(); - - if (!result.valid) { - logger.error('Environment validation failed:'); - for (const error of result.errors) { - logger.error(` - ${error}`); - } - - logger.info('\n💡 Set environment variables:'); - logger.info(' export NODE_ENV=development'); - logger.info(' export LOG_LEVEL=info'); - logger.info('\nOptional integrations:'); - logger.info(' export ANTHROPIC_API_KEY=sk-ant-...'); - logger.info(' export SLACK_BOT_TOKEN=xoxb-...'); - logger.info(' export LINEAR_API_KEY=lin_api_...'); - - process.exit(1); - } - - if (result.warnings.length > 0) { - logger.warn('Environment warnings:'); - for (const warning of result.warnings) { - logger.warn(` - ${warning}`); - } - } - - // Log configuration summary - logger.debug('Environment configuration:'); - logger.debug(` NODE_ENV: ${result.config.NODE_ENV}`); - logger.debug(` LOG_LEVEL: ${result.config.LOG_LEVEL}`); - logger.debug(` AI Features: ${EnvValidator.hasAIFeatures() ? '✓' : '✗'}`); - logger.debug(` Slack Integration: ${EnvValidator.hasSlackIntegration() ? '✓' : '✗'}`); - logger.debug(` Linear Integration: ${EnvValidator.hasLinearIntegration() ? '✓' : '✗'}`); - logger.debug(` Datadog Monitoring: ${EnvValidator.hasDatadogMonitoring() ? '✓' : '✗'}`); - - return result.config; - } -} - -// Export singleton instance for easy access +import logger from './logger'; + +/** + * Environment Variable Validator + * Validates required and optional environment variables at startup + * Provides clear error messages and defaults for optional variables + */ + +export interface EnvConfig { + // Core Configuration + NODE_ENV: 'development' | 'production' | 'test'; + LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error' | 'silent'; + + // Performance Configuration + INDEXER_PARALLEL: boolean; + INDEXER_WORKERS: number; + INDEXER_CACHE_DIR: string; + INDEXER_MAX_MEMORY: number; + + // AI Features (optional) + ANTHROPIC_API_KEY?: string; + CLAUDE_MODEL?: string; + + // Integrations (optional) + SLACK_BOT_TOKEN?: string; + SLACK_SIGNING_SECRET?: string; + LINEAR_API_KEY?: string; + DD_AGENT_HOST?: string; + DD_DOGSTATSD_PORT?: number; + GITHUB_TOKEN?: string; + + // API Server (optional) + API_PORT?: number; + API_HOST?: string; + JWT_SECRET?: string; +} + +interface EnvVariable { + name: keyof EnvConfig; + required: boolean; + type: 'string' | 'number' | 'boolean' | 'enum'; + default?: any; + validate?: (value: any) => boolean; + enumValues?: string[]; + description: string; +} + +const ENV_SCHEMA: EnvVariable[] = [ + // Core Configuration + { + name: 'NODE_ENV', + required: false, + type: 'enum', + enumValues: ['development', 'production', 'test'], + default: 'development', + description: 'Node environment' + }, + { + name: 'LOG_LEVEL', + required: false, + type: 'enum', + enumValues: ['debug', 'info', 'warn', 'error', 'silent'], + default: 'info', + description: 'Logging level' + }, + + // Performance Configuration + { + name: 'INDEXER_PARALLEL', + required: false, + type: 'boolean', + default: true, + description: 'Enable parallel processing' + }, + { + name: 'INDEXER_WORKERS', + required: false, + type: 'number', + default: 4, + validate: (val) => val > 0 && val <= 16, + description: 'Number of worker threads (1-16)' + }, + { + name: 'INDEXER_CACHE_DIR', + required: false, + type: 'string', + default: '.indexer-cache', + description: 'Cache directory path' + }, + { + name: 'INDEXER_MAX_MEMORY', + required: false, + type: 'number', + default: 500, + validate: (val) => val > 0 && val <= 2000, + description: 'Maximum memory in MB (1-2000)' + }, + + // AI Features + { + name: 'ANTHROPIC_API_KEY', + required: false, + type: 'string', + validate: (val) => !val || val.startsWith('sk-ant-'), + description: 'Anthropic API key for AI features' + }, + { + name: 'CLAUDE_MODEL', + required: false, + type: 'string', + default: 'claude-opus-4-1-20250805', + description: 'Claude model to use' + }, + + // Slack Integration + { + name: 'SLACK_BOT_TOKEN', + required: false, + type: 'string', + validate: (val) => !val || val.startsWith('xoxb-'), + description: 'Slack bot token' + }, + { + name: 'SLACK_SIGNING_SECRET', + required: false, + type: 'string', + description: 'Slack signing secret for webhook verification' + }, + + // Linear Integration + { + name: 'LINEAR_API_KEY', + required: false, + type: 'string', + validate: (val) => !val || val.startsWith('lin_api_'), + description: 'Linear API key for ticket creation' + }, + + // Datadog Integration + { + name: 'DD_AGENT_HOST', + required: false, + type: 'string', + default: 'localhost', + description: 'Datadog agent host' + }, + { + name: 'DD_DOGSTATSD_PORT', + required: false, + type: 'number', + default: 8125, + description: 'Datadog StatsD port' + }, + + // GitHub Integration + { + name: 'GITHUB_TOKEN', + required: false, + type: 'string', + validate: (val) => !val || val.startsWith('ghp_') || val.startsWith('github_pat_'), + description: 'GitHub personal access token' + }, + + // API Server + { + name: 'API_PORT', + required: false, + type: 'number', + default: 4000, + validate: (val) => val > 0 && val <= 65535, + description: 'API server port' + }, + { + name: 'API_HOST', + required: false, + type: 'string', + default: 'localhost', + description: 'API server host' + }, + { + name: 'JWT_SECRET', + required: false, + type: 'string', + validate: (val) => !val || val.length >= 32, + description: 'JWT secret for API authentication (min 32 chars)' + } +]; + +export class EnvValidator { + private errors: string[] = []; + private warnings: string[] = []; + private config: Partial = {}; + + /** + * Validate all environment variables according to schema + */ + public validate(): { valid: boolean; config: EnvConfig; errors: string[]; warnings: string[] } { + this.errors = []; + this.warnings = []; + this.config = {}; + + for (const variable of ENV_SCHEMA) { + this.validateVariable(variable); + } + + return { + valid: this.errors.length === 0, + config: this.config as EnvConfig, + errors: this.errors, + warnings: this.warnings + }; + } + + private validateVariable(variable: EnvVariable): void { + const rawValue = process.env[variable.name]; + + // Check if required variable is missing + if (variable.required && !rawValue) { + this.errors.push(`Missing required environment variable: ${variable.name} - ${variable.description}`); + return; + } + + // Use default if not provided + if (!rawValue) { + if (variable.default !== undefined) { + this.config[variable.name] = variable.default; + } + return; + } + + // Parse and validate the value + let parsedValue: any = rawValue; + + switch (variable.type) { + case 'number': + parsedValue = parseInt(rawValue, 10); + if (isNaN(parsedValue)) { + this.errors.push(`Invalid number for ${variable.name}: ${rawValue}`); + return; + } + break; + + case 'boolean': + parsedValue = rawValue.toLowerCase() === 'true' || rawValue === '1'; + break; + + case 'enum': + if (variable.enumValues && !variable.enumValues.includes(rawValue)) { + this.errors.push( + `Invalid value for ${variable.name}: ${rawValue}. Must be one of: ${variable.enumValues.join(', ')}` + ); + return; + } + break; + } + + // Run custom validation if provided + if (variable.validate && !variable.validate(parsedValue)) { + this.errors.push(`Invalid value for ${variable.name}: ${rawValue} - ${variable.description}`); + return; + } + + this.config[variable.name] = parsedValue; + } + + /** + * Check if AI features are available + */ + public static hasAIFeatures(): boolean { + return !!process.env.ANTHROPIC_API_KEY; + } + + /** + * Check if Slack integration is configured + */ + public static hasSlackIntegration(): boolean { + return !!process.env.SLACK_BOT_TOKEN && !!process.env.SLACK_SIGNING_SECRET; + } + + /** + * Check if Linear integration is configured + */ + public static hasLinearIntegration(): boolean { + return !!process.env.LINEAR_API_KEY; + } + + /** + * Check if Datadog monitoring is configured + */ + public static hasDatadogMonitoring(): boolean { + return !!process.env.DD_AGENT_HOST; + } + + /** + * Get validated configuration or exit on error + */ + public static getConfig(): EnvConfig { + const validator = new EnvValidator(); + const result = validator.validate(); + + if (!result.valid) { + logger.error('Environment validation failed:'); + for (const error of result.errors) { + logger.error(` - ${error}`); + } + + logger.info('\n💡 Set environment variables:'); + logger.info(' export NODE_ENV=development'); + logger.info(' export LOG_LEVEL=info'); + logger.info('\nOptional integrations:'); + logger.info(' export ANTHROPIC_API_KEY=sk-ant-...'); + logger.info(' export SLACK_BOT_TOKEN=xoxb-...'); + logger.info(' export LINEAR_API_KEY=lin_api_...'); + + process.exit(1); + } + + if (result.warnings.length > 0) { + logger.warn('Environment warnings:'); + for (const warning of result.warnings) { + logger.warn(` - ${warning}`); + } + } + + // Log configuration summary + logger.debug('Environment configuration:'); + logger.debug(` NODE_ENV: ${result.config.NODE_ENV}`); + logger.debug(` LOG_LEVEL: ${result.config.LOG_LEVEL}`); + logger.debug(` AI Features: ${EnvValidator.hasAIFeatures() ? '✓' : '✗'}`); + logger.debug(` Slack Integration: ${EnvValidator.hasSlackIntegration() ? '✓' : '✗'}`); + logger.debug(` Linear Integration: ${EnvValidator.hasLinearIntegration() ? '✓' : '✗'}`); + logger.debug(` Datadog Monitoring: ${EnvValidator.hasDatadogMonitoring() ? '✓' : '✗'}`); + + return result.config; + } +} + +// Export singleton instance for easy access export default new EnvValidator(); \ No newline at end of file diff --git a/src/utils/file-utils.ts b/src/utils/file-utils.ts index 83a0345..af9ffdf 100644 --- a/src/utils/file-utils.ts +++ b/src/utils/file-utils.ts @@ -1,76 +1,76 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -/** - * Ensures that the directory for a given file path exists. - * Creates all necessary parent directories if they don't exist. - */ -export function ensureDirectoryExists(filePath: string): void { - const dir = path.dirname(filePath); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } -} - -/** - * Moves a file from source to destination, creating directories as needed. - */ -export function moveFile(source: string, destination: string): void { - if (!fs.existsSync(source)) { - throw new Error(`Source file does not exist: ${source}`); - } - - ensureDirectoryExists(destination); - fs.renameSync(source, destination); -} - -/** - * Lists all files matching a pattern in a directory. - */ -export function findFiles(dir: string, pattern: RegExp): string[] { - const files: string[] = []; - - if (!fs.existsSync(dir)) { - return files; - } - - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.isFile() && pattern.test(entry.name)) { - files.push(fullPath); - } - } - - return files; -} - -/** - * Cleans up empty directories recursively. - */ -export function cleanupEmptyDirectories(dir: string): void { - if (!fs.existsSync(dir)) { - return; - } - - const entries = fs.readdirSync(dir); - - if (entries.length === 0) { - fs.rmdirSync(dir); - } else { - for (const entry of entries) { - const fullPath = path.join(dir, entry); - const stat = fs.statSync(fullPath); - if (stat.isDirectory()) { - cleanupEmptyDirectories(fullPath); - } - } - - // Check again after cleaning subdirectories - const remainingEntries = fs.readdirSync(dir); - if (remainingEntries.length === 0) { - fs.rmdirSync(dir); - } - } +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Ensures that the directory for a given file path exists. + * Creates all necessary parent directories if they don't exist. + */ +export function ensureDirectoryExists(filePath: string): void { + const dir = path.dirname(filePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } +} + +/** + * Moves a file from source to destination, creating directories as needed. + */ +export function moveFile(source: string, destination: string): void { + if (!fs.existsSync(source)) { + throw new Error(`Source file does not exist: ${source}`); + } + + ensureDirectoryExists(destination); + fs.renameSync(source, destination); +} + +/** + * Lists all files matching a pattern in a directory. + */ +export function findFiles(dir: string, pattern: RegExp): string[] { + const files: string[] = []; + + if (!fs.existsSync(dir)) { + return files; + } + + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isFile() && pattern.test(entry.name)) { + files.push(fullPath); + } + } + + return files; +} + +/** + * Cleans up empty directories recursively. + */ +export function cleanupEmptyDirectories(dir: string): void { + if (!fs.existsSync(dir)) { + return; + } + + const entries = fs.readdirSync(dir); + + if (entries.length === 0) { + fs.rmdirSync(dir); + } else { + for (const entry of entries) { + const fullPath = path.join(dir, entry); + const stat = fs.statSync(fullPath); + if (stat.isDirectory()) { + cleanupEmptyDirectories(fullPath); + } + } + + // Check again after cleaning subdirectories + const remainingEntries = fs.readdirSync(dir); + if (remainingEntries.length === 0) { + fs.rmdirSync(dir); + } + } } \ No newline at end of file diff --git a/src/utils/formatter.ts b/src/utils/formatter.ts index 7e86559..995e436 100644 --- a/src/utils/formatter.ts +++ b/src/utils/formatter.ts @@ -1,305 +1,305 @@ -import { ProjectIndex, FunctionInfo, ClassInfo } from '../types'; -import * as chalk from 'chalk'; - -export class Formatter { - private useColor: boolean; - - constructor(useColor: boolean = true) { - this.useColor = useColor; - } - - public formatStatistics(stats: ProjectIndex['statistics']): string { - const lines: string[] = []; - - lines.push(this.header('Project Statistics')); - lines.push(`Total Files: ${this.value(stats.totalFiles)}`); - lines.push(`Total Functions: ${this.value(stats.totalFunctions)}`); - lines.push(`Total Classes: ${this.value(stats.totalClasses)}`); - lines.push(`Total Constants: ${this.value(stats.totalConstants)}`); - - if (stats.avgComplexity) { - lines.push(`Average Complexity: ${this.value(stats.avgComplexity.toFixed(2))}`); - } - - if (stats.languages && Object.keys(stats.languages).length > 0) { - lines.push(''); - lines.push(this.header('Languages')); - for (const [lang, count] of Object.entries(stats.languages)) { - const percentage = ((count / stats.totalFiles) * 100).toFixed(1); - lines.push(` ${lang}: ${this.value(count)} files (${percentage}%)`); - } - } - - return lines.join('\n'); - } - - public formatFunction(func: FunctionInfo, filePath?: string): string { - const lines: string[] = []; - - lines.push(this.functionSignature(func)); - - if (filePath) { - lines.push(` File: ${this.path(filePath)}`); - } - - if (func.startLine && func.endLine) { - lines.push(` Lines: ${func.startLine}-${func.endLine}`); - } - - if (func.async) { - lines.push(` ${this.tag('async')}`); - } - - if (func.generator) { - lines.push(` ${this.tag('generator')}`); - } - - return lines.join('\n'); - } - - public formatClass(cls: ClassInfo, filePath?: string): string { - const lines: string[] = []; - - lines.push(this.classSignature(cls)); - - if (filePath) { - lines.push(` File: ${this.path(filePath)}`); - } - - if (cls.startLine && cls.endLine) { - lines.push(` Lines: ${cls.startLine}-${cls.endLine}`); - } - - if (cls.extends) { - lines.push(` Extends: ${this.type(cls.extends)}`); - } - - if (cls.implements && cls.implements.length > 0) { - lines.push(` Implements: ${cls.implements.map(i => this.type(i)).join(', ')}`); - } - - if (cls.methods.length > 0) { - lines.push(` Methods: ${cls.methods.length}`); - for (const method of cls.methods.slice(0, 5)) { - lines.push(` - ${this.methodSignature(method)}`); - } - if (cls.methods.length > 5) { - lines.push(` ... and ${cls.methods.length - 5} more`); - } - } - - if (cls.properties.length > 0) { - lines.push(` Properties: ${cls.properties.length}`); - for (const prop of cls.properties.slice(0, 5)) { - lines.push(` - ${this.propertySignature(prop)}`); - } - if (cls.properties.length > 5) { - lines.push(` ... and ${cls.properties.length - 5} more`); - } - } - - return lines.join('\n'); - } - - public formatQueryResults(results: any[]): string { - const lines: string[] = []; - - if (results.length === 0) { - return 'No results found'; - } - - lines.push(this.header(`Found ${results.length} results`)); - lines.push(''); - - for (const result of results.slice(0, 20)) { - switch (result.type) { - case 'function': - lines.push(this.formatQueryResult(result)); - break; - case 'class': - lines.push(this.formatQueryResult(result)); - break; - case 'constant': - lines.push(this.formatQueryResult(result)); - break; - default: - lines.push(` ${result.name} (${result.type})`); - } - } - - if (results.length > 20) { - lines.push(''); - lines.push(`... and ${results.length - 20} more results`); - } - - return lines.join('\n'); - } - - private formatQueryResult(result: any): string { - let line = ` ${this.name(result.name)} `; - line += `(${this.type(result.type)}) `; - line += `at ${this.path(result.file)}:${result.line}`; - - if (result.params) { - const params = result.params.map((p: any) => p.name || p).join(', '); - line += ` (${params})`; - } - - return line; - } - - public formatHealthReport(report: any): string { - const lines: string[] = []; - - lines.push(this.header('Project Health Report')); - lines.push(`Overall Score: ${this.healthScore(report.score)}`); - lines.push(''); - - if (report.issues && report.issues.length > 0) { - lines.push(this.header('Issues Found')); - for (const issue of report.issues) { - lines.push(` ${this.warning('⚠')} ${issue.message}`); - if (issue.file) { - lines.push(` File: ${this.path(issue.file)}`); - } - } - } - - if (report.recommendations && report.recommendations.length > 0) { - lines.push(''); - lines.push(this.header('Recommendations')); - for (const rec of report.recommendations) { - lines.push(` ✓ ${rec}`); - } - } - - return lines.join('\n'); - } - - public formatDependencyTree(deps: Record, rootFile?: string): string { - const lines: string[] = []; - const visited = new Set(); - - const formatNode = (file: string, indent: number = 0) => { - if (visited.has(file)) { - lines.push(`${' '.repeat(indent)}${this.path(file)} ${this.tag('circular')}`); - return; - } - - visited.add(file); - lines.push(`${' '.repeat(indent)}${this.path(file)}`); - - const fileDeps = deps[file]; - if (fileDeps) { - for (const dep of fileDeps) { - formatNode(dep, indent + 1); - } - } - }; - - if (rootFile) { - formatNode(rootFile); - } else { - // Show all root files (files that are not dependencies of others) - const allDeps = new Set(Object.values(deps).flat()); - const roots = Object.keys(deps).filter(file => !allDeps.has(file)); - - for (const root of roots) { - formatNode(root); - lines.push(''); - } - } - - return lines.join('\n'); - } - - // Color/style helpers - private header(text: string): string { - return this.useColor ? chalk.bold.blue(text) : text; - } - - private value(value: any): string { - return this.useColor ? chalk.cyan(String(value)) : String(value); - } - - private path(path: string): string { - return this.useColor ? chalk.gray(path) : path; - } - - private name(name: string): string { - return this.useColor ? chalk.yellow(name) : name; - } - - private type(type: string): string { - return this.useColor ? chalk.magenta(type) : type; - } - - private tag(tag: string): string { - return this.useColor ? chalk.bgBlue.white(` ${tag} `) : `[${tag}]`; - } - - private warning(text: string): string { - return this.useColor ? chalk.red.bold(text) : `WARNING: ${text}`; - } - - private functionSignature(func: FunctionInfo): string { - const params = func.parameters.map(p => `${p.name}: ${this.type(p.type)}`).join(', '); - const signature = `${this.name(func.name)}(${params}): ${this.type(func.returnType)}`; - - if (func.async) { - return `async ${signature}`; - } - if (func.generator) { - return `function* ${signature}`; - } - return signature; - } - - private classSignature(cls: ClassInfo): string { - let signature = `class ${this.name(cls.name)}`; - - if (cls.extends) { - signature += ` extends ${this.type(cls.extends)}`; - } - - if (cls.implements && cls.implements.length > 0) { - signature += ` implements ${cls.implements.map(i => this.type(i)).join(', ')}`; - } - - return signature; - } - - private methodSignature(method: any): string { - const visibility = method.visibility === 'private' ? '-' : - method.visibility === 'protected' ? '#' : '+'; - const staticMod = method.static ? 'static ' : ''; - return `${visibility} ${staticMod}${method.name}()`; - } - - private propertySignature(prop: any): string { - const visibility = prop.visibility === 'private' ? '-' : - prop.visibility === 'protected' ? '#' : '+'; - const staticMod = prop.static ? 'static ' : ''; - const readonlyMod = prop.readonly ? 'readonly ' : ''; - return `${visibility} ${staticMod}${readonlyMod}${prop.name}: ${this.type(prop.type)}`; - } - - private healthScore(score: number): string { - let color = chalk.green; - let label = 'Excellent'; - - if (score < 50) { - color = chalk.red; - label = 'Poor'; - } else if (score < 70) { - color = chalk.yellow; - label = 'Fair'; - } else if (score < 85) { - color = chalk.cyan; - label = 'Good'; - } - - const scoreStr = `${score}/100 (${label})`; - return this.useColor ? color(scoreStr) : scoreStr; - } +import { ProjectIndex, FunctionInfo, ClassInfo } from '../types'; +import * as chalk from 'chalk'; + +export class Formatter { + private useColor: boolean; + + constructor(useColor: boolean = true) { + this.useColor = useColor; + } + + public formatStatistics(stats: ProjectIndex['statistics']): string { + const lines: string[] = []; + + lines.push(this.header('Project Statistics')); + lines.push(`Total Files: ${this.value(stats.totalFiles)}`); + lines.push(`Total Functions: ${this.value(stats.totalFunctions)}`); + lines.push(`Total Classes: ${this.value(stats.totalClasses)}`); + lines.push(`Total Constants: ${this.value(stats.totalConstants)}`); + + if (stats.avgComplexity) { + lines.push(`Average Complexity: ${this.value(stats.avgComplexity.toFixed(2))}`); + } + + if (stats.languages && Object.keys(stats.languages).length > 0) { + lines.push(''); + lines.push(this.header('Languages')); + for (const [lang, count] of Object.entries(stats.languages)) { + const percentage = ((count / stats.totalFiles) * 100).toFixed(1); + lines.push(` ${lang}: ${this.value(count)} files (${percentage}%)`); + } + } + + return lines.join('\n'); + } + + public formatFunction(func: FunctionInfo, filePath?: string): string { + const lines: string[] = []; + + lines.push(this.functionSignature(func)); + + if (filePath) { + lines.push(` File: ${this.path(filePath)}`); + } + + if (func.startLine && func.endLine) { + lines.push(` Lines: ${func.startLine}-${func.endLine}`); + } + + if (func.async) { + lines.push(` ${this.tag('async')}`); + } + + if (func.generator) { + lines.push(` ${this.tag('generator')}`); + } + + return lines.join('\n'); + } + + public formatClass(cls: ClassInfo, filePath?: string): string { + const lines: string[] = []; + + lines.push(this.classSignature(cls)); + + if (filePath) { + lines.push(` File: ${this.path(filePath)}`); + } + + if (cls.startLine && cls.endLine) { + lines.push(` Lines: ${cls.startLine}-${cls.endLine}`); + } + + if (cls.extends) { + lines.push(` Extends: ${this.type(cls.extends)}`); + } + + if (cls.implements && cls.implements.length > 0) { + lines.push(` Implements: ${cls.implements.map(i => this.type(i)).join(', ')}`); + } + + if (cls.methods.length > 0) { + lines.push(` Methods: ${cls.methods.length}`); + for (const method of cls.methods.slice(0, 5)) { + lines.push(` - ${this.methodSignature(method)}`); + } + if (cls.methods.length > 5) { + lines.push(` ... and ${cls.methods.length - 5} more`); + } + } + + if (cls.properties.length > 0) { + lines.push(` Properties: ${cls.properties.length}`); + for (const prop of cls.properties.slice(0, 5)) { + lines.push(` - ${this.propertySignature(prop)}`); + } + if (cls.properties.length > 5) { + lines.push(` ... and ${cls.properties.length - 5} more`); + } + } + + return lines.join('\n'); + } + + public formatQueryResults(results: any[]): string { + const lines: string[] = []; + + if (results.length === 0) { + return 'No results found'; + } + + lines.push(this.header(`Found ${results.length} results`)); + lines.push(''); + + for (const result of results.slice(0, 20)) { + switch (result.type) { + case 'function': + lines.push(this.formatQueryResult(result)); + break; + case 'class': + lines.push(this.formatQueryResult(result)); + break; + case 'constant': + lines.push(this.formatQueryResult(result)); + break; + default: + lines.push(` ${result.name} (${result.type})`); + } + } + + if (results.length > 20) { + lines.push(''); + lines.push(`... and ${results.length - 20} more results`); + } + + return lines.join('\n'); + } + + private formatQueryResult(result: any): string { + let line = ` ${this.name(result.name)} `; + line += `(${this.type(result.type)}) `; + line += `at ${this.path(result.file)}:${result.line}`; + + if (result.params) { + const params = result.params.map((p: any) => p.name || p).join(', '); + line += ` (${params})`; + } + + return line; + } + + public formatHealthReport(report: any): string { + const lines: string[] = []; + + lines.push(this.header('Project Health Report')); + lines.push(`Overall Score: ${this.healthScore(report.score)}`); + lines.push(''); + + if (report.issues && report.issues.length > 0) { + lines.push(this.header('Issues Found')); + for (const issue of report.issues) { + lines.push(` ${this.warning('⚠')} ${issue.message}`); + if (issue.file) { + lines.push(` File: ${this.path(issue.file)}`); + } + } + } + + if (report.recommendations && report.recommendations.length > 0) { + lines.push(''); + lines.push(this.header('Recommendations')); + for (const rec of report.recommendations) { + lines.push(` ✓ ${rec}`); + } + } + + return lines.join('\n'); + } + + public formatDependencyTree(deps: Record, rootFile?: string): string { + const lines: string[] = []; + const visited = new Set(); + + const formatNode = (file: string, indent: number = 0) => { + if (visited.has(file)) { + lines.push(`${' '.repeat(indent)}${this.path(file)} ${this.tag('circular')}`); + return; + } + + visited.add(file); + lines.push(`${' '.repeat(indent)}${this.path(file)}`); + + const fileDeps = deps[file]; + if (fileDeps) { + for (const dep of fileDeps) { + formatNode(dep, indent + 1); + } + } + }; + + if (rootFile) { + formatNode(rootFile); + } else { + // Show all root files (files that are not dependencies of others) + const allDeps = new Set(Object.values(deps).flat()); + const roots = Object.keys(deps).filter(file => !allDeps.has(file)); + + for (const root of roots) { + formatNode(root); + lines.push(''); + } + } + + return lines.join('\n'); + } + + // Color/style helpers + private header(text: string): string { + return this.useColor ? chalk.bold.blue(text) : text; + } + + private value(value: any): string { + return this.useColor ? chalk.cyan(String(value)) : String(value); + } + + private path(path: string): string { + return this.useColor ? chalk.gray(path) : path; + } + + private name(name: string): string { + return this.useColor ? chalk.yellow(name) : name; + } + + private type(type: string): string { + return this.useColor ? chalk.magenta(type) : type; + } + + private tag(tag: string): string { + return this.useColor ? chalk.bgBlue.white(` ${tag} `) : `[${tag}]`; + } + + private warning(text: string): string { + return this.useColor ? chalk.red.bold(text) : `WARNING: ${text}`; + } + + private functionSignature(func: FunctionInfo): string { + const params = func.parameters.map(p => `${p.name}: ${this.type(p.type)}`).join(', '); + const signature = `${this.name(func.name)}(${params}): ${this.type(func.returnType)}`; + + if (func.async) { + return `async ${signature}`; + } + if (func.generator) { + return `function* ${signature}`; + } + return signature; + } + + private classSignature(cls: ClassInfo): string { + let signature = `class ${this.name(cls.name)}`; + + if (cls.extends) { + signature += ` extends ${this.type(cls.extends)}`; + } + + if (cls.implements && cls.implements.length > 0) { + signature += ` implements ${cls.implements.map(i => this.type(i)).join(', ')}`; + } + + return signature; + } + + private methodSignature(method: any): string { + const visibility = method.visibility === 'private' ? '-' : + method.visibility === 'protected' ? '#' : '+'; + const staticMod = method.static ? 'static ' : ''; + return `${visibility} ${staticMod}${method.name}()`; + } + + private propertySignature(prop: any): string { + const visibility = prop.visibility === 'private' ? '-' : + prop.visibility === 'protected' ? '#' : '+'; + const staticMod = prop.static ? 'static ' : ''; + const readonlyMod = prop.readonly ? 'readonly ' : ''; + return `${visibility} ${staticMod}${readonlyMod}${prop.name}: ${this.type(prop.type)}`; + } + + private healthScore(score: number): string { + let color = chalk.green; + let label = 'Excellent'; + + if (score < 50) { + color = chalk.red; + label = 'Poor'; + } else if (score < 70) { + color = chalk.yellow; + label = 'Fair'; + } else if (score < 85) { + color = chalk.cyan; + label = 'Good'; + } + + const scoreStr = `${score}/100 (${label})`; + return this.useColor ? color(scoreStr) : scoreStr; + } } \ No newline at end of file diff --git a/src/utils/logger.ts b/src/utils/logger.ts index aab4e36..6faf48a 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,174 +1,174 @@ -/** - * Logger utility for consistent logging across the application - */ - -export enum LogLevel { - DEBUG = 0, - INFO = 1, - WARN = 2, - ERROR = 3, - SILENT = 4 -} - -export interface LoggerOptions { - level?: LogLevel; - prefix?: string; - timestamp?: boolean; - colors?: boolean; -} - -export class Logger { - private level: LogLevel; - private prefix: string; - private showTimestamp: boolean; - private useColors: boolean; - - // ANSI color codes - private colors = { - reset: '\x1b[0m', - red: '\x1b[31m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - gray: '\x1b[90m', - green: '\x1b[32m' - }; - - constructor(options: LoggerOptions = {}) { - this.level = options.level ?? this.getLevelFromEnv(); - this.prefix = options.prefix || ''; - this.showTimestamp = options.timestamp ?? true; - this.useColors = options.colors ?? process.stdout.isTTY; - } - - private getLevelFromEnv(): LogLevel { - const envLevel = process.env.LOG_LEVEL?.toUpperCase(); - switch (envLevel) { - case 'DEBUG': return LogLevel.DEBUG; - case 'INFO': return LogLevel.INFO; - case 'WARN': return LogLevel.WARN; - case 'ERROR': return LogLevel.ERROR; - case 'SILENT': return LogLevel.SILENT; - default: return process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG; - } - } - - private formatMessage(level: string, message: string, ...args: any[]): string { - const parts: string[] = []; - - if (this.showTimestamp) { - const timestamp = new Date().toISOString(); - parts.push(this.useColors ? `${this.colors.gray}${timestamp}${this.colors.reset}` : timestamp); - } - - if (this.prefix) { - parts.push(`[${this.prefix}]`); - } - - parts.push(level); - parts.push(message); - - // Format additional arguments - if (args.length > 0) { - const formatted = args.map(arg => { - if (typeof arg === 'object') { - try { - return JSON.stringify(arg, null, 2); - } catch { - return String(arg); - } - } - return String(arg); - }).join(' '); - parts.push(formatted); - } - - return parts.join(' '); - } - - debug(message: string, ...args: any[]): void { - if (this.level <= LogLevel.DEBUG) { - const level = this.useColors ? `${this.colors.gray}[DEBUG]${this.colors.reset}` : '[DEBUG]'; - console.log(this.formatMessage(level, message, ...args)); - } - } - - info(message: string, ...args: any[]): void { - if (this.level <= LogLevel.INFO) { - const level = this.useColors ? `${this.colors.blue}[INFO]${this.colors.reset}` : '[INFO]'; - console.log(this.formatMessage(level, message, ...args)); - } - } - - warn(message: string, ...args: any[]): void { - if (this.level <= LogLevel.WARN) { - const level = this.useColors ? `${this.colors.yellow}[WARN]${this.colors.reset}` : '[WARN]'; - console.warn(this.formatMessage(level, message, ...args)); - } - } - - error(message: string, ...args: any[]): void { - if (this.level <= LogLevel.ERROR) { - const level = this.useColors ? `${this.colors.red}[ERROR]${this.colors.reset}` : '[ERROR]'; - console.error(this.formatMessage(level, message, ...args)); - } - } - - success(message: string, ...args: any[]): void { - if (this.level <= LogLevel.INFO) { - const level = this.useColors ? `${this.colors.green}[SUCCESS]${this.colors.reset}` : '[SUCCESS]'; - console.log(this.formatMessage(level, message, ...args)); - } - } - - // Create a child logger with a specific prefix - child(prefix: string): Logger { - const childPrefix = this.prefix ? `${this.prefix}:${prefix}` : prefix; - return new Logger({ - level: this.level, - prefix: childPrefix, - timestamp: this.showTimestamp, - colors: this.useColors - }); - } - - // Change log level at runtime - setLevel(level: LogLevel): void { - this.level = level; - } - - // Progress bar for long operations - progress(current: number, total: number, message?: string): void { - if (this.level > LogLevel.INFO) return; - - const percentage = Math.round((current / total) * 100); - const barLength = 30; - const filled = Math.round((percentage / 100) * barLength); - const empty = barLength - filled; - - const bar = '█'.repeat(filled) + '░'.repeat(empty); - const progressStr = `${bar} ${percentage}% (${current}/${total})`; - - const output = message ? `${progressStr} - ${message}` : progressStr; - - // Use carriage return to overwrite the same line - process.stdout.write(`\r${output}`); - - if (current >= total) { - process.stdout.write('\n'); - } - } -} - -// Singleton instance for the default logger -const defaultLogger = new Logger(); - -// Export both the class and convenient methods -export default defaultLogger; - -// Convenience functions that use the default logger -export const debug = (message: string, ...args: any[]) => defaultLogger.debug(message, ...args); -export const info = (message: string, ...args: any[]) => defaultLogger.info(message, ...args); -export const warn = (message: string, ...args: any[]) => defaultLogger.warn(message, ...args); -export const error = (message: string, ...args: any[]) => defaultLogger.error(message, ...args); -export const success = (message: string, ...args: any[]) => defaultLogger.success(message, ...args); +/** + * Logger utility for consistent logging across the application + */ + +export enum LogLevel { + DEBUG = 0, + INFO = 1, + WARN = 2, + ERROR = 3, + SILENT = 4 +} + +export interface LoggerOptions { + level?: LogLevel; + prefix?: string; + timestamp?: boolean; + colors?: boolean; +} + +export class Logger { + private level: LogLevel; + private prefix: string; + private showTimestamp: boolean; + private useColors: boolean; + + // ANSI color codes + private colors = { + reset: '\x1b[0m', + red: '\x1b[31m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + gray: '\x1b[90m', + green: '\x1b[32m' + }; + + constructor(options: LoggerOptions = {}) { + this.level = options.level ?? this.getLevelFromEnv(); + this.prefix = options.prefix || ''; + this.showTimestamp = options.timestamp ?? true; + this.useColors = options.colors ?? process.stdout.isTTY; + } + + private getLevelFromEnv(): LogLevel { + const envLevel = process.env.LOG_LEVEL?.toUpperCase(); + switch (envLevel) { + case 'DEBUG': return LogLevel.DEBUG; + case 'INFO': return LogLevel.INFO; + case 'WARN': return LogLevel.WARN; + case 'ERROR': return LogLevel.ERROR; + case 'SILENT': return LogLevel.SILENT; + default: return process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG; + } + } + + private formatMessage(level: string, message: string, ...args: any[]): string { + const parts: string[] = []; + + if (this.showTimestamp) { + const timestamp = new Date().toISOString(); + parts.push(this.useColors ? `${this.colors.gray}${timestamp}${this.colors.reset}` : timestamp); + } + + if (this.prefix) { + parts.push(`[${this.prefix}]`); + } + + parts.push(level); + parts.push(message); + + // Format additional arguments + if (args.length > 0) { + const formatted = args.map(arg => { + if (typeof arg === 'object') { + try { + return JSON.stringify(arg, null, 2); + } catch { + return String(arg); + } + } + return String(arg); + }).join(' '); + parts.push(formatted); + } + + return parts.join(' '); + } + + debug(message: string, ...args: any[]): void { + if (this.level <= LogLevel.DEBUG) { + const level = this.useColors ? `${this.colors.gray}[DEBUG]${this.colors.reset}` : '[DEBUG]'; + console.log(this.formatMessage(level, message, ...args)); + } + } + + info(message: string, ...args: any[]): void { + if (this.level <= LogLevel.INFO) { + const level = this.useColors ? `${this.colors.blue}[INFO]${this.colors.reset}` : '[INFO]'; + console.log(this.formatMessage(level, message, ...args)); + } + } + + warn(message: string, ...args: any[]): void { + if (this.level <= LogLevel.WARN) { + const level = this.useColors ? `${this.colors.yellow}[WARN]${this.colors.reset}` : '[WARN]'; + console.warn(this.formatMessage(level, message, ...args)); + } + } + + error(message: string, ...args: any[]): void { + if (this.level <= LogLevel.ERROR) { + const level = this.useColors ? `${this.colors.red}[ERROR]${this.colors.reset}` : '[ERROR]'; + console.error(this.formatMessage(level, message, ...args)); + } + } + + success(message: string, ...args: any[]): void { + if (this.level <= LogLevel.INFO) { + const level = this.useColors ? `${this.colors.green}[SUCCESS]${this.colors.reset}` : '[SUCCESS]'; + console.log(this.formatMessage(level, message, ...args)); + } + } + + // Create a child logger with a specific prefix + child(prefix: string): Logger { + const childPrefix = this.prefix ? `${this.prefix}:${prefix}` : prefix; + return new Logger({ + level: this.level, + prefix: childPrefix, + timestamp: this.showTimestamp, + colors: this.useColors + }); + } + + // Change log level at runtime + setLevel(level: LogLevel): void { + this.level = level; + } + + // Progress bar for long operations + progress(current: number, total: number, message?: string): void { + if (this.level > LogLevel.INFO) return; + + const percentage = Math.round((current / total) * 100); + const barLength = 30; + const filled = Math.round((percentage / 100) * barLength); + const empty = barLength - filled; + + const bar = '█'.repeat(filled) + '░'.repeat(empty); + const progressStr = `${bar} ${percentage}% (${current}/${total})`; + + const output = message ? `${progressStr} - ${message}` : progressStr; + + // Use carriage return to overwrite the same line + process.stdout.write(`\r${output}`); + + if (current >= total) { + process.stdout.write('\n'); + } + } +} + +// Singleton instance for the default logger +const defaultLogger = new Logger(); + +// Export both the class and convenient methods +export default defaultLogger; + +// Convenience functions that use the default logger +export const debug = (message: string, ...args: any[]) => defaultLogger.debug(message, ...args); +export const info = (message: string, ...args: any[]) => defaultLogger.info(message, ...args); +export const warn = (message: string, ...args: any[]) => defaultLogger.warn(message, ...args); +export const error = (message: string, ...args: any[]) => defaultLogger.error(message, ...args); +export const success = (message: string, ...args: any[]) => defaultLogger.success(message, ...args); export const progress = (current: number, total: number, message?: string) => defaultLogger.progress(current, total, message); \ No newline at end of file diff --git a/src/utils/progress.ts b/src/utils/progress.ts index afac69b..ec11989 100644 --- a/src/utils/progress.ts +++ b/src/utils/progress.ts @@ -1,293 +1,293 @@ -/** - * Progress indicator utilities for long-running operations - */ - -import { Logger } from './logger'; - -export interface ProgressOptions { - total: number; - message?: string; - showPercentage?: boolean; - showBar?: boolean; - showETA?: boolean; - updateInterval?: number; // Minimum ms between updates -} - -export class ProgressTracker { - private current: number = 0; - private total: number; - private startTime: number; - private lastUpdateTime: number = 0; - private updateInterval: number; - private message?: string; - private showPercentage: boolean; - private showBar: boolean; - private showETA: boolean; - private logger: Logger; - private isCompleted: boolean = false; - - constructor(options: ProgressOptions, logger?: Logger) { - this.total = options.total; - this.message = options.message; - this.showPercentage = options.showPercentage ?? true; - this.showBar = options.showBar ?? true; - this.showETA = options.showETA ?? true; - this.updateInterval = options.updateInterval ?? 100; // Default 100ms - this.startTime = Date.now(); - this.logger = logger || new Logger({ prefix: 'Progress' }); - } - - /** - * Increment progress by 1 - */ - increment(message?: string): void { - this.update(this.current + 1, message); - } - - /** - * Update progress to a specific value - */ - update(current: number, message?: string): void { - this.current = Math.min(current, this.total); - - if (message) { - this.message = message; - } - - // Throttle updates - const now = Date.now(); - if (now - this.lastUpdateTime < this.updateInterval && this.current < this.total) { - return; - } - this.lastUpdateTime = now; - - this.render(); - - if (this.current >= this.total && !this.isCompleted) { - this.complete(); - } - } - - /** - * Render the progress bar - */ - private render(): void { - const percentage = Math.round((this.current / this.total) * 100); - const elapsed = Date.now() - this.startTime; - const rate = this.current / (elapsed / 1000); - - let output = ''; - - // Progress bar - if (this.showBar) { - const barLength = 30; - const filled = Math.round((percentage / 100) * barLength); - const empty = barLength - filled; - const bar = '█'.repeat(filled) + '░'.repeat(empty); - output += bar + ' '; - } - - // Percentage - if (this.showPercentage) { - output += `${percentage}% `; - } - - // Current/Total - output += `(${this.current}/${this.total})`; - - // ETA - if (this.showETA && rate > 0 && this.current < this.total) { - const remaining = this.total - this.current; - const eta = remaining / rate; - output += ` ETA: ${this.formatTime(eta)}`; - } - - // Rate - if (rate > 0) { - output += ` [${rate.toFixed(1)}/s]`; - } - - // Message - if (this.message) { - output += ` - ${this.message}`; - } - - // Use carriage return to overwrite the same line - process.stdout.write(`\r${output}`); - } - - /** - * Mark progress as complete - */ - complete(message?: string): void { - this.current = this.total; - this.isCompleted = true; - this.render(); - - const elapsed = (Date.now() - this.startTime) / 1000; - const finalMessage = message || this.message || 'Complete'; - - process.stdout.write('\n'); - this.logger.success(`${finalMessage} (${elapsed.toFixed(1)}s)`); - } - - /** - * Format time in seconds to human-readable format - */ - private formatTime(seconds: number): string { - if (seconds < 60) { - return `${Math.round(seconds)}s`; - } - const minutes = Math.floor(seconds / 60); - const secs = Math.round(seconds % 60); - return `${minutes}m ${secs}s`; - } - - /** - * Stop the progress tracker without completing - */ - stop(): void { - process.stdout.write('\n'); - } -} - -/** - * Simple spinner for indeterminate progress - */ -export class Spinner { - private frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; - private currentFrame = 0; - private interval?: NodeJS.Timeout; - private message: string; - private logger: Logger; - - constructor(message: string, logger?: Logger) { - this.message = message; - this.logger = logger || new Logger({ prefix: 'Spinner' }); - } - - start(): void { - this.interval = setInterval(() => { - const frame = this.frames[this.currentFrame]; - process.stdout.write(`\r${frame} ${this.message}`); - this.currentFrame = (this.currentFrame + 1) % this.frames.length; - }, 80); - } - - update(message: string): void { - this.message = message; - } - - stop(success: boolean = true, message?: string): void { - if (this.interval) { - clearInterval(this.interval); - process.stdout.write('\r' + ' '.repeat(this.message.length + 3) + '\r'); - - if (message) { - if (success) { - this.logger.success(message); - } else { - this.logger.error(message); - } - } - } - } -} - -/** - * Progress bar for file operations - */ -export class FileProgress { - private tracker: ProgressTracker; - private logger: Logger; - private files: string[] = []; - private errors: string[] = []; - - constructor(totalFiles: number, logger?: Logger) { - this.logger = logger || new Logger({ prefix: 'FileProgress' }); - this.tracker = new ProgressTracker({ - total: totalFiles, - message: 'Processing files', - showBar: true, - showPercentage: true, - showETA: true - }, this.logger); - } - - /** - * Process a file and update progress - */ - processFile(fileName: string, success: boolean = true): void { - if (success) { - this.files.push(fileName); - } else { - this.errors.push(fileName); - } - - const shortName = fileName.length > 40 - ? '...' + fileName.slice(-37) - : fileName; - - this.tracker.increment(shortName); - } - - /** - * Complete file processing - */ - complete(): void { - const message = this.errors.length > 0 - ? `Processed ${this.files.length} files (${this.errors.length} errors)` - : `Processed ${this.files.length} files`; - - this.tracker.complete(message); - - if (this.errors.length > 0) { - this.logger.warn(`Files with errors: ${this.errors.join(', ')}`); - } - } - - /** - * Get processing statistics - */ - getStats(): { processed: number; errors: number; errorRate: number } { - const processed = this.files.length + this.errors.length; - const errorRate = processed > 0 ? this.errors.length / processed : 0; - - return { - processed, - errors: this.errors.length, - errorRate - }; - } -} - -/** - * Helper function for simple progress tracking - */ -export function withProgress( - items: T[], - processor: (item: T, index: number) => Promise | void, - message?: string -): Promise { - return new Promise(async (resolve, reject) => { - const tracker = new ProgressTracker({ - total: items.length, - message: message || 'Processing items', - showBar: true, - showPercentage: true, - showETA: true - }); - - try { - for (let i = 0; i < items.length; i++) { - await processor(items[i], i); - tracker.update(i + 1); - } - tracker.complete(); - resolve(); - } catch (error) { - tracker.stop(); - reject(error); - } - }); +/** + * Progress indicator utilities for long-running operations + */ + +import { Logger } from './logger'; + +export interface ProgressOptions { + total: number; + message?: string; + showPercentage?: boolean; + showBar?: boolean; + showETA?: boolean; + updateInterval?: number; // Minimum ms between updates +} + +export class ProgressTracker { + private current: number = 0; + private total: number; + private startTime: number; + private lastUpdateTime: number = 0; + private updateInterval: number; + private message?: string; + private showPercentage: boolean; + private showBar: boolean; + private showETA: boolean; + private logger: Logger; + private isCompleted: boolean = false; + + constructor(options: ProgressOptions, logger?: Logger) { + this.total = options.total; + this.message = options.message; + this.showPercentage = options.showPercentage ?? true; + this.showBar = options.showBar ?? true; + this.showETA = options.showETA ?? true; + this.updateInterval = options.updateInterval ?? 100; // Default 100ms + this.startTime = Date.now(); + this.logger = logger || new Logger({ prefix: 'Progress' }); + } + + /** + * Increment progress by 1 + */ + increment(message?: string): void { + this.update(this.current + 1, message); + } + + /** + * Update progress to a specific value + */ + update(current: number, message?: string): void { + this.current = Math.min(current, this.total); + + if (message) { + this.message = message; + } + + // Throttle updates + const now = Date.now(); + if (now - this.lastUpdateTime < this.updateInterval && this.current < this.total) { + return; + } + this.lastUpdateTime = now; + + this.render(); + + if (this.current >= this.total && !this.isCompleted) { + this.complete(); + } + } + + /** + * Render the progress bar + */ + private render(): void { + const percentage = Math.round((this.current / this.total) * 100); + const elapsed = Date.now() - this.startTime; + const rate = this.current / (elapsed / 1000); + + let output = ''; + + // Progress bar + if (this.showBar) { + const barLength = 30; + const filled = Math.round((percentage / 100) * barLength); + const empty = barLength - filled; + const bar = '█'.repeat(filled) + '░'.repeat(empty); + output += bar + ' '; + } + + // Percentage + if (this.showPercentage) { + output += `${percentage}% `; + } + + // Current/Total + output += `(${this.current}/${this.total})`; + + // ETA + if (this.showETA && rate > 0 && this.current < this.total) { + const remaining = this.total - this.current; + const eta = remaining / rate; + output += ` ETA: ${this.formatTime(eta)}`; + } + + // Rate + if (rate > 0) { + output += ` [${rate.toFixed(1)}/s]`; + } + + // Message + if (this.message) { + output += ` - ${this.message}`; + } + + // Use carriage return to overwrite the same line + process.stdout.write(`\r${output}`); + } + + /** + * Mark progress as complete + */ + complete(message?: string): void { + this.current = this.total; + this.isCompleted = true; + this.render(); + + const elapsed = (Date.now() - this.startTime) / 1000; + const finalMessage = message || this.message || 'Complete'; + + process.stdout.write('\n'); + this.logger.success(`${finalMessage} (${elapsed.toFixed(1)}s)`); + } + + /** + * Format time in seconds to human-readable format + */ + private formatTime(seconds: number): string { + if (seconds < 60) { + return `${Math.round(seconds)}s`; + } + const minutes = Math.floor(seconds / 60); + const secs = Math.round(seconds % 60); + return `${minutes}m ${secs}s`; + } + + /** + * Stop the progress tracker without completing + */ + stop(): void { + process.stdout.write('\n'); + } +} + +/** + * Simple spinner for indeterminate progress + */ +export class Spinner { + private frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; + private currentFrame = 0; + private interval?: NodeJS.Timeout; + private message: string; + private logger: Logger; + + constructor(message: string, logger?: Logger) { + this.message = message; + this.logger = logger || new Logger({ prefix: 'Spinner' }); + } + + start(): void { + this.interval = setInterval(() => { + const frame = this.frames[this.currentFrame]; + process.stdout.write(`\r${frame} ${this.message}`); + this.currentFrame = (this.currentFrame + 1) % this.frames.length; + }, 80); + } + + update(message: string): void { + this.message = message; + } + + stop(success: boolean = true, message?: string): void { + if (this.interval) { + clearInterval(this.interval); + process.stdout.write('\r' + ' '.repeat(this.message.length + 3) + '\r'); + + if (message) { + if (success) { + this.logger.success(message); + } else { + this.logger.error(message); + } + } + } + } +} + +/** + * Progress bar for file operations + */ +export class FileProgress { + private tracker: ProgressTracker; + private logger: Logger; + private files: string[] = []; + private errors: string[] = []; + + constructor(totalFiles: number, logger?: Logger) { + this.logger = logger || new Logger({ prefix: 'FileProgress' }); + this.tracker = new ProgressTracker({ + total: totalFiles, + message: 'Processing files', + showBar: true, + showPercentage: true, + showETA: true + }, this.logger); + } + + /** + * Process a file and update progress + */ + processFile(fileName: string, success: boolean = true): void { + if (success) { + this.files.push(fileName); + } else { + this.errors.push(fileName); + } + + const shortName = fileName.length > 40 + ? '...' + fileName.slice(-37) + : fileName; + + this.tracker.increment(shortName); + } + + /** + * Complete file processing + */ + complete(): void { + const message = this.errors.length > 0 + ? `Processed ${this.files.length} files (${this.errors.length} errors)` + : `Processed ${this.files.length} files`; + + this.tracker.complete(message); + + if (this.errors.length > 0) { + this.logger.warn(`Files with errors: ${this.errors.join(', ')}`); + } + } + + /** + * Get processing statistics + */ + getStats(): { processed: number; errors: number; errorRate: number } { + const processed = this.files.length + this.errors.length; + const errorRate = processed > 0 ? this.errors.length / processed : 0; + + return { + processed, + errors: this.errors.length, + errorRate + }; + } +} + +/** + * Helper function for simple progress tracking + */ +export function withProgress( + items: T[], + processor: (item: T, index: number) => Promise | void, + message?: string +): Promise { + return new Promise(async (resolve, reject) => { + const tracker = new ProgressTracker({ + total: items.length, + message: message || 'Processing items', + showBar: true, + showPercentage: true, + showETA: true + }); + + try { + for (let i = 0; i < items.length; i++) { + await processor(items[i], i); + tracker.update(i + 1); + } + tracker.complete(); + resolve(); + } catch (error) { + tracker.stop(); + reject(error); + } + }); } \ No newline at end of file diff --git a/src/utils/query-helpers.ts b/src/utils/query-helpers.ts index ce62659..d16dbe2 100644 --- a/src/utils/query-helpers.ts +++ b/src/utils/query-helpers.ts @@ -1,104 +1,104 @@ -/** - * Query Helper Utilities - * Fixes JSON serialization and adds fuzzy matching - */ - -/** - * Simple fuzzy matching with tokenization - */ -export function fuzzyMatch(query: string, target: string, threshold = 0.7): boolean { - const q = query.toLowerCase(); - const t = target.toLowerCase(); - - // Exact match - if (t.includes(q)) return true; - - // Token matching for camelCase/snake_case - const qTokens = q.split(/[_\-\s]+|(?=[A-Z])/).filter(Boolean); - const tTokens = t.split(/[_\-\s]+|(?=[A-Z])/).filter(Boolean); - - // Check if query tokens are in target - const matches = qTokens.filter(qt => - tTokens.some(tt => tt.includes(qt)) - ); - - return matches.length >= qTokens.length * threshold; -} - -/** - * Extract searchable string from various object types - */ -export function extractSearchableString(obj: any): string { - if (typeof obj === 'string') return obj; - if (typeof obj === 'number' || typeof obj === 'boolean') return String(obj); - if (obj && typeof obj === 'object') { - if (obj.name) return extractSearchableString(obj.name); - if (obj.value) return extractSearchableString(obj.value); - if (obj.id) return extractSearchableString(obj.id); - if (Array.isArray(obj)) return obj.map(extractSearchableString).join(' '); - } - return ''; -} - -/** - * Format result for display - */ -export function formatResult(result: any): string { - const line = result.line ? `:${result.line}` : ''; - let name = result.name; - - // Handle complex objects - if (typeof name === 'object' && name !== null) { - name = extractSearchableString(name); - } - - return ` ${result.type}: ${name} in ${result.file}${line}`; -} - -/** - * Common GraphQL hook patterns - */ -const GRAPHQL_HOOKS = [ - /^use[A-Z]\w+(Query|Mutation|Subscription|LazyQuery)$/, - /^use(Query|Mutation|LazyQuery|Subscription)$/ -]; - -/** - * Detect if a function name is a GraphQL hook - */ -export function isGraphQLHook(name: string): boolean { - return GRAPHQL_HOOKS.some(pattern => pattern.test(name)); -} - -/** - * Semantic aliases for common terms - */ -const ALIASES: Record = { - calendar: ['schedule', 'appointment', 'meeting', 'event'], - skill: ['function', 'capability', 'action'], - get: ['fetch', 'retrieve', 'load', 'query'], - create: ['add', 'new', 'insert', 'make'], - update: ['edit', 'modify', 'change', 'patch'], - delete: ['remove', 'destroy', 'drop', 'clear'] -}; - -/** - * Expand query with semantic aliases - */ -export function expandQuery(query: string): string[] { - const lower = query.toLowerCase(); - const expanded = [query]; - - if (ALIASES[lower]) { - expanded.push(...ALIASES[lower]); - } - - // Check if query is an alias - for (const [key, values] of Object.entries(ALIASES)) { - if (values.includes(lower)) { - expanded.push(key); - } - } - - return [...new Set(expanded)]; +/** + * Query Helper Utilities + * Fixes JSON serialization and adds fuzzy matching + */ + +/** + * Simple fuzzy matching with tokenization + */ +export function fuzzyMatch(query: string, target: string, threshold = 0.7): boolean { + const q = query.toLowerCase(); + const t = target.toLowerCase(); + + // Exact match + if (t.includes(q)) return true; + + // Token matching for camelCase/snake_case + const qTokens = q.split(/[_\-\s]+|(?=[A-Z])/).filter(Boolean); + const tTokens = t.split(/[_\-\s]+|(?=[A-Z])/).filter(Boolean); + + // Check if query tokens are in target + const matches = qTokens.filter(qt => + tTokens.some(tt => tt.includes(qt)) + ); + + return matches.length >= qTokens.length * threshold; +} + +/** + * Extract searchable string from various object types + */ +export function extractSearchableString(obj: any): string { + if (typeof obj === 'string') return obj; + if (typeof obj === 'number' || typeof obj === 'boolean') return String(obj); + if (obj && typeof obj === 'object') { + if (obj.name) return extractSearchableString(obj.name); + if (obj.value) return extractSearchableString(obj.value); + if (obj.id) return extractSearchableString(obj.id); + if (Array.isArray(obj)) return obj.map(extractSearchableString).join(' '); + } + return ''; +} + +/** + * Format result for display + */ +export function formatResult(result: any): string { + const line = result.line ? `:${result.line}` : ''; + let name = result.name; + + // Handle complex objects + if (typeof name === 'object' && name !== null) { + name = extractSearchableString(name); + } + + return ` ${result.type}: ${name} in ${result.file}${line}`; +} + +/** + * Common GraphQL hook patterns + */ +const GRAPHQL_HOOKS = [ + /^use[A-Z]\w+(Query|Mutation|Subscription|LazyQuery)$/, + /^use(Query|Mutation|LazyQuery|Subscription)$/ +]; + +/** + * Detect if a function name is a GraphQL hook + */ +export function isGraphQLHook(name: string): boolean { + return GRAPHQL_HOOKS.some(pattern => pattern.test(name)); +} + +/** + * Semantic aliases for common terms + */ +const ALIASES: Record = { + calendar: ['schedule', 'appointment', 'meeting', 'event'], + skill: ['function', 'capability', 'action'], + get: ['fetch', 'retrieve', 'load', 'query'], + create: ['add', 'new', 'insert', 'make'], + update: ['edit', 'modify', 'change', 'patch'], + delete: ['remove', 'destroy', 'drop', 'clear'] +}; + +/** + * Expand query with semantic aliases + */ +export function expandQuery(query: string): string[] { + const lower = query.toLowerCase(); + const expanded = [query]; + + if (ALIASES[lower]) { + expanded.push(...ALIASES[lower]); + } + + // Check if query is an alias + for (const [key, values] of Object.entries(ALIASES)) { + if (values.includes(lower)) { + expanded.push(key); + } + } + + return [...new Set(expanded)]; } \ No newline at end of file diff --git a/src/utils/query.ts b/src/utils/query.ts index 22ba219..d40cf3d 100644 --- a/src/utils/query.ts +++ b/src/utils/query.ts @@ -1,269 +1,269 @@ -import { ProjectIndex, FileIndex} from '../types'; - -export interface QueryOptions { - type?: 'function' | 'class' | 'constant' | 'import' | 'export'; - language?: string; - service?: string; - pattern?: string; - minComplexity?: number; - maxComplexity?: number; - limit?: number; -} - -export class QueryEngine { - private index: ProjectIndex; - - constructor(index: ProjectIndex) { - this.index = index; - } - - public query(options: QueryOptions = {}): any[] { - let results: any[] = []; - - // Filter files by language and service - const files = this.filterFiles(options); - - // Extract items based on type - for (const [filePath, fileIndex] of Object.entries(files)) { - const items = this.extractItems(filePath, fileIndex, options); - results.push(...items); - } - - // Apply pattern matching - if (options.pattern) { - const regex = new RegExp(options.pattern, 'i'); - results = results.filter(item => regex.test(item.name)); - } - - // Apply complexity filtering - if (options.minComplexity !== undefined || options.maxComplexity !== undefined) { - results = results.filter(item => { - const complexity = item.complexity || 0; - if (options.minComplexity !== undefined && complexity < options.minComplexity) { - return false; - } - if (options.maxComplexity !== undefined && complexity > options.maxComplexity) { - return false; - } - return true; - }); - } - - // Apply limit - if (options.limit && results.length > options.limit) { - results = results.slice(0, options.limit); - } - - return results; - } - - private filterFiles(options: QueryOptions): Record { - const filtered: Record = {}; - - for (const [filePath, fileIndex] of Object.entries(this.index.files)) { - // Filter by language - if (options.language && fileIndex.language !== options.language) { - continue; - } - - // Filter by service (for monorepos) - if (options.service && this.index.monorepo) { - const fileService = this.getServiceForFile(filePath); - if (fileService !== options.service) { - continue; - } - } - - filtered[filePath] = fileIndex; - } - - return filtered; - } - - private getServiceForFile(filePath: string): string | null { - if (!this.index.monorepo || !this.index.monorepo.services) { - return null; - } - - for (const [serviceName, serviceInfo] of Object.entries(this.index.monorepo.services)) { - if (filePath.startsWith(serviceInfo.path)) { - return serviceName; - } - } - - return null; - } - - private extractItems(filePath: string, fileIndex: FileIndex, options: QueryOptions): any[] { - const items: any[] = []; - - if (!options.type || options.type === 'function') { - for (const func of fileIndex.functions) { - items.push({ - ...func, - type: 'function', - file: filePath, - language: fileIndex.language, - complexity: fileIndex.complexity - }); - } - } - - if (!options.type || options.type === 'class') { - for (const cls of fileIndex.classes) { - items.push({ - ...cls, - type: 'class', - file: filePath, - language: fileIndex.language, - complexity: fileIndex.complexity - }); - } - } - - if (!options.type || options.type === 'constant') { - for (const constant of fileIndex.constants) { - items.push({ - name: constant, - type: 'constant', - file: filePath, - language: fileIndex.language - }); - } - } - - if (!options.type || options.type === 'import') { - for (const imp of fileIndex.imports) { - items.push({ - name: imp, - type: 'import', - file: filePath, - language: fileIndex.language - }); - } - } - - if (!options.type || options.type === 'export') { - for (const exp of fileIndex.exports) { - items.push({ - name: exp, - type: 'export', - file: filePath, - language: fileIndex.language - }); - } - } - - return items; - } - - public findDefinition(name: string): any[] { - const results: any[] = []; - - for (const [filePath, fileIndex] of Object.entries(this.index.files)) { - // Check functions - for (const func of fileIndex.functions) { - if (func.name === name) { - results.push({ - ...func, - type: 'function', - file: filePath, - language: fileIndex.language - }); - } - } - - // Check classes - for (const cls of fileIndex.classes) { - if (cls.name === name) { - results.push({ - ...cls, - type: 'class', - file: filePath, - language: fileIndex.language - }); - } - } - - // Check constants - const hasConstant = fileIndex.constants.some((c: any) => - typeof c === 'string' ? c === name : c.name === name - ); - if (hasConstant) { - results.push({ - name, - type: 'constant', - file: filePath, - language: fileIndex.language - }); - } - } - - return results; - } - - public findReferences(name: string): string[] { - const references: string[] = []; - - for (const [filePath, fileIndex] of Object.entries(this.index.files)) { - // Check imports - const hasImport = fileIndex.imports.some((imp: any) => - typeof imp === 'string' ? imp === name : imp.source === name || - (imp.specifiers && imp.specifiers.some((spec: any) => spec.name === name || spec.alias === name)) - ); - if (hasImport) { - references.push(filePath); - continue; - } - - // Check if any function or class uses this - for (const func of fileIndex.functions) { - // Simple heuristic: check if function might reference the name - // In a real implementation, we'd need to parse the function body - if (func.parameters.some(p => p.type === name)) { - references.push(filePath); - break; - } - } - } - - return [...new Set(references)]; // Remove duplicates - } - - public getDependencyChain(filePath: string): string[] { - const chain: string[] = []; - const visited = new Set(); - - const traverse = (file: string, depth: number = 0) => { - if (visited.has(file) || depth > 10) { - return; - } - - visited.add(file); - chain.push(file); - - const deps = this.index.dependencyGraph[file]; - if (deps) { - for (const dep of deps) { - traverse(dep, depth + 1); - } - } - }; - - traverse(filePath); - return chain; - } - - public getStatistics(): any { - return { - ...this.index.statistics, - queryableItems: { - functions: this.query({ type: 'function' }).length, - classes: this.query({ type: 'class' }).length, - constants: this.query({ type: 'constant' }).length, - imports: this.query({ type: 'import' }).length, - exports: this.query({ type: 'export' }).length - } - }; - } +import { ProjectIndex, FileIndex} from '../types'; + +export interface QueryOptions { + type?: 'function' | 'class' | 'constant' | 'import' | 'export'; + language?: string; + service?: string; + pattern?: string; + minComplexity?: number; + maxComplexity?: number; + limit?: number; +} + +export class QueryEngine { + private index: ProjectIndex; + + constructor(index: ProjectIndex) { + this.index = index; + } + + public query(options: QueryOptions = {}): any[] { + let results: any[] = []; + + // Filter files by language and service + const files = this.filterFiles(options); + + // Extract items based on type + for (const [filePath, fileIndex] of Object.entries(files)) { + const items = this.extractItems(filePath, fileIndex, options); + results.push(...items); + } + + // Apply pattern matching + if (options.pattern) { + const regex = new RegExp(options.pattern, 'i'); + results = results.filter(item => regex.test(item.name)); + } + + // Apply complexity filtering + if (options.minComplexity !== undefined || options.maxComplexity !== undefined) { + results = results.filter(item => { + const complexity = item.complexity || 0; + if (options.minComplexity !== undefined && complexity < options.minComplexity) { + return false; + } + if (options.maxComplexity !== undefined && complexity > options.maxComplexity) { + return false; + } + return true; + }); + } + + // Apply limit + if (options.limit && results.length > options.limit) { + results = results.slice(0, options.limit); + } + + return results; + } + + private filterFiles(options: QueryOptions): Record { + const filtered: Record = {}; + + for (const [filePath, fileIndex] of Object.entries(this.index.files)) { + // Filter by language + if (options.language && fileIndex.language !== options.language) { + continue; + } + + // Filter by service (for monorepos) + if (options.service && this.index.monorepo) { + const fileService = this.getServiceForFile(filePath); + if (fileService !== options.service) { + continue; + } + } + + filtered[filePath] = fileIndex; + } + + return filtered; + } + + private getServiceForFile(filePath: string): string | null { + if (!this.index.monorepo || !this.index.monorepo.services) { + return null; + } + + for (const [serviceName, serviceInfo] of Object.entries(this.index.monorepo.services)) { + if (filePath.startsWith(serviceInfo.path)) { + return serviceName; + } + } + + return null; + } + + private extractItems(filePath: string, fileIndex: FileIndex, options: QueryOptions): any[] { + const items: any[] = []; + + if (!options.type || options.type === 'function') { + for (const func of fileIndex.functions) { + items.push({ + ...func, + type: 'function', + file: filePath, + language: fileIndex.language, + complexity: fileIndex.complexity + }); + } + } + + if (!options.type || options.type === 'class') { + for (const cls of fileIndex.classes) { + items.push({ + ...cls, + type: 'class', + file: filePath, + language: fileIndex.language, + complexity: fileIndex.complexity + }); + } + } + + if (!options.type || options.type === 'constant') { + for (const constant of fileIndex.constants) { + items.push({ + name: constant, + type: 'constant', + file: filePath, + language: fileIndex.language + }); + } + } + + if (!options.type || options.type === 'import') { + for (const imp of fileIndex.imports) { + items.push({ + name: imp, + type: 'import', + file: filePath, + language: fileIndex.language + }); + } + } + + if (!options.type || options.type === 'export') { + for (const exp of fileIndex.exports) { + items.push({ + name: exp, + type: 'export', + file: filePath, + language: fileIndex.language + }); + } + } + + return items; + } + + public findDefinition(name: string): any[] { + const results: any[] = []; + + for (const [filePath, fileIndex] of Object.entries(this.index.files)) { + // Check functions + for (const func of fileIndex.functions) { + if (func.name === name) { + results.push({ + ...func, + type: 'function', + file: filePath, + language: fileIndex.language + }); + } + } + + // Check classes + for (const cls of fileIndex.classes) { + if (cls.name === name) { + results.push({ + ...cls, + type: 'class', + file: filePath, + language: fileIndex.language + }); + } + } + + // Check constants + const hasConstant = fileIndex.constants.some((c: any) => + typeof c === 'string' ? c === name : c.name === name + ); + if (hasConstant) { + results.push({ + name, + type: 'constant', + file: filePath, + language: fileIndex.language + }); + } + } + + return results; + } + + public findReferences(name: string): string[] { + const references: string[] = []; + + for (const [filePath, fileIndex] of Object.entries(this.index.files)) { + // Check imports + const hasImport = fileIndex.imports.some((imp: any) => + typeof imp === 'string' ? imp === name : imp.source === name || + (imp.specifiers && imp.specifiers.some((spec: any) => spec.name === name || spec.alias === name)) + ); + if (hasImport) { + references.push(filePath); + continue; + } + + // Check if any function or class uses this + for (const func of fileIndex.functions) { + // Simple heuristic: check if function might reference the name + // In a real implementation, we'd need to parse the function body + if (func.parameters.some(p => p.type === name)) { + references.push(filePath); + break; + } + } + } + + return [...new Set(references)]; // Remove duplicates + } + + public getDependencyChain(filePath: string): string[] { + const chain: string[] = []; + const visited = new Set(); + + const traverse = (file: string, depth: number = 0) => { + if (visited.has(file) || depth > 10) { + return; + } + + visited.add(file); + chain.push(file); + + const deps = this.index.dependencyGraph[file]; + if (deps) { + for (const dep of deps) { + traverse(dep, depth + 1); + } + } + }; + + traverse(filePath); + return chain; + } + + public getStatistics(): any { + return { + ...this.index.statistics, + queryableItems: { + functions: this.query({ type: 'function' }).length, + classes: this.query({ type: 'class' }).length, + constants: this.query({ type: 'constant' }).length, + imports: this.query({ type: 'import' }).length, + exports: this.query({ type: 'export' }).length + } + }; + } } \ No newline at end of file diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 6f113c0..944d749 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,303 +1,303 @@ -/** - * Input validation utilities for CLI commands - */ - -import * as fs from 'fs'; -import * as path from 'path'; -import { Logger } from './logger'; - -const logger = new Logger({ prefix: 'Validation' }); - -export interface ValidationResult { - valid: boolean; - error?: string; - value?: any; -} - -export class InputValidator { - /** - * Validate file path exists and is readable - */ - static validateFilePath(filePath: string): ValidationResult { - if (!filePath || filePath.trim() === '') { - return { valid: false, error: 'File path cannot be empty' }; - } - - const resolvedPath = path.resolve(filePath); - - try { - if (!fs.existsSync(resolvedPath)) { - return { valid: false, error: `File does not exist: ${resolvedPath}` }; - } - - const stats = fs.statSync(resolvedPath); - if (!stats.isFile()) { - return { valid: false, error: `Path is not a file: ${resolvedPath}` }; - } - - // Check if file is readable - fs.accessSync(resolvedPath, fs.constants.R_OK); - - return { valid: true, value: resolvedPath }; - } catch (error) { - return { valid: false, error: `Cannot read file: ${error}` }; - } - } - - /** - * Validate directory path exists and is accessible - */ - static validateDirectoryPath(dirPath: string, createIfMissing: boolean = false): ValidationResult { - if (!dirPath || dirPath.trim() === '') { - return { valid: false, error: 'Directory path cannot be empty' }; - } - - const resolvedPath = path.resolve(dirPath); - - try { - if (!fs.existsSync(resolvedPath)) { - if (createIfMissing) { - fs.mkdirSync(resolvedPath, { recursive: true }); - logger.debug(`Created directory: ${resolvedPath}`); - return { valid: true, value: resolvedPath }; - } - return { valid: false, error: `Directory does not exist: ${resolvedPath}` }; - } - - const stats = fs.statSync(resolvedPath); - if (!stats.isDirectory()) { - return { valid: false, error: `Path is not a directory: ${resolvedPath}` }; - } - - // Check if directory is accessible - fs.accessSync(resolvedPath, fs.constants.R_OK | fs.constants.W_OK); - - return { valid: true, value: resolvedPath }; - } catch (error) { - return { valid: false, error: `Cannot access directory: ${error}` }; - } - } - - /** - * Validate port number - */ - static validatePort(port: string | number): ValidationResult { - const portNum = typeof port === 'string' ? parseInt(port, 10) : port; - - if (isNaN(portNum)) { - return { valid: false, error: 'Port must be a number' }; - } - - if (portNum < 1 || portNum > 65535) { - return { valid: false, error: 'Port must be between 1 and 65535' }; - } - - // Check for commonly restricted ports - if (portNum < 1024 && process.platform !== 'win32' && process.getuid && process.getuid() !== 0) { - return { - valid: false, - error: `Port ${portNum} requires root/administrator privileges` - }; - } - - return { valid: true, value: portNum }; - } - - /** - * Validate export format - */ - static validateExportFormat(format: string): ValidationResult { - const validFormats = ['json', 'graphviz', 'dot', 'markdown', 'md', 'mermaid', 'mmd', 'ascii', 'txt']; - const normalizedFormat = format.toLowerCase(); - - if (!validFormats.includes(normalizedFormat)) { - return { - valid: false, - error: `Invalid format: ${format}. Valid formats: ${validFormats.join(', ')}` - }; - } - - // Normalize format aliases - const formatMap: Record = { - 'dot': 'graphviz', - 'md': 'markdown', - 'mmd': 'mermaid', - 'txt': 'ascii' - }; - - const finalFormat = formatMap[normalizedFormat] || normalizedFormat; - return { valid: true, value: finalFormat }; - } - - /** - * Validate query pattern - */ - static validateQuery(query: string): ValidationResult { - if (!query || query.trim() === '') { - return { valid: false, error: 'Query cannot be empty' }; - } - - // Check for potentially dangerous regex patterns - try { - // Test if it's a valid regex - new RegExp(query); - } catch (error) { - // Not a valid regex, treat as literal string - that's ok - } - - // Warn about overly broad queries - if (query === '*' || query === '.*' || query === '.+') { - logger.warn('Query is very broad and may return many results'); - } - - return { valid: true, value: query.trim() }; - } - - /** - * Validate scan mode - */ - static validateScanMode(mode: string): ValidationResult { - const validModes = ['quick', 'full', 'deep', 'smart']; - const normalizedMode = mode.toLowerCase(); - - if (!validModes.includes(normalizedMode)) { - return { - valid: false, - error: `Invalid mode: ${mode}. Valid modes: ${validModes.join(', ')}` - }; - } - - return { valid: true, value: normalizedMode }; - } - - /** - * Validate worker count - */ - static validateWorkerCount(count: string | number): ValidationResult { - const workerCount = typeof count === 'string' ? parseInt(count, 10) : count; - - if (isNaN(workerCount)) { - return { valid: false, error: 'Worker count must be a number' }; - } - - if (workerCount < 1) { - return { valid: false, error: 'Worker count must be at least 1' }; - } - - const maxWorkers = require('os').cpus().length; - if (workerCount > maxWorkers * 2) { - logger.warn(`Worker count ${workerCount} exceeds recommended maximum of ${maxWorkers * 2}`); - } - - return { valid: true, value: workerCount }; - } - - /** - * Validate file size limit - */ - static validateFileSize(size: string): ValidationResult { - const units: Record = { - 'b': 1, - 'kb': 1024, - 'mb': 1024 * 1024, - 'gb': 1024 * 1024 * 1024 - }; - - const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/); - - if (!match) { - return { valid: false, error: 'Invalid size format. Use: 100, 100kb, 10mb, 1gb' }; - } - - const value = parseFloat(match[1]); - const unit = match[2] || 'b'; - const bytes = value * units[unit]; - - if (bytes > 100 * 1024 * 1024) { // 100MB warning - logger.warn(`Large file size limit: ${size}. This may impact performance.`); - } - - return { valid: true, value: bytes }; - } - - /** - * Sanitize file path to prevent directory traversal - */ - static sanitizePath(inputPath: string, basePath?: string): ValidationResult { - // Remove any null bytes - let sanitized = inputPath.replace(/\0/g, ''); - - // Normalize the path - sanitized = path.normalize(sanitized); - - // Check for directory traversal attempts - if (sanitized.includes('..')) { - return { valid: false, error: 'Path traversal detected' }; - } - - // If base path provided, ensure the path stays within it - if (basePath) { - const resolvedBase = path.resolve(basePath); - const resolvedPath = path.resolve(basePath, sanitized); - - if (!resolvedPath.startsWith(resolvedBase)) { - return { valid: false, error: 'Path escapes base directory' }; - } - } - - return { valid: true, value: sanitized }; - } - - /** - * Validate environment variables - */ - static validateEnvironmentVars(required: string[]): ValidationResult { - const missing: string[] = []; - - for (const varName of required) { - if (!process.env[varName]) { - missing.push(varName); - } - } - - if (missing.length > 0) { - return { - valid: false, - error: `Missing required environment variables: ${missing.join(', ')}` - }; - } - - return { valid: true }; - } -} - -/** - * Decorator for validating CLI command inputs - */ -export function validate(validations: Record ValidationResult>) { - return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { - const originalMethod = descriptor.value; - - descriptor.value = async function (...args: any[]) { - const options = args[0] || {}; - - for (const [key, validator] of Object.entries(validations)) { - if (key in options) { - const result = validator(options[key]); - if (!result.valid) { - logger.error(`Validation failed for ${key}: ${result.error}`); - process.exit(1); - } - // Replace with validated/normalized value - if (result.value !== undefined) { - options[key] = result.value; - } - } - } - - return originalMethod.apply(this, args); - }; - - return descriptor; - }; +/** + * Input validation utilities for CLI commands + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import { Logger } from './logger'; + +const logger = new Logger({ prefix: 'Validation' }); + +export interface ValidationResult { + valid: boolean; + error?: string; + value?: any; +} + +export class InputValidator { + /** + * Validate file path exists and is readable + */ + static validateFilePath(filePath: string): ValidationResult { + if (!filePath || filePath.trim() === '') { + return { valid: false, error: 'File path cannot be empty' }; + } + + const resolvedPath = path.resolve(filePath); + + try { + if (!fs.existsSync(resolvedPath)) { + return { valid: false, error: `File does not exist: ${resolvedPath}` }; + } + + const stats = fs.statSync(resolvedPath); + if (!stats.isFile()) { + return { valid: false, error: `Path is not a file: ${resolvedPath}` }; + } + + // Check if file is readable + fs.accessSync(resolvedPath, fs.constants.R_OK); + + return { valid: true, value: resolvedPath }; + } catch (error) { + return { valid: false, error: `Cannot read file: ${error}` }; + } + } + + /** + * Validate directory path exists and is accessible + */ + static validateDirectoryPath(dirPath: string, createIfMissing: boolean = false): ValidationResult { + if (!dirPath || dirPath.trim() === '') { + return { valid: false, error: 'Directory path cannot be empty' }; + } + + const resolvedPath = path.resolve(dirPath); + + try { + if (!fs.existsSync(resolvedPath)) { + if (createIfMissing) { + fs.mkdirSync(resolvedPath, { recursive: true }); + logger.debug(`Created directory: ${resolvedPath}`); + return { valid: true, value: resolvedPath }; + } + return { valid: false, error: `Directory does not exist: ${resolvedPath}` }; + } + + const stats = fs.statSync(resolvedPath); + if (!stats.isDirectory()) { + return { valid: false, error: `Path is not a directory: ${resolvedPath}` }; + } + + // Check if directory is accessible + fs.accessSync(resolvedPath, fs.constants.R_OK | fs.constants.W_OK); + + return { valid: true, value: resolvedPath }; + } catch (error) { + return { valid: false, error: `Cannot access directory: ${error}` }; + } + } + + /** + * Validate port number + */ + static validatePort(port: string | number): ValidationResult { + const portNum = typeof port === 'string' ? parseInt(port, 10) : port; + + if (isNaN(portNum)) { + return { valid: false, error: 'Port must be a number' }; + } + + if (portNum < 1 || portNum > 65535) { + return { valid: false, error: 'Port must be between 1 and 65535' }; + } + + // Check for commonly restricted ports + if (portNum < 1024 && process.platform !== 'win32' && process.getuid && process.getuid() !== 0) { + return { + valid: false, + error: `Port ${portNum} requires root/administrator privileges` + }; + } + + return { valid: true, value: portNum }; + } + + /** + * Validate export format + */ + static validateExportFormat(format: string): ValidationResult { + const validFormats = ['json', 'graphviz', 'dot', 'markdown', 'md', 'mermaid', 'mmd', 'ascii', 'txt']; + const normalizedFormat = format.toLowerCase(); + + if (!validFormats.includes(normalizedFormat)) { + return { + valid: false, + error: `Invalid format: ${format}. Valid formats: ${validFormats.join(', ')}` + }; + } + + // Normalize format aliases + const formatMap: Record = { + 'dot': 'graphviz', + 'md': 'markdown', + 'mmd': 'mermaid', + 'txt': 'ascii' + }; + + const finalFormat = formatMap[normalizedFormat] || normalizedFormat; + return { valid: true, value: finalFormat }; + } + + /** + * Validate query pattern + */ + static validateQuery(query: string): ValidationResult { + if (!query || query.trim() === '') { + return { valid: false, error: 'Query cannot be empty' }; + } + + // Check for potentially dangerous regex patterns + try { + // Test if it's a valid regex + new RegExp(query); + } catch (error) { + // Not a valid regex, treat as literal string - that's ok + } + + // Warn about overly broad queries + if (query === '*' || query === '.*' || query === '.+') { + logger.warn('Query is very broad and may return many results'); + } + + return { valid: true, value: query.trim() }; + } + + /** + * Validate scan mode + */ + static validateScanMode(mode: string): ValidationResult { + const validModes = ['quick', 'full', 'deep', 'smart']; + const normalizedMode = mode.toLowerCase(); + + if (!validModes.includes(normalizedMode)) { + return { + valid: false, + error: `Invalid mode: ${mode}. Valid modes: ${validModes.join(', ')}` + }; + } + + return { valid: true, value: normalizedMode }; + } + + /** + * Validate worker count + */ + static validateWorkerCount(count: string | number): ValidationResult { + const workerCount = typeof count === 'string' ? parseInt(count, 10) : count; + + if (isNaN(workerCount)) { + return { valid: false, error: 'Worker count must be a number' }; + } + + if (workerCount < 1) { + return { valid: false, error: 'Worker count must be at least 1' }; + } + + const maxWorkers = require('os').cpus().length; + if (workerCount > maxWorkers * 2) { + logger.warn(`Worker count ${workerCount} exceeds recommended maximum of ${maxWorkers * 2}`); + } + + return { valid: true, value: workerCount }; + } + + /** + * Validate file size limit + */ + static validateFileSize(size: string): ValidationResult { + const units: Record = { + 'b': 1, + 'kb': 1024, + 'mb': 1024 * 1024, + 'gb': 1024 * 1024 * 1024 + }; + + const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/); + + if (!match) { + return { valid: false, error: 'Invalid size format. Use: 100, 100kb, 10mb, 1gb' }; + } + + const value = parseFloat(match[1]); + const unit = match[2] || 'b'; + const bytes = value * units[unit]; + + if (bytes > 100 * 1024 * 1024) { // 100MB warning + logger.warn(`Large file size limit: ${size}. This may impact performance.`); + } + + return { valid: true, value: bytes }; + } + + /** + * Sanitize file path to prevent directory traversal + */ + static sanitizePath(inputPath: string, basePath?: string): ValidationResult { + // Remove any null bytes + let sanitized = inputPath.replace(/\0/g, ''); + + // Normalize the path + sanitized = path.normalize(sanitized); + + // Check for directory traversal attempts + if (sanitized.includes('..')) { + return { valid: false, error: 'Path traversal detected' }; + } + + // If base path provided, ensure the path stays within it + if (basePath) { + const resolvedBase = path.resolve(basePath); + const resolvedPath = path.resolve(basePath, sanitized); + + if (!resolvedPath.startsWith(resolvedBase)) { + return { valid: false, error: 'Path escapes base directory' }; + } + } + + return { valid: true, value: sanitized }; + } + + /** + * Validate environment variables + */ + static validateEnvironmentVars(required: string[]): ValidationResult { + const missing: string[] = []; + + for (const varName of required) { + if (!process.env[varName]) { + missing.push(varName); + } + } + + if (missing.length > 0) { + return { + valid: false, + error: `Missing required environment variables: ${missing.join(', ')}` + }; + } + + return { valid: true }; + } +} + +/** + * Decorator for validating CLI command inputs + */ +export function validate(validations: Record ValidationResult>) { + return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { + const originalMethod = descriptor.value; + + descriptor.value = async function (...args: any[]) { + const options = args[0] || {}; + + for (const [key, validator] of Object.entries(validations)) { + if (key in options) { + const result = validator(options[key]); + if (!result.valid) { + logger.error(`Validation failed for ${key}: ${result.error}`); + process.exit(1); + } + // Replace with validated/normalized value + if (result.value !== undefined) { + options[key] = result.value; + } + } + } + + return originalMethod.apply(this, args); + }; + + return descriptor; + }; } \ No newline at end of file diff --git a/test/README.md b/test/README.md index 2700fe1..38a1661 100644 --- a/test/README.md +++ b/test/README.md @@ -1,77 +1,77 @@ -# Test Directory Structure - -## Organization - -``` -test/ -├── core/ # Core module tests -│ └── cache-manager.test.ts -├── fixtures/ # Test data files -│ └── complex_python.py -├── manual/ # Manual test scripts -│ ├── test-parser-simple.js -│ └── test-python-parser.js -├── parsers/ # Parser tests -│ ├── javascript.test.ts -│ ├── python-ast.test.ts -│ └── python-tree-sitter.test.ts -└── setup.ts # Jest setup configuration -``` - -## Running Tests - -### Automated Tests (Jest) -```bash -# Run all tests -npm test - -# Run specific test file -npm test python-tree-sitter - -# Run with coverage -npm run test:coverage - -# Watch mode -npm test -- --watch -``` - -### Manual Tests -```bash -# Test tree-sitter Python parser -node test/manual/test-python-parser.js - -# Simple tree-sitter validation -node test/manual/test-parser-simple.js -``` - -## Test Files - -### Core Tests -- `cache-manager.test.ts` - Tests for LRU cache functionality - -### Parser Tests -- `javascript.test.ts` - JavaScript parser tests -- `python-ast.test.ts` - Python AST parser tests (legacy) -- `python-tree-sitter.test.ts` - Tree-sitter Python parser tests - -### Fixtures -- `complex_python.py` - Comprehensive Python test file with all language features - -### Manual Tests -- `test-parser-simple.js` - Basic tree-sitter functionality check -- `test-python-parser.js` - Full Python parser validation - -## Adding New Tests - -1. **Unit Tests**: Add to appropriate subdirectory (core/, parsers/, etc.) -2. **Fixtures**: Place test data files in fixtures/ -3. **Manual Tests**: Add scripts to manual/ for debugging and validation -4. **Integration Tests**: Create new directory if needed - -## Coverage - -Current test coverage is low (~8%). Priority areas for new tests: -- API layer (0% coverage) -- AI agents (0% coverage) -- Exporters (0% coverage) +# Test Directory Structure + +## Organization + +``` +test/ +├── core/ # Core module tests +│ └── cache-manager.test.ts +├── fixtures/ # Test data files +│ └── complex_python.py +├── manual/ # Manual test scripts +│ ├── test-parser-simple.js +│ └── test-python-parser.js +├── parsers/ # Parser tests +│ ├── javascript.test.ts +│ ├── python-ast.test.ts +│ └── python-tree-sitter.test.ts +└── setup.ts # Jest setup configuration +``` + +## Running Tests + +### Automated Tests (Jest) +```bash +# Run all tests +npm test + +# Run specific test file +npm test python-tree-sitter + +# Run with coverage +npm run test:coverage + +# Watch mode +npm test -- --watch +``` + +### Manual Tests +```bash +# Test tree-sitter Python parser +node test/manual/test-python-parser.js + +# Simple tree-sitter validation +node test/manual/test-parser-simple.js +``` + +## Test Files + +### Core Tests +- `cache-manager.test.ts` - Tests for LRU cache functionality + +### Parser Tests +- `javascript.test.ts` - JavaScript parser tests +- `python-ast.test.ts` - Python AST parser tests (legacy) +- `python-tree-sitter.test.ts` - Tree-sitter Python parser tests + +### Fixtures +- `complex_python.py` - Comprehensive Python test file with all language features + +### Manual Tests +- `test-parser-simple.js` - Basic tree-sitter functionality check +- `test-python-parser.js` - Full Python parser validation + +## Adding New Tests + +1. **Unit Tests**: Add to appropriate subdirectory (core/, parsers/, etc.) +2. **Fixtures**: Place test data files in fixtures/ +3. **Manual Tests**: Add scripts to manual/ for debugging and validation +4. **Integration Tests**: Create new directory if needed + +## Coverage + +Current test coverage is low (~8%). Priority areas for new tests: +- API layer (0% coverage) +- AI agents (0% coverage) +- Exporters (0% coverage) - Integration tests \ No newline at end of file diff --git a/test/core/cache-manager.test.ts b/test/core/cache-manager.test.ts index 29f8a93..f427d4a 100644 --- a/test/core/cache-manager.test.ts +++ b/test/core/cache-manager.test.ts @@ -1,182 +1,182 @@ -import { CacheManager } from '../../src/core/cache-manager'; -import { FileIndex } from '../../src/types'; - -describe('CacheManager', () => { - let cache: CacheManager; - - beforeEach(() => { - cache = new CacheManager({ - maxSize: 1024 * 1024, // 1MB for testing - maxAge: 60 * 1000 // 1 minute for testing - }); - }); - - afterEach(() => { - cache.clear(); - }); - - const createMockFileIndex = (size: number = 100): FileIndex => { - const data = 'x'.repeat(size); - return { - imports: [{ source: data, specifiers: [], type: 'static' }], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - lastModified: new Date().toISOString(), - language: 'JavaScript', - size: size, - complexity: 1 - }; - }; - - describe('basic operations', () => { - it('should set and get items', () => { - const fileIndex = createMockFileIndex(); - cache.set('test.js', fileIndex); - - const retrieved = cache.get('test.js'); - expect(retrieved).toEqual(fileIndex); - }); - - it('should return undefined for non-existent keys', () => { - expect(cache.get('nonexistent.js')).toBeUndefined(); - }); - - it('should check if key exists', () => { - const fileIndex = createMockFileIndex(); - cache.set('test.js', fileIndex); - - expect(cache.has('test.js')).toBe(true); - expect(cache.has('nonexistent.js')).toBe(false); - }); - - it('should delete items', () => { - const fileIndex = createMockFileIndex(); - cache.set('test.js', fileIndex); - - cache.delete('test.js'); - expect(cache.has('test.js')).toBe(false); - }); - - it('should clear all items', () => { - cache.set('test1.js', createMockFileIndex()); - cache.set('test2.js', createMockFileIndex()); - cache.set('test3.js', createMockFileIndex()); - - cache.clear(); - - expect(cache.has('test1.js')).toBe(false); - expect(cache.has('test2.js')).toBe(false); - expect(cache.has('test3.js')).toBe(false); - }); - }); - - describe('memory management', () => { - it('should evict old items when max size is exceeded', () => { - // Create items that are ~500KB each - const largeFileIndex1 = createMockFileIndex(250000); - const largeFileIndex2 = createMockFileIndex(250000); - const largeFileIndex3 = createMockFileIndex(250000); - const largeFileIndex4 = createMockFileIndex(250000); - - cache.set('file1.js', largeFileIndex1); - cache.set('file2.js', largeFileIndex2); - cache.set('file3.js', largeFileIndex3); - cache.set('file4.js', largeFileIndex4); - - // First item should be evicted - expect(cache.has('file1.js')).toBe(false); - // Last items should still exist - expect(cache.has('file3.js')).toBe(true); - expect(cache.has('file4.js')).toBe(true); - }); - - it('should track memory usage correctly', () => { - const stats1 = cache.getStats(); - expect(stats1.totalSize).toBe(0); - expect(stats1.itemCount).toBe(0); - - cache.set('test.js', createMockFileIndex(1000)); - - const stats2 = cache.getStats(); - expect(stats2.itemCount).toBe(1); - expect(stats2.totalSize).toBeGreaterThan(0); - expect(stats2.utilizationPercent).toBeGreaterThan(0); - expect(stats2.utilizationPercent).toBeLessThan(100); - }); - - it('should update size tracking when updating existing entries', () => { - const smallIndex = createMockFileIndex(100); - const largeIndex = createMockFileIndex(1000); - - cache.set('test.js', smallIndex); - const stats1 = cache.getStats(); - const initialSize = stats1.totalSize; - - cache.set('test.js', largeIndex); - const stats2 = cache.getStats(); - - expect(stats2.totalSize).toBeGreaterThan(initialSize); - expect(stats2.itemCount).toBe(1); // Still only one item - }); - }); - - describe('pruning', () => { - it('should prune old entries', async () => { - const oldFileIndex = createMockFileIndex(); - oldFileIndex.lastModified = new Date(Date.now() - 2 * 60 * 1000).toISOString(); // 2 minutes ago - - const newFileIndex = createMockFileIndex(); - newFileIndex.lastModified = new Date().toISOString(); - - cache.set('old.js', oldFileIndex); - cache.set('new.js', newFileIndex); - - const prunedCount = cache.prune(60 * 1000); // Prune items older than 1 minute - - expect(prunedCount).toBe(1); - expect(cache.has('old.js')).toBe(false); - expect(cache.has('new.js')).toBe(true); - }); - }); - - describe('edge cases', () => { - it('should handle empty file index', () => { - const emptyIndex: FileIndex = { - imports: [], - exports: [], - functions: [], - classes: [], - constants: [], - dependencies: [], - lastModified: new Date().toISOString(), - language: 'JavaScript', - size: 0, - complexity: 0 - }; - - cache.set('empty.js', emptyIndex); - expect(cache.get('empty.js')).toEqual(emptyIndex); - }); - - it('should handle very large single item within size limit', () => { - const maxItemSize = 900000; // 900KB, under 1MB limit - const largeIndex = createMockFileIndex(maxItemSize / 2); - - cache.set('large.js', largeIndex); - expect(cache.has('large.js')).toBe(true); - }); - - it('should handle rapid updates to same key', () => { - for (let i = 0; i < 100; i++) { - cache.set('rapid.js', createMockFileIndex(i * 10)); - } - - expect(cache.has('rapid.js')).toBe(true); - const stats = cache.getStats(); - expect(stats.itemCount).toBe(1); - }); - }); +import { CacheManager } from '../../src/core/cache-manager'; +import { FileIndex } from '../../src/types'; + +describe('CacheManager', () => { + let cache: CacheManager; + + beforeEach(() => { + cache = new CacheManager({ + maxSize: 1024 * 1024, // 1MB for testing + maxAge: 60 * 1000 // 1 minute for testing + }); + }); + + afterEach(() => { + cache.clear(); + }); + + const createMockFileIndex = (size: number = 100): FileIndex => { + const data = 'x'.repeat(size); + return { + imports: [{ source: data, specifiers: [], type: 'static' }], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + lastModified: new Date().toISOString(), + language: 'JavaScript', + size: size, + complexity: 1 + }; + }; + + describe('basic operations', () => { + it('should set and get items', () => { + const fileIndex = createMockFileIndex(); + cache.set('test.js', fileIndex); + + const retrieved = cache.get('test.js'); + expect(retrieved).toEqual(fileIndex); + }); + + it('should return undefined for non-existent keys', () => { + expect(cache.get('nonexistent.js')).toBeUndefined(); + }); + + it('should check if key exists', () => { + const fileIndex = createMockFileIndex(); + cache.set('test.js', fileIndex); + + expect(cache.has('test.js')).toBe(true); + expect(cache.has('nonexistent.js')).toBe(false); + }); + + it('should delete items', () => { + const fileIndex = createMockFileIndex(); + cache.set('test.js', fileIndex); + + cache.delete('test.js'); + expect(cache.has('test.js')).toBe(false); + }); + + it('should clear all items', () => { + cache.set('test1.js', createMockFileIndex()); + cache.set('test2.js', createMockFileIndex()); + cache.set('test3.js', createMockFileIndex()); + + cache.clear(); + + expect(cache.has('test1.js')).toBe(false); + expect(cache.has('test2.js')).toBe(false); + expect(cache.has('test3.js')).toBe(false); + }); + }); + + describe('memory management', () => { + it('should evict old items when max size is exceeded', () => { + // Create items that are ~500KB each + const largeFileIndex1 = createMockFileIndex(250000); + const largeFileIndex2 = createMockFileIndex(250000); + const largeFileIndex3 = createMockFileIndex(250000); + const largeFileIndex4 = createMockFileIndex(250000); + + cache.set('file1.js', largeFileIndex1); + cache.set('file2.js', largeFileIndex2); + cache.set('file3.js', largeFileIndex3); + cache.set('file4.js', largeFileIndex4); + + // First item should be evicted + expect(cache.has('file1.js')).toBe(false); + // Last items should still exist + expect(cache.has('file3.js')).toBe(true); + expect(cache.has('file4.js')).toBe(true); + }); + + it('should track memory usage correctly', () => { + const stats1 = cache.getStats(); + expect(stats1.totalSize).toBe(0); + expect(stats1.itemCount).toBe(0); + + cache.set('test.js', createMockFileIndex(1000)); + + const stats2 = cache.getStats(); + expect(stats2.itemCount).toBe(1); + expect(stats2.totalSize).toBeGreaterThan(0); + expect(stats2.utilizationPercent).toBeGreaterThan(0); + expect(stats2.utilizationPercent).toBeLessThan(100); + }); + + it('should update size tracking when updating existing entries', () => { + const smallIndex = createMockFileIndex(100); + const largeIndex = createMockFileIndex(1000); + + cache.set('test.js', smallIndex); + const stats1 = cache.getStats(); + const initialSize = stats1.totalSize; + + cache.set('test.js', largeIndex); + const stats2 = cache.getStats(); + + expect(stats2.totalSize).toBeGreaterThan(initialSize); + expect(stats2.itemCount).toBe(1); // Still only one item + }); + }); + + describe('pruning', () => { + it('should prune old entries', async () => { + const oldFileIndex = createMockFileIndex(); + oldFileIndex.lastModified = new Date(Date.now() - 2 * 60 * 1000).toISOString(); // 2 minutes ago + + const newFileIndex = createMockFileIndex(); + newFileIndex.lastModified = new Date().toISOString(); + + cache.set('old.js', oldFileIndex); + cache.set('new.js', newFileIndex); + + const prunedCount = cache.prune(60 * 1000); // Prune items older than 1 minute + + expect(prunedCount).toBe(1); + expect(cache.has('old.js')).toBe(false); + expect(cache.has('new.js')).toBe(true); + }); + }); + + describe('edge cases', () => { + it('should handle empty file index', () => { + const emptyIndex: FileIndex = { + imports: [], + exports: [], + functions: [], + classes: [], + constants: [], + dependencies: [], + lastModified: new Date().toISOString(), + language: 'JavaScript', + size: 0, + complexity: 0 + }; + + cache.set('empty.js', emptyIndex); + expect(cache.get('empty.js')).toEqual(emptyIndex); + }); + + it('should handle very large single item within size limit', () => { + const maxItemSize = 900000; // 900KB, under 1MB limit + const largeIndex = createMockFileIndex(maxItemSize / 2); + + cache.set('large.js', largeIndex); + expect(cache.has('large.js')).toBe(true); + }); + + it('should handle rapid updates to same key', () => { + for (let i = 0; i < 100; i++) { + cache.set('rapid.js', createMockFileIndex(i * 10)); + } + + expect(cache.has('rapid.js')).toBe(true); + const stats = cache.getStats(); + expect(stats.itemCount).toBe(1); + }); + }); }); \ No newline at end of file diff --git a/test/core/multi-repo-knowledge-graph.test.ts b/test/core/multi-repo-knowledge-graph.test.ts index 82c9c00..a6991c3 100644 --- a/test/core/multi-repo-knowledge-graph.test.ts +++ b/test/core/multi-repo-knowledge-graph.test.ts @@ -1,405 +1,405 @@ -import { MultiRepoKnowledgeGraph } from '../../src/core/multi-repo-knowledge-graph'; -import * as fs from 'fs'; -import * as path from 'path'; - -jest.mock('fs'); - -describe('MultiRepoKnowledgeGraph', () => { - let knowledgeGraph: MultiRepoKnowledgeGraph; - - beforeEach(() => { - jest.clearAllMocks(); - knowledgeGraph = new MultiRepoKnowledgeGraph(); - (fs.writeFileSync as jest.Mock).mockReturnValue(undefined); - }); - - describe('analyze', () => { - it('should analyze single repository structure', () => { - const mockIndex = { - files: { - 'src/index.js': { - imports: ['./utils', './config'], - exports: ['main'], - functions: [{ name: 'main' }] - }, - 'src/utils.js': { - exports: ['helper'], - functions: [{ name: 'helper' }] - } - }, - statistics: { totalFiles: 2 } - }; - - const result = knowledgeGraph.analyze(mockIndex); - - expect(result.nodes).toHaveLength(2); - expect(result.edges).toHaveLength(2); // Two imports from index.js - expect(result.nodes[0]).toMatchObject({ - id: expect.any(String), - label: 'src/index.js', - type: 'file' - }); - }); - - it('should detect monorepo with multiple services', () => { - const mockIndex = { - files: { - 'frontend/src/App.js': { - imports: ['axios', '@shared/utils'], - exports: ['App'] - }, - 'backend/src/server.js': { - imports: ['express', '@shared/config'], - exports: ['server'] - }, - 'shared/utils.js': { - exports: ['formatDate', 'parseData'] - } - }, - monorepo: { - services: { - frontend: { files: 1, dependencies: ['shared'] }, - backend: { files: 1, dependencies: ['shared'] }, - shared: { files: 1, dependencies: [] } - } - } - }; - - const result = knowledgeGraph.analyze(mockIndex); - - expect(result.services).toBeDefined(); - expect(result.services).toHaveProperty('frontend'); - expect(result.services).toHaveProperty('backend'); - expect(result.services).toHaveProperty('shared'); - expect(result.crossServiceDependencies).toBeDefined(); - }); - - it('should identify circular dependencies', () => { - const mockIndex = { - files: { - 'a.js': { imports: ['./b'] }, - 'b.js': { imports: ['./c'] }, - 'c.js': { imports: ['./a'] } - } - }; - - const result = knowledgeGraph.analyze(mockIndex); - - expect(result.circularDependencies).toBeDefined(); - expect(result.circularDependencies.length).toBeGreaterThan(0); - }); - - it('should calculate complexity metrics', () => { - const mockIndex = { - files: { - 'complex.js': { - functions: Array(10).fill({ name: 'func' }), - classes: Array(5).fill({ name: 'Class' }), - complexity: 25 - }, - 'simple.js': { - functions: [{ name: 'simple' }], - complexity: 2 - } - }, - statistics: { - totalFiles: 2, - totalFunctions: 11, - totalClasses: 5 - } - }; - - const result = knowledgeGraph.analyze(mockIndex); - - expect(result.metrics).toBeDefined(); - expect(result.metrics.averageComplexity).toBeDefined(); - expect(result.metrics.highComplexityFiles).toBeDefined(); - expect(result.metrics.highComplexityFiles).toContain('complex.js'); - }); - }); - - describe('generateMermaidDiagram', () => { - it('should generate valid Mermaid syntax', () => { - const mockAnalysis = { - nodes: [ - { id: '1', label: 'index.js', type: 'file' }, - { id: '2', label: 'utils.js', type: 'file' } - ], - edges: [ - { from: '1', to: '2', type: 'import' } - ] - }; - - const diagram = knowledgeGraph.generateMermaidDiagram(mockAnalysis); - - expect(diagram).toContain('graph TD'); - expect(diagram).toContain('1[index.js]'); - expect(diagram).toContain('2[utils.js]'); - expect(diagram).toContain('1 --> 2'); - }); - - it('should handle service-level dependencies', () => { - const mockAnalysis = { - nodes: [], - edges: [], - services: { - frontend: { dependencies: ['backend', 'shared'] }, - backend: { dependencies: ['shared'] }, - shared: { dependencies: [] } - } - }; - - const diagram = knowledgeGraph.generateMermaidDiagram(mockAnalysis); - - expect(diagram).toContain('subgraph'); - expect(diagram).toContain('frontend'); - expect(diagram).toContain('backend'); - expect(diagram).toContain('shared'); - }); - - it('should highlight circular dependencies', () => { - const mockAnalysis = { - nodes: [ - { id: '1', label: 'a.js', type: 'file' }, - { id: '2', label: 'b.js', type: 'file' } - ], - edges: [ - { from: '1', to: '2', type: 'import' }, - { from: '2', to: '1', type: 'import' } - ], - circularDependencies: [['a.js', 'b.js']] - }; - - const diagram = knowledgeGraph.generateMermaidDiagram(mockAnalysis); - - expect(diagram).toContain('style'); - expect(diagram).toContain('stroke:#ff0000'); - }); - }); - - describe('generateInteractiveDashboard', () => { - it('should generate HTML dashboard', () => { - const mockAnalysis = { - nodes: [{ id: '1', label: 'test.js' }], - edges: [], - metrics: { - totalNodes: 1, - totalEdges: 0, - averageComplexity: 5 - } - }; - - const html = knowledgeGraph.generateInteractiveDashboard(mockAnalysis); - - expect(html).toContain(''); - expect(html).toContain(' { - const mockAnalysis = { - nodes: [], - edges: [], - metrics: { - totalFiles: 100, - totalConnections: 250, - averageComplexity: 8.5, - circularDependencyCount: 3 - } - }; - - const html = knowledgeGraph.generateInteractiveDashboard(mockAnalysis); - - expect(html).toContain('100'); // totalFiles - expect(html).toContain('250'); // totalConnections - expect(html).toContain('8.5'); // averageComplexity - expect(html).toContain('3'); // circularDependencyCount - }); - - it('should handle empty analysis', () => { - const mockAnalysis = { - nodes: [], - edges: [] - }; - - const html = knowledgeGraph.generateInteractiveDashboard(mockAnalysis); - - expect(html).toContain(''); - expect(html).not.toContain('undefined'); - }); - }); - - describe('detectAPIConnections', () => { - it('should identify REST API connections', () => { - const mockIndex = { - files: { - 'frontend/api.js': { - content: "fetch('/api/users')", - imports: ['axios'] - }, - 'backend/routes.js': { - content: "router.get('/api/users', handler)", - exports: ['router'] - } - } - }; - - const connections = knowledgeGraph['detectAPIConnections'](mockIndex); - - expect(connections).toHaveLength(1); - expect(connections[0]).toMatchObject({ - from: 'frontend/api.js', - to: 'backend/routes.js', - endpoint: '/api/users', - method: 'GET' - }); - }); - - it('should identify GraphQL connections', () => { - const mockIndex = { - files: { - 'frontend/queries.js': { - content: `query GetUser { user { id name } }`, - imports: ['@apollo/client'] - }, - 'backend/schema.graphql': { - content: `type Query { user: User }`, - language: 'GraphQL' - } - } - }; - - const connections = knowledgeGraph['detectAPIConnections'](mockIndex); - - expect(connections.some(c => c.type === 'graphql')).toBe(true); - }); - }); - - describe('analyzeServiceBoundaries', () => { - it('should detect service boundaries in monorepo', () => { - const mockIndex = { - files: { - 'packages/frontend/index.js': {}, - 'packages/backend/server.js': {}, - 'packages/shared/utils.js': {} - }, - monorepo: { - root: 'packages', - services: { - frontend: { path: 'packages/frontend' }, - backend: { path: 'packages/backend' }, - shared: { path: 'packages/shared' } - } - } - }; - - const boundaries = knowledgeGraph['analyzeServiceBoundaries'](mockIndex); - - expect(boundaries).toHaveProperty('frontend'); - expect(boundaries).toHaveProperty('backend'); - expect(boundaries).toHaveProperty('shared'); - expect(boundaries.frontend.files).toContain('packages/frontend/index.js'); - }); - - it('should identify cross-service imports', () => { - const mockIndex = { - files: { - 'frontend/App.js': { - imports: ['../shared/utils', '../backend/types'] - }, - 'shared/utils.js': {}, - 'backend/types.js': {} - } - }; - - const boundaries = knowledgeGraph['analyzeServiceBoundaries'](mockIndex); - - expect(boundaries.violations).toBeDefined(); - expect(boundaries.violations.some(v => - v.from === 'frontend/App.js' && v.to === 'backend/types.js' - )).toBe(true); - }); - }); - - describe('calculateMetrics', () => { - it('should calculate all metrics correctly', () => { - const mockAnalysis = { - nodes: Array(10).fill({ type: 'file' }), - edges: Array(15).fill({ type: 'import' }), - circularDependencies: [['a', 'b'], ['c', 'd', 'e']], - services: { - frontend: { files: 5 }, - backend: { files: 3 }, - shared: { files: 2 } - } - }; - - const metrics = knowledgeGraph['calculateMetrics'](mockAnalysis); - - expect(metrics.totalNodes).toBe(10); - expect(metrics.totalEdges).toBe(15); - expect(metrics.circularDependencyCount).toBe(2); - expect(metrics.serviceCount).toBe(3); - expect(metrics.averageConnectivity).toBe(1.5); // 15 edges / 10 nodes - }); - - it('should handle empty analysis', () => { - const mockAnalysis = { - nodes: [], - edges: [] - }; - - const metrics = knowledgeGraph['calculateMetrics'](mockAnalysis); - - expect(metrics.totalNodes).toBe(0); - expect(metrics.totalEdges).toBe(0); - expect(metrics.averageConnectivity).toBe(0); - }); - }); - - describe('exportToFile', () => { - it('should export analysis to JSON file', () => { - const mockAnalysis = { - nodes: [{ id: '1', label: 'test' }], - edges: [], - timestamp: new Date().toISOString() - }; - - knowledgeGraph.exportToFile(mockAnalysis, '/output/analysis.json'); - - expect(fs.writeFileSync).toHaveBeenCalledWith( - '/output/analysis.json', - JSON.stringify(mockAnalysis, null, 2) - ); - }); - - it('should export Mermaid diagram to file', () => { - const mockAnalysis = { - nodes: [], - edges: [] - }; - - knowledgeGraph.exportToFile(mockAnalysis, '/output/diagram.mmd', 'mermaid'); - - expect(fs.writeFileSync).toHaveBeenCalledWith( - '/output/diagram.mmd', - expect.stringContaining('graph TD') - ); - }); - - it('should export HTML dashboard to file', () => { - const mockAnalysis = { - nodes: [], - edges: [] - }; - - knowledgeGraph.exportToFile(mockAnalysis, '/output/dashboard.html', 'html'); - - expect(fs.writeFileSync).toHaveBeenCalledWith( - '/output/dashboard.html', - expect.stringContaining('') - ); - }); - }); +import { MultiRepoKnowledgeGraph } from '../../src/core/multi-repo-knowledge-graph'; +import * as fs from 'fs'; +import * as path from 'path'; + +jest.mock('fs'); + +describe('MultiRepoKnowledgeGraph', () => { + let knowledgeGraph: MultiRepoKnowledgeGraph; + + beforeEach(() => { + jest.clearAllMocks(); + knowledgeGraph = new MultiRepoKnowledgeGraph(); + (fs.writeFileSync as jest.Mock).mockReturnValue(undefined); + }); + + describe('analyze', () => { + it('should analyze single repository structure', () => { + const mockIndex = { + files: { + 'src/index.js': { + imports: ['./utils', './config'], + exports: ['main'], + functions: [{ name: 'main' }] + }, + 'src/utils.js': { + exports: ['helper'], + functions: [{ name: 'helper' }] + } + }, + statistics: { totalFiles: 2 } + }; + + const result = knowledgeGraph.analyze(mockIndex); + + expect(result.nodes).toHaveLength(2); + expect(result.edges).toHaveLength(2); // Two imports from index.js + expect(result.nodes[0]).toMatchObject({ + id: expect.any(String), + label: 'src/index.js', + type: 'file' + }); + }); + + it('should detect monorepo with multiple services', () => { + const mockIndex = { + files: { + 'frontend/src/App.js': { + imports: ['axios', '@shared/utils'], + exports: ['App'] + }, + 'backend/src/server.js': { + imports: ['express', '@shared/config'], + exports: ['server'] + }, + 'shared/utils.js': { + exports: ['formatDate', 'parseData'] + } + }, + monorepo: { + services: { + frontend: { files: 1, dependencies: ['shared'] }, + backend: { files: 1, dependencies: ['shared'] }, + shared: { files: 1, dependencies: [] } + } + } + }; + + const result = knowledgeGraph.analyze(mockIndex); + + expect(result.services).toBeDefined(); + expect(result.services).toHaveProperty('frontend'); + expect(result.services).toHaveProperty('backend'); + expect(result.services).toHaveProperty('shared'); + expect(result.crossServiceDependencies).toBeDefined(); + }); + + it('should identify circular dependencies', () => { + const mockIndex = { + files: { + 'a.js': { imports: ['./b'] }, + 'b.js': { imports: ['./c'] }, + 'c.js': { imports: ['./a'] } + } + }; + + const result = knowledgeGraph.analyze(mockIndex); + + expect(result.circularDependencies).toBeDefined(); + expect(result.circularDependencies.length).toBeGreaterThan(0); + }); + + it('should calculate complexity metrics', () => { + const mockIndex = { + files: { + 'complex.js': { + functions: Array(10).fill({ name: 'func' }), + classes: Array(5).fill({ name: 'Class' }), + complexity: 25 + }, + 'simple.js': { + functions: [{ name: 'simple' }], + complexity: 2 + } + }, + statistics: { + totalFiles: 2, + totalFunctions: 11, + totalClasses: 5 + } + }; + + const result = knowledgeGraph.analyze(mockIndex); + + expect(result.metrics).toBeDefined(); + expect(result.metrics.averageComplexity).toBeDefined(); + expect(result.metrics.highComplexityFiles).toBeDefined(); + expect(result.metrics.highComplexityFiles).toContain('complex.js'); + }); + }); + + describe('generateMermaidDiagram', () => { + it('should generate valid Mermaid syntax', () => { + const mockAnalysis = { + nodes: [ + { id: '1', label: 'index.js', type: 'file' }, + { id: '2', label: 'utils.js', type: 'file' } + ], + edges: [ + { from: '1', to: '2', type: 'import' } + ] + }; + + const diagram = knowledgeGraph.generateMermaidDiagram(mockAnalysis); + + expect(diagram).toContain('graph TD'); + expect(diagram).toContain('1[index.js]'); + expect(diagram).toContain('2[utils.js]'); + expect(diagram).toContain('1 --> 2'); + }); + + it('should handle service-level dependencies', () => { + const mockAnalysis = { + nodes: [], + edges: [], + services: { + frontend: { dependencies: ['backend', 'shared'] }, + backend: { dependencies: ['shared'] }, + shared: { dependencies: [] } + } + }; + + const diagram = knowledgeGraph.generateMermaidDiagram(mockAnalysis); + + expect(diagram).toContain('subgraph'); + expect(diagram).toContain('frontend'); + expect(diagram).toContain('backend'); + expect(diagram).toContain('shared'); + }); + + it('should highlight circular dependencies', () => { + const mockAnalysis = { + nodes: [ + { id: '1', label: 'a.js', type: 'file' }, + { id: '2', label: 'b.js', type: 'file' } + ], + edges: [ + { from: '1', to: '2', type: 'import' }, + { from: '2', to: '1', type: 'import' } + ], + circularDependencies: [['a.js', 'b.js']] + }; + + const diagram = knowledgeGraph.generateMermaidDiagram(mockAnalysis); + + expect(diagram).toContain('style'); + expect(diagram).toContain('stroke:#ff0000'); + }); + }); + + describe('generateInteractiveDashboard', () => { + it('should generate HTML dashboard', () => { + const mockAnalysis = { + nodes: [{ id: '1', label: 'test.js' }], + edges: [], + metrics: { + totalNodes: 1, + totalEdges: 0, + averageComplexity: 5 + } + }; + + const html = knowledgeGraph.generateInteractiveDashboard(mockAnalysis); + + expect(html).toContain(''); + expect(html).toContain(' { + const mockAnalysis = { + nodes: [], + edges: [], + metrics: { + totalFiles: 100, + totalConnections: 250, + averageComplexity: 8.5, + circularDependencyCount: 3 + } + }; + + const html = knowledgeGraph.generateInteractiveDashboard(mockAnalysis); + + expect(html).toContain('100'); // totalFiles + expect(html).toContain('250'); // totalConnections + expect(html).toContain('8.5'); // averageComplexity + expect(html).toContain('3'); // circularDependencyCount + }); + + it('should handle empty analysis', () => { + const mockAnalysis = { + nodes: [], + edges: [] + }; + + const html = knowledgeGraph.generateInteractiveDashboard(mockAnalysis); + + expect(html).toContain(''); + expect(html).not.toContain('undefined'); + }); + }); + + describe('detectAPIConnections', () => { + it('should identify REST API connections', () => { + const mockIndex = { + files: { + 'frontend/api.js': { + content: "fetch('/api/users')", + imports: ['axios'] + }, + 'backend/routes.js': { + content: "router.get('/api/users', handler)", + exports: ['router'] + } + } + }; + + const connections = knowledgeGraph['detectAPIConnections'](mockIndex); + + expect(connections).toHaveLength(1); + expect(connections[0]).toMatchObject({ + from: 'frontend/api.js', + to: 'backend/routes.js', + endpoint: '/api/users', + method: 'GET' + }); + }); + + it('should identify GraphQL connections', () => { + const mockIndex = { + files: { + 'frontend/queries.js': { + content: `query GetUser { user { id name } }`, + imports: ['@apollo/client'] + }, + 'backend/schema.graphql': { + content: `type Query { user: User }`, + language: 'GraphQL' + } + } + }; + + const connections = knowledgeGraph['detectAPIConnections'](mockIndex); + + expect(connections.some(c => c.type === 'graphql')).toBe(true); + }); + }); + + describe('analyzeServiceBoundaries', () => { + it('should detect service boundaries in monorepo', () => { + const mockIndex = { + files: { + 'packages/frontend/index.js': {}, + 'packages/backend/server.js': {}, + 'packages/shared/utils.js': {} + }, + monorepo: { + root: 'packages', + services: { + frontend: { path: 'packages/frontend' }, + backend: { path: 'packages/backend' }, + shared: { path: 'packages/shared' } + } + } + }; + + const boundaries = knowledgeGraph['analyzeServiceBoundaries'](mockIndex); + + expect(boundaries).toHaveProperty('frontend'); + expect(boundaries).toHaveProperty('backend'); + expect(boundaries).toHaveProperty('shared'); + expect(boundaries.frontend.files).toContain('packages/frontend/index.js'); + }); + + it('should identify cross-service imports', () => { + const mockIndex = { + files: { + 'frontend/App.js': { + imports: ['../shared/utils', '../backend/types'] + }, + 'shared/utils.js': {}, + 'backend/types.js': {} + } + }; + + const boundaries = knowledgeGraph['analyzeServiceBoundaries'](mockIndex); + + expect(boundaries.violations).toBeDefined(); + expect(boundaries.violations.some(v => + v.from === 'frontend/App.js' && v.to === 'backend/types.js' + )).toBe(true); + }); + }); + + describe('calculateMetrics', () => { + it('should calculate all metrics correctly', () => { + const mockAnalysis = { + nodes: Array(10).fill({ type: 'file' }), + edges: Array(15).fill({ type: 'import' }), + circularDependencies: [['a', 'b'], ['c', 'd', 'e']], + services: { + frontend: { files: 5 }, + backend: { files: 3 }, + shared: { files: 2 } + } + }; + + const metrics = knowledgeGraph['calculateMetrics'](mockAnalysis); + + expect(metrics.totalNodes).toBe(10); + expect(metrics.totalEdges).toBe(15); + expect(metrics.circularDependencyCount).toBe(2); + expect(metrics.serviceCount).toBe(3); + expect(metrics.averageConnectivity).toBe(1.5); // 15 edges / 10 nodes + }); + + it('should handle empty analysis', () => { + const mockAnalysis = { + nodes: [], + edges: [] + }; + + const metrics = knowledgeGraph['calculateMetrics'](mockAnalysis); + + expect(metrics.totalNodes).toBe(0); + expect(metrics.totalEdges).toBe(0); + expect(metrics.averageConnectivity).toBe(0); + }); + }); + + describe('exportToFile', () => { + it('should export analysis to JSON file', () => { + const mockAnalysis = { + nodes: [{ id: '1', label: 'test' }], + edges: [], + timestamp: new Date().toISOString() + }; + + knowledgeGraph.exportToFile(mockAnalysis, '/output/analysis.json'); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + '/output/analysis.json', + JSON.stringify(mockAnalysis, null, 2) + ); + }); + + it('should export Mermaid diagram to file', () => { + const mockAnalysis = { + nodes: [], + edges: [] + }; + + knowledgeGraph.exportToFile(mockAnalysis, '/output/diagram.mmd', 'mermaid'); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + '/output/diagram.mmd', + expect.stringContaining('graph TD') + ); + }); + + it('should export HTML dashboard to file', () => { + const mockAnalysis = { + nodes: [], + edges: [] + }; + + knowledgeGraph.exportToFile(mockAnalysis, '/output/dashboard.html', 'html'); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + '/output/dashboard.html', + expect.stringContaining('') + ); + }); + }); }); \ No newline at end of file diff --git a/test/core/smart-indexer.test.ts b/test/core/smart-indexer.test.ts index 0804ee7..425baaa 100644 --- a/test/core/smart-indexer.test.ts +++ b/test/core/smart-indexer.test.ts @@ -1,324 +1,324 @@ -import { jest, describe, it, expect, beforeEach } from '@jest/globals'; -import { SmartIndexer } from '../../src/core/smart-indexer'; -import { Indexer } from '../../src/core/indexer'; -import { MultiRepoKnowledgeGraph } from '../../src/core/multi-repo-knowledge-graph'; -import * as fs from 'fs'; -import * as path from 'path'; - -// Mock dependencies -jest.mock('../../src/core/indexer'); -jest.mock('../../src/core/multi-repo-knowledge-graph'); -jest.mock('../../src/core/config'); -jest.mock('fs'); -jest.mock('child_process'); - -describe('SmartIndexer', () => { - let smartIndexer: SmartIndexer; - const mockProjectPath = '/test/project'; - const mockConfigPath = '/test/config.yml'; - - beforeEach(() => { - jest.clearAllMocks(); - smartIndexer = new SmartIndexer(mockProjectPath, mockConfigPath); - - // Mock fs.existsSync - (fs.existsSync as jest.Mock).mockReturnValue(true); - (fs.readFileSync as jest.Mock).mockReturnValue('{}'); - (fs.mkdirSync as jest.Mock).mockReturnValue(undefined); - (fs.writeFileSync as jest.Mock).mockReturnValue(undefined); - }); - - describe('constructor', () => { - it('should initialize with project path and config', () => { - expect(smartIndexer).toBeDefined(); - expect(smartIndexer['projectPath']).toBe(mockProjectPath); - expect(smartIndexer['configPath']).toBe(mockConfigPath); - }); - - it('should use current directory if no project path provided', () => { - const indexer = new SmartIndexer(); - expect(indexer['projectPath']).toBe(process.cwd()); - }); - }); - - describe('runComplete', () => { - it('should execute all indexing steps', async () => { - // Mock private methods - const detectProjectSpy = jest.spyOn(smartIndexer as any, 'detectProjectType').mockResolvedValue({ - type: 'monorepo', - services: ['frontend', 'backend'] - }); - const runIndexingSpy = jest.spyOn(smartIndexer as any, 'runIndexing').mockResolvedValue({ - files: {}, - statistics: { totalFiles: 100 } - }); - const generateExportsSpy = jest.spyOn(smartIndexer as any, 'generateAllExports').mockResolvedValue(undefined); - const generateVisualizationsSpy = jest.spyOn(smartIndexer as any, 'generateVisualizations').mockResolvedValue(undefined); - const generateDashboardSpy = jest.spyOn(smartIndexer as any, 'generateDashboard').mockResolvedValue(undefined); - const showSummarySpy = jest.spyOn(smartIndexer as any, 'showSummary').mockResolvedValue(undefined); - - await smartIndexer.runComplete(); - - expect(detectProjectSpy).toHaveBeenCalled(); - expect(runIndexingSpy).toHaveBeenCalled(); - expect(generateExportsSpy).toHaveBeenCalled(); - expect(generateVisualizationsSpy).toHaveBeenCalled(); - expect(generateDashboardSpy).toHaveBeenCalled(); - expect(showSummarySpy).toHaveBeenCalled(); - }); - - it('should handle errors gracefully', async () => { - jest.spyOn(smartIndexer as any, 'detectProjectType').mockRejectedValue(new Error('Detection failed')); - - const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); - const processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never); - - await smartIndexer.runComplete(); - - expect(consoleSpy).toHaveBeenCalledWith('❌ SmartIndexer failed:', expect.any(Error)); - expect(processExitSpy).toHaveBeenCalledWith(1); - - consoleSpy.mockRestore(); - processExitSpy.mockRestore(); - }); - }); - - describe('detectProjectType', () => { - it('should detect monorepo structure', async () => { - (fs.existsSync as jest.Mock).mockImplementation((path: string) => { - return path.includes('lerna.json') || path.includes('packages'); - }); - - (fs.readdirSync as jest.Mock).mockReturnValue(['frontend', 'backend', 'shared']); - - const result = await smartIndexer['detectProjectType'](); - - expect(result.type).toContain('monorepo'); - expect(result.services).toBeDefined(); - }); - - it('should detect Next.js project', async () => { - (fs.existsSync as jest.Mock).mockImplementation((path: string) => { - return path.includes('next.config'); - }); - - (fs.readFileSync as jest.Mock).mockImplementation((path: string) => { - if (path.includes('package.json')) { - return JSON.stringify({ dependencies: { next: '^13.0.0' } }); - } - return '{}'; - }); - - const result = await smartIndexer['detectProjectType'](); - expect(result.framework).toBe('Next.js'); - }); - - it('should detect Python project', async () => { - (fs.existsSync as jest.Mock).mockImplementation((path: string) => { - return path.includes('requirements.txt') || path.includes('pyproject.toml'); - }); - - const result = await smartIndexer['detectProjectType'](); - expect(result.language).toBe('Python'); - }); - }); - - describe('runIndexing', () => { - it('should create index with progress callback', async () => { - const mockIndex = { - files: { 'test.js': { functions: [], classes: [] } }, - statistics: { totalFiles: 1, totalFunctions: 0 }, - timestamp: new Date().toISOString() - }; - - const mockIndexer = { - buildIndex: jest.fn().mockResolvedValue(mockIndex) - }; - - (Indexer as jest.MockedClass).mockImplementation(() => mockIndexer as any); - - const result = await smartIndexer['runIndexing'](); - - expect(result).toEqual(mockIndex); - expect(mockIndexer.buildIndex).toHaveBeenCalledWith( - mockProjectPath, - expect.objectContaining({ - onProgress: expect.any(Function), - parallel: true - }) - ); - }); - - it('should handle indexing errors', async () => { - const mockIndexer = { - buildIndex: jest.fn().mockRejectedValue(new Error('Indexing failed')) - }; - - (Indexer as jest.MockedClass).mockImplementation(() => mockIndexer as any); - - await expect(smartIndexer['runIndexing']()).rejects.toThrow('Indexing failed'); - }); - }); - - describe('generateAllExports', () => { - it('should generate all export formats', async () => { - const mockIndex = { - files: {}, - statistics: { totalFiles: 5 }, - timestamp: new Date().toISOString() - }; - - // Mock exporters - const exportFormats = ['json', 'markdown', 'graphviz', 'mermaid', 'ascii']; - const exportSpies: any = {}; - - for (const format of exportFormats) { - const spy = jest.spyOn(smartIndexer as any, `export${format.charAt(0).toUpperCase() + format.slice(1)}`); - spy.mockResolvedValue(undefined); - exportSpies[format] = spy; - } - - await smartIndexer['generateAllExports'](mockIndex, mockProjectPath); - - for (const format of exportFormats) { - expect(exportSpies[format]).toHaveBeenCalledWith(mockIndex, mockProjectPath); - } - }); - - it('should continue if one export fails', async () => { - const mockIndex = { files: {}, statistics: {}, timestamp: '' }; - - jest.spyOn(smartIndexer as any, 'exportJson').mockRejectedValue(new Error('JSON failed')); - jest.spyOn(smartIndexer as any, 'exportMarkdown').mockResolvedValue(undefined); - - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); - - await smartIndexer['generateAllExports'](mockIndex, mockProjectPath); - - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to export json')); - consoleSpy.mockRestore(); - }); - }); - - describe('generateVisualizations', () => { - it('should create multi-repo knowledge graph', async () => { - const mockIndex = { - files: { 'test.js': {} }, - statistics: {}, - monorepo: { services: { frontend: {}, backend: {} } } - }; - - const mockGraph = { - analyze: jest.fn().mockReturnValue({ nodes: [], edges: [] }), - generateMermaidDiagram: jest.fn().mockReturnValue('graph TD'), - generateInteractiveDashboard: jest.fn().mockReturnValue('') - }; - - (MultiRepoKnowledgeGraph as jest.MockedClass).mockImplementation(() => mockGraph as any); - - await smartIndexer['generateVisualizations'](mockIndex, mockProjectPath); - - expect(mockGraph.analyze).toHaveBeenCalledWith(mockIndex); - expect(mockGraph.generateMermaidDiagram).toHaveBeenCalled(); - expect(mockGraph.generateInteractiveDashboard).toHaveBeenCalled(); - }); - }); - - describe('generateDashboard', () => { - it('should create HTML dashboard', async () => { - const mockProjectInfo = { - type: 'monorepo', - services: ['frontend', 'backend'] - }; - - const mockIndex = { - statistics: { - totalFiles: 100, - totalFunctions: 50, - totalClasses: 10, - languages: { JavaScript: 80, TypeScript: 20 } - } - }; - - await smartIndexer['generateDashboard'](mockProjectInfo, mockIndex, mockProjectPath); - - expect(fs.writeFileSync).toHaveBeenCalledWith( - expect.stringContaining('dashboard.html'), - expect.stringContaining('') - ); - }); - }); - - describe('showSummary', () => { - it('should display analysis summary', async () => { - const mockIndex = { - statistics: { - totalFiles: 100, - totalFunctions: 50, - totalClasses: 10, - languages: { JavaScript: 60, Python: 40 } - }, - timestamp: new Date().toISOString() - }; - - const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); - - await smartIndexer['showSummary'](mockIndex, mockProjectPath); - - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Analysis Complete')); - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('100 files')); - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('50 functions')); - - consoleSpy.mockRestore(); - }); - - it('should show language distribution', async () => { - const mockIndex = { - statistics: { - totalFiles: 100, - languages: { - JavaScript: 40, - TypeScript: 30, - Python: 20, - Go: 10 - } - } - }; - - const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); - - await smartIndexer['showSummary'](mockIndex, mockProjectPath); - - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('JavaScript')); - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('40%')); - - consoleSpy.mockRestore(); - }); - }); - - describe('Edge Cases', () => { - it('should handle empty project', async () => { - (fs.readdirSync as jest.Mock).mockReturnValue([]); - - const result = await smartIndexer['detectProjectType'](); - expect(result.type).toBe('Empty project'); - }); - - it('should handle missing package.json', async () => { - (fs.existsSync as jest.Mock).mockReturnValue(false); - (fs.readFileSync as jest.Mock).mockImplementation(() => { - throw new Error('File not found'); - }); - - const result = await smartIndexer['detectProjectType'](); - expect(result).toBeDefined(); - }); - - it('should handle malformed JSON', async () => { - (fs.readFileSync as jest.Mock).mockReturnValue('{ invalid json }'); - - const result = await smartIndexer['detectProjectType'](); - expect(result).toBeDefined(); - }); - }); +import { jest, describe, it, expect, beforeEach } from '@jest/globals'; +import { SmartIndexer } from '../../src/core/smart-indexer'; +import { Indexer } from '../../src/core/indexer'; +import { MultiRepoKnowledgeGraph } from '../../src/core/multi-repo-knowledge-graph'; +import * as fs from 'fs'; +import * as path from 'path'; + +// Mock dependencies +jest.mock('../../src/core/indexer'); +jest.mock('../../src/core/multi-repo-knowledge-graph'); +jest.mock('../../src/core/config'); +jest.mock('fs'); +jest.mock('child_process'); + +describe('SmartIndexer', () => { + let smartIndexer: SmartIndexer; + const mockProjectPath = '/test/project'; + const mockConfigPath = '/test/config.yml'; + + beforeEach(() => { + jest.clearAllMocks(); + smartIndexer = new SmartIndexer(mockProjectPath, mockConfigPath); + + // Mock fs.existsSync + (fs.existsSync as jest.Mock).mockReturnValue(true); + (fs.readFileSync as jest.Mock).mockReturnValue('{}'); + (fs.mkdirSync as jest.Mock).mockReturnValue(undefined); + (fs.writeFileSync as jest.Mock).mockReturnValue(undefined); + }); + + describe('constructor', () => { + it('should initialize with project path and config', () => { + expect(smartIndexer).toBeDefined(); + expect(smartIndexer['projectPath']).toBe(mockProjectPath); + expect(smartIndexer['configPath']).toBe(mockConfigPath); + }); + + it('should use current directory if no project path provided', () => { + const indexer = new SmartIndexer(); + expect(indexer['projectPath']).toBe(process.cwd()); + }); + }); + + describe('runComplete', () => { + it('should execute all indexing steps', async () => { + // Mock private methods + const detectProjectSpy = jest.spyOn(smartIndexer as any, 'detectProjectType').mockResolvedValue({ + type: 'monorepo', + services: ['frontend', 'backend'] + }); + const runIndexingSpy = jest.spyOn(smartIndexer as any, 'runIndexing').mockResolvedValue({ + files: {}, + statistics: { totalFiles: 100 } + }); + const generateExportsSpy = jest.spyOn(smartIndexer as any, 'generateAllExports').mockResolvedValue(undefined); + const generateVisualizationsSpy = jest.spyOn(smartIndexer as any, 'generateVisualizations').mockResolvedValue(undefined); + const generateDashboardSpy = jest.spyOn(smartIndexer as any, 'generateDashboard').mockResolvedValue(undefined); + const showSummarySpy = jest.spyOn(smartIndexer as any, 'showSummary').mockResolvedValue(undefined); + + await smartIndexer.runComplete(); + + expect(detectProjectSpy).toHaveBeenCalled(); + expect(runIndexingSpy).toHaveBeenCalled(); + expect(generateExportsSpy).toHaveBeenCalled(); + expect(generateVisualizationsSpy).toHaveBeenCalled(); + expect(generateDashboardSpy).toHaveBeenCalled(); + expect(showSummarySpy).toHaveBeenCalled(); + }); + + it('should handle errors gracefully', async () => { + jest.spyOn(smartIndexer as any, 'detectProjectType').mockRejectedValue(new Error('Detection failed')); + + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + const processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never); + + await smartIndexer.runComplete(); + + expect(consoleSpy).toHaveBeenCalledWith('❌ SmartIndexer failed:', expect.any(Error)); + expect(processExitSpy).toHaveBeenCalledWith(1); + + consoleSpy.mockRestore(); + processExitSpy.mockRestore(); + }); + }); + + describe('detectProjectType', () => { + it('should detect monorepo structure', async () => { + (fs.existsSync as jest.Mock).mockImplementation((path: string) => { + return path.includes('lerna.json') || path.includes('packages'); + }); + + (fs.readdirSync as jest.Mock).mockReturnValue(['frontend', 'backend', 'shared']); + + const result = await smartIndexer['detectProjectType'](); + + expect(result.type).toContain('monorepo'); + expect(result.services).toBeDefined(); + }); + + it('should detect Next.js project', async () => { + (fs.existsSync as jest.Mock).mockImplementation((path: string) => { + return path.includes('next.config'); + }); + + (fs.readFileSync as jest.Mock).mockImplementation((path: string) => { + if (path.includes('package.json')) { + return JSON.stringify({ dependencies: { next: '^13.0.0' } }); + } + return '{}'; + }); + + const result = await smartIndexer['detectProjectType'](); + expect(result.framework).toBe('Next.js'); + }); + + it('should detect Python project', async () => { + (fs.existsSync as jest.Mock).mockImplementation((path: string) => { + return path.includes('requirements.txt') || path.includes('pyproject.toml'); + }); + + const result = await smartIndexer['detectProjectType'](); + expect(result.language).toBe('Python'); + }); + }); + + describe('runIndexing', () => { + it('should create index with progress callback', async () => { + const mockIndex = { + files: { 'test.js': { functions: [], classes: [] } }, + statistics: { totalFiles: 1, totalFunctions: 0 }, + timestamp: new Date().toISOString() + }; + + const mockIndexer = { + buildIndex: jest.fn().mockResolvedValue(mockIndex) + }; + + (Indexer as jest.MockedClass).mockImplementation(() => mockIndexer as any); + + const result = await smartIndexer['runIndexing'](); + + expect(result).toEqual(mockIndex); + expect(mockIndexer.buildIndex).toHaveBeenCalledWith( + mockProjectPath, + expect.objectContaining({ + onProgress: expect.any(Function), + parallel: true + }) + ); + }); + + it('should handle indexing errors', async () => { + const mockIndexer = { + buildIndex: jest.fn().mockRejectedValue(new Error('Indexing failed')) + }; + + (Indexer as jest.MockedClass).mockImplementation(() => mockIndexer as any); + + await expect(smartIndexer['runIndexing']()).rejects.toThrow('Indexing failed'); + }); + }); + + describe('generateAllExports', () => { + it('should generate all export formats', async () => { + const mockIndex = { + files: {}, + statistics: { totalFiles: 5 }, + timestamp: new Date().toISOString() + }; + + // Mock exporters + const exportFormats = ['json', 'markdown', 'graphviz', 'mermaid', 'ascii']; + const exportSpies: any = {}; + + for (const format of exportFormats) { + const spy = jest.spyOn(smartIndexer as any, `export${format.charAt(0).toUpperCase() + format.slice(1)}`); + spy.mockResolvedValue(undefined); + exportSpies[format] = spy; + } + + await smartIndexer['generateAllExports'](mockIndex, mockProjectPath); + + for (const format of exportFormats) { + expect(exportSpies[format]).toHaveBeenCalledWith(mockIndex, mockProjectPath); + } + }); + + it('should continue if one export fails', async () => { + const mockIndex = { files: {}, statistics: {}, timestamp: '' }; + + jest.spyOn(smartIndexer as any, 'exportJson').mockRejectedValue(new Error('JSON failed')); + jest.spyOn(smartIndexer as any, 'exportMarkdown').mockResolvedValue(undefined); + + const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + + await smartIndexer['generateAllExports'](mockIndex, mockProjectPath); + + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to export json')); + consoleSpy.mockRestore(); + }); + }); + + describe('generateVisualizations', () => { + it('should create multi-repo knowledge graph', async () => { + const mockIndex = { + files: { 'test.js': {} }, + statistics: {}, + monorepo: { services: { frontend: {}, backend: {} } } + }; + + const mockGraph = { + analyze: jest.fn().mockReturnValue({ nodes: [], edges: [] }), + generateMermaidDiagram: jest.fn().mockReturnValue('graph TD'), + generateInteractiveDashboard: jest.fn().mockReturnValue('') + }; + + (MultiRepoKnowledgeGraph as jest.MockedClass).mockImplementation(() => mockGraph as any); + + await smartIndexer['generateVisualizations'](mockIndex, mockProjectPath); + + expect(mockGraph.analyze).toHaveBeenCalledWith(mockIndex); + expect(mockGraph.generateMermaidDiagram).toHaveBeenCalled(); + expect(mockGraph.generateInteractiveDashboard).toHaveBeenCalled(); + }); + }); + + describe('generateDashboard', () => { + it('should create HTML dashboard', async () => { + const mockProjectInfo = { + type: 'monorepo', + services: ['frontend', 'backend'] + }; + + const mockIndex = { + statistics: { + totalFiles: 100, + totalFunctions: 50, + totalClasses: 10, + languages: { JavaScript: 80, TypeScript: 20 } + } + }; + + await smartIndexer['generateDashboard'](mockProjectInfo, mockIndex, mockProjectPath); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.stringContaining('dashboard.html'), + expect.stringContaining('') + ); + }); + }); + + describe('showSummary', () => { + it('should display analysis summary', async () => { + const mockIndex = { + statistics: { + totalFiles: 100, + totalFunctions: 50, + totalClasses: 10, + languages: { JavaScript: 60, Python: 40 } + }, + timestamp: new Date().toISOString() + }; + + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + + await smartIndexer['showSummary'](mockIndex, mockProjectPath); + + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Analysis Complete')); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('100 files')); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('50 functions')); + + consoleSpy.mockRestore(); + }); + + it('should show language distribution', async () => { + const mockIndex = { + statistics: { + totalFiles: 100, + languages: { + JavaScript: 40, + TypeScript: 30, + Python: 20, + Go: 10 + } + } + }; + + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + + await smartIndexer['showSummary'](mockIndex, mockProjectPath); + + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('JavaScript')); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('40%')); + + consoleSpy.mockRestore(); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty project', async () => { + (fs.readdirSync as jest.Mock).mockReturnValue([]); + + const result = await smartIndexer['detectProjectType'](); + expect(result.type).toBe('Empty project'); + }); + + it('should handle missing package.json', async () => { + (fs.existsSync as jest.Mock).mockReturnValue(false); + (fs.readFileSync as jest.Mock).mockImplementation(() => { + throw new Error('File not found'); + }); + + const result = await smartIndexer['detectProjectType'](); + expect(result).toBeDefined(); + }); + + it('should handle malformed JSON', async () => { + (fs.readFileSync as jest.Mock).mockReturnValue('{ invalid json }'); + + const result = await smartIndexer['detectProjectType'](); + expect(result).toBeDefined(); + }); + }); }); \ No newline at end of file diff --git a/test/fixtures/complex_python.py b/test/fixtures/complex_python.py index c385639..ddb930d 100644 --- a/test/fixtures/complex_python.py +++ b/test/fixtures/complex_python.py @@ -1,402 +1,402 @@ -""" -Complex Python test file for tree-sitter parser validation. -Tests all major Python 3 language features. -""" - -# Standard imports -import os -import sys -from typing import List, Dict, Optional, Union, TypeVar, Generic -from dataclasses import dataclass, field -from abc import ABC, abstractmethod -from collections.abc import Iterable -import asyncio -from functools import lru_cache, wraps -import numpy as np - -# Relative imports -from ..utils import helper_function -from . import local_module - -# Constants -MAX_SIZE: int = 1000 -DEFAULT_NAME = "Python Parser" -API_ENDPOINT = "https://api.example.com" -_PRIVATE_CONSTANT = 42 - -# Type variables -T = TypeVar('T') -K = TypeVar('K') -V = TypeVar('V') - -# __all__ export definition -__all__ = ['DataProcessor', 'process_data', 'async_fetch', 'CONFIG'] - - -# Decorators -def timing_decorator(func): - """Decorator to measure function execution time.""" - @wraps(func) - def wrapper(*args, **kwargs): - import time - start = time.time() - result = func(*args, **kwargs) - end = time.time() - print(f"{func.__name__} took {end - start} seconds") - return result - return wrapper - - -def singleton(cls): - """Class decorator for singleton pattern.""" - instances = {} - def get_instance(*args, **kwargs): - if cls not in instances: - instances[cls] = cls(*args, **kwargs) - return instances[cls] - return get_instance - - -# Simple function with type hints -def add_numbers(a: int, b: int = 10) -> int: - """Add two numbers together.""" - return a + b - - -# Function with complex type hints -def process_items( - items: List[Dict[str, Union[str, int]]], - filter_key: Optional[str] = None, - *args: tuple, - **kwargs: dict -) -> Dict[str, List[int]]: - """Process a list of items with filtering.""" - result = {} - for item in items: - if filter_key and filter_key in item: - key = str(item[filter_key]) - if key not in result: - result[key] = [] - result[key].append(len(item)) - return result - - -# Generator function -def fibonacci(n: int): - """Generate Fibonacci sequence.""" - a, b = 0, 1 - for _ in range(n): - yield a - a, b = b, a + b - - -# Async function -async def async_fetch(url: str) -> dict: - """Asynchronously fetch data from URL.""" - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - return await response.json() - - -# Async generator -async def async_counter(start: int, end: int): - """Async generator that counts from start to end.""" - for i in range(start, end): - await asyncio.sleep(0.1) - yield i - - -# Function with decorators -@lru_cache(maxsize=128) -@timing_decorator -def expensive_computation(n: int) -> int: - """Expensive computation with caching.""" - if n <= 1: - return n - return expensive_computation(n - 1) + expensive_computation(n - 2) - - -# Lambda functions -square = lambda x: x ** 2 -add = lambda x, y: x + y - - -# Abstract base class -class Animal(ABC): - """Abstract base class for animals.""" - - def __init__(self, name: str): - self.name = name - self._age = 0 - self.__private_id = id(self) - - @abstractmethod - def make_sound(self) -> str: - """Make the animal's sound.""" - pass - - @property - def age(self) -> int: - """Get the animal's age.""" - return self._age - - @age.setter - def age(self, value: int): - """Set the animal's age.""" - if value < 0: - raise ValueError("Age cannot be negative") - self._age = value - - @staticmethod - def get_species_count() -> int: - """Get total number of species.""" - return 42 - - @classmethod - def from_dict(cls, data: dict) -> 'Animal': - """Create animal from dictionary.""" - return cls(data.get('name', 'Unknown')) - - -# Concrete class with inheritance -class Dog(Animal): - """Concrete dog class.""" - - breed: str = "Unknown" - - def __init__(self, name: str, breed: str = "Mixed"): - super().__init__(name) - self.breed = breed - - def make_sound(self) -> str: - return "Woof!" - - def fetch(self, item: str) -> str: - return f"{self.name} fetched the {item}" - - -# Multiple inheritance -class Flyable: - """Mixin for flyable creatures.""" - - def fly(self, height: int) -> str: - return f"Flying at {height} meters" - - -class Bird(Animal, Flyable): - """Bird class with multiple inheritance.""" - - def make_sound(self) -> str: - return "Tweet!" - - -# Class with metaclass -class SingletonMeta(type): - """Metaclass for singleton pattern.""" - _instances = {} - - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - cls._instances[cls] = super().__call__(*args, **kwargs) - return cls._instances[cls] - - -class DatabaseConnection(metaclass=SingletonMeta): - """Singleton database connection.""" - - def __init__(self, connection_string: str): - self.connection_string = connection_string - self._connected = False - - def connect(self): - """Connect to database.""" - self._connected = True - - def disconnect(self): - """Disconnect from database.""" - self._connected = False - - -# Dataclass -@dataclass -class Point: - """Point in 2D space.""" - x: float - y: float - label: str = "origin" - metadata: Dict[str, any] = field(default_factory=dict) - - def distance_from_origin(self) -> float: - """Calculate distance from origin.""" - return (self.x ** 2 + self.y ** 2) ** 0.5 - - -# Generic class -class Stack(Generic[T]): - """Generic stack implementation.""" - - def __init__(self): - self._items: List[T] = [] - - def push(self, item: T) -> None: - """Push item onto stack.""" - self._items.append(item) - - def pop(self) -> Optional[T]: - """Pop item from stack.""" - if self._items: - return self._items.pop() - return None - - def __len__(self) -> int: - return len(self._items) - - -# Nested class -class Outer: - """Outer class with nested inner class.""" - - class Inner: - """Inner nested class.""" - - def inner_method(self) -> str: - return "Inner method" - - def outer_method(self) -> str: - inner = self.Inner() - return f"Outer method calling {inner.inner_method()}" - - -# Context manager -class FileManager: - """Context manager for file operations.""" - - def __init__(self, filename: str, mode: str = 'r'): - self.filename = filename - self.mode = mode - self.file = None - - def __enter__(self): - self.file = open(self.filename, self.mode) - return self.file - - def __exit__(self, exc_type, exc_val, exc_tb): - if self.file: - self.file.close() - - -# Class with operators -class Vector: - """Vector class with operator overloading.""" - - def __init__(self, x: float, y: float): - self.x = x - self.y = y - - def __add__(self, other: 'Vector') -> 'Vector': - return Vector(self.x + other.x, self.y + other.y) - - def __mul__(self, scalar: float) -> 'Vector': - return Vector(self.x * scalar, self.y * scalar) - - def __repr__(self) -> str: - return f"Vector({self.x}, {self.y})" - - -# Enum-like class -class Color: - """Color enumeration.""" - RED = "red" - GREEN = "green" - BLUE = "blue" - - _ALL_COLORS = [RED, GREEN, BLUE] - - @classmethod - def is_valid(cls, color: str) -> bool: - """Check if color is valid.""" - return color in cls._ALL_COLORS - - -# Configuration class -class Config: - """Application configuration.""" - DEBUG: bool = False - DATABASE_URL: str = "sqlite:///app.db" - SECRET_KEY: str = os.environ.get('SECRET_KEY', 'dev-key') - MAX_CONNECTIONS: int = 100 - - _instance = None - __slots__ = ['debug', 'db_url'] - - def __new__(cls): - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - -# Complex function with nested functions -def data_processor(raw_data: List[dict]) -> Dict[str, any]: - """Process raw data with nested helper functions.""" - - def validate_item(item: dict) -> bool: - """Validate single item.""" - return 'id' in item and 'value' in item - - def transform_item(item: dict) -> dict: - """Transform single item.""" - return { - 'id': item['id'], - 'processed_value': item['value'] * 2, - 'timestamp': datetime.now() - } - - # List comprehension - valid_items = [item for item in raw_data if validate_item(item)] - - # Dictionary comprehension - processed = { - str(item['id']): transform_item(item) - for item in valid_items - } - - # Generator expression - total = sum(item['value'] for item in valid_items) - - return { - 'processed': processed, - 'total': total, - 'count': len(valid_items) - } - - -# Exception classes -class CustomError(Exception): - """Base custom exception.""" - pass - - -class ValidationError(CustomError): - """Validation error exception.""" - - def __init__(self, field: str, message: str): - self.field = field - super().__init__(f"Validation error in {field}: {message}") - - -# Module-level code -if __name__ == "__main__": - # Test code - print(f"Testing {DEFAULT_NAME}") - - # Create instances - dog = Dog("Buddy", "Golden Retriever") - print(dog.make_sound()) - - # Test async - async def main(): - data = await async_fetch(API_ENDPOINT) - print(data) - - # Run async code +""" +Complex Python test file for tree-sitter parser validation. +Tests all major Python 3 language features. +""" + +# Standard imports +import os +import sys +from typing import List, Dict, Optional, Union, TypeVar, Generic +from dataclasses import dataclass, field +from abc import ABC, abstractmethod +from collections.abc import Iterable +import asyncio +from functools import lru_cache, wraps +import numpy as np + +# Relative imports +from ..utils import helper_function +from . import local_module + +# Constants +MAX_SIZE: int = 1000 +DEFAULT_NAME = "Python Parser" +API_ENDPOINT = "https://api.example.com" +_PRIVATE_CONSTANT = 42 + +# Type variables +T = TypeVar('T') +K = TypeVar('K') +V = TypeVar('V') + +# __all__ export definition +__all__ = ['DataProcessor', 'process_data', 'async_fetch', 'CONFIG'] + + +# Decorators +def timing_decorator(func): + """Decorator to measure function execution time.""" + @wraps(func) + def wrapper(*args, **kwargs): + import time + start = time.time() + result = func(*args, **kwargs) + end = time.time() + print(f"{func.__name__} took {end - start} seconds") + return result + return wrapper + + +def singleton(cls): + """Class decorator for singleton pattern.""" + instances = {} + def get_instance(*args, **kwargs): + if cls not in instances: + instances[cls] = cls(*args, **kwargs) + return instances[cls] + return get_instance + + +# Simple function with type hints +def add_numbers(a: int, b: int = 10) -> int: + """Add two numbers together.""" + return a + b + + +# Function with complex type hints +def process_items( + items: List[Dict[str, Union[str, int]]], + filter_key: Optional[str] = None, + *args: tuple, + **kwargs: dict +) -> Dict[str, List[int]]: + """Process a list of items with filtering.""" + result = {} + for item in items: + if filter_key and filter_key in item: + key = str(item[filter_key]) + if key not in result: + result[key] = [] + result[key].append(len(item)) + return result + + +# Generator function +def fibonacci(n: int): + """Generate Fibonacci sequence.""" + a, b = 0, 1 + for _ in range(n): + yield a + a, b = b, a + b + + +# Async function +async def async_fetch(url: str) -> dict: + """Asynchronously fetch data from URL.""" + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + return await response.json() + + +# Async generator +async def async_counter(start: int, end: int): + """Async generator that counts from start to end.""" + for i in range(start, end): + await asyncio.sleep(0.1) + yield i + + +# Function with decorators +@lru_cache(maxsize=128) +@timing_decorator +def expensive_computation(n: int) -> int: + """Expensive computation with caching.""" + if n <= 1: + return n + return expensive_computation(n - 1) + expensive_computation(n - 2) + + +# Lambda functions +square = lambda x: x ** 2 +add = lambda x, y: x + y + + +# Abstract base class +class Animal(ABC): + """Abstract base class for animals.""" + + def __init__(self, name: str): + self.name = name + self._age = 0 + self.__private_id = id(self) + + @abstractmethod + def make_sound(self) -> str: + """Make the animal's sound.""" + pass + + @property + def age(self) -> int: + """Get the animal's age.""" + return self._age + + @age.setter + def age(self, value: int): + """Set the animal's age.""" + if value < 0: + raise ValueError("Age cannot be negative") + self._age = value + + @staticmethod + def get_species_count() -> int: + """Get total number of species.""" + return 42 + + @classmethod + def from_dict(cls, data: dict) -> 'Animal': + """Create animal from dictionary.""" + return cls(data.get('name', 'Unknown')) + + +# Concrete class with inheritance +class Dog(Animal): + """Concrete dog class.""" + + breed: str = "Unknown" + + def __init__(self, name: str, breed: str = "Mixed"): + super().__init__(name) + self.breed = breed + + def make_sound(self) -> str: + return "Woof!" + + def fetch(self, item: str) -> str: + return f"{self.name} fetched the {item}" + + +# Multiple inheritance +class Flyable: + """Mixin for flyable creatures.""" + + def fly(self, height: int) -> str: + return f"Flying at {height} meters" + + +class Bird(Animal, Flyable): + """Bird class with multiple inheritance.""" + + def make_sound(self) -> str: + return "Tweet!" + + +# Class with metaclass +class SingletonMeta(type): + """Metaclass for singleton pattern.""" + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] + + +class DatabaseConnection(metaclass=SingletonMeta): + """Singleton database connection.""" + + def __init__(self, connection_string: str): + self.connection_string = connection_string + self._connected = False + + def connect(self): + """Connect to database.""" + self._connected = True + + def disconnect(self): + """Disconnect from database.""" + self._connected = False + + +# Dataclass +@dataclass +class Point: + """Point in 2D space.""" + x: float + y: float + label: str = "origin" + metadata: Dict[str, any] = field(default_factory=dict) + + def distance_from_origin(self) -> float: + """Calculate distance from origin.""" + return (self.x ** 2 + self.y ** 2) ** 0.5 + + +# Generic class +class Stack(Generic[T]): + """Generic stack implementation.""" + + def __init__(self): + self._items: List[T] = [] + + def push(self, item: T) -> None: + """Push item onto stack.""" + self._items.append(item) + + def pop(self) -> Optional[T]: + """Pop item from stack.""" + if self._items: + return self._items.pop() + return None + + def __len__(self) -> int: + return len(self._items) + + +# Nested class +class Outer: + """Outer class with nested inner class.""" + + class Inner: + """Inner nested class.""" + + def inner_method(self) -> str: + return "Inner method" + + def outer_method(self) -> str: + inner = self.Inner() + return f"Outer method calling {inner.inner_method()}" + + +# Context manager +class FileManager: + """Context manager for file operations.""" + + def __init__(self, filename: str, mode: str = 'r'): + self.filename = filename + self.mode = mode + self.file = None + + def __enter__(self): + self.file = open(self.filename, self.mode) + return self.file + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.file: + self.file.close() + + +# Class with operators +class Vector: + """Vector class with operator overloading.""" + + def __init__(self, x: float, y: float): + self.x = x + self.y = y + + def __add__(self, other: 'Vector') -> 'Vector': + return Vector(self.x + other.x, self.y + other.y) + + def __mul__(self, scalar: float) -> 'Vector': + return Vector(self.x * scalar, self.y * scalar) + + def __repr__(self) -> str: + return f"Vector({self.x}, {self.y})" + + +# Enum-like class +class Color: + """Color enumeration.""" + RED = "red" + GREEN = "green" + BLUE = "blue" + + _ALL_COLORS = [RED, GREEN, BLUE] + + @classmethod + def is_valid(cls, color: str) -> bool: + """Check if color is valid.""" + return color in cls._ALL_COLORS + + +# Configuration class +class Config: + """Application configuration.""" + DEBUG: bool = False + DATABASE_URL: str = "sqlite:///app.db" + SECRET_KEY: str = os.environ.get('SECRET_KEY', 'dev-key') + MAX_CONNECTIONS: int = 100 + + _instance = None + __slots__ = ['debug', 'db_url'] + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + +# Complex function with nested functions +def data_processor(raw_data: List[dict]) -> Dict[str, any]: + """Process raw data with nested helper functions.""" + + def validate_item(item: dict) -> bool: + """Validate single item.""" + return 'id' in item and 'value' in item + + def transform_item(item: dict) -> dict: + """Transform single item.""" + return { + 'id': item['id'], + 'processed_value': item['value'] * 2, + 'timestamp': datetime.now() + } + + # List comprehension + valid_items = [item for item in raw_data if validate_item(item)] + + # Dictionary comprehension + processed = { + str(item['id']): transform_item(item) + for item in valid_items + } + + # Generator expression + total = sum(item['value'] for item in valid_items) + + return { + 'processed': processed, + 'total': total, + 'count': len(valid_items) + } + + +# Exception classes +class CustomError(Exception): + """Base custom exception.""" + pass + + +class ValidationError(CustomError): + """Validation error exception.""" + + def __init__(self, field: str, message: str): + self.field = field + super().__init__(f"Validation error in {field}: {message}") + + +# Module-level code +if __name__ == "__main__": + # Test code + print(f"Testing {DEFAULT_NAME}") + + # Create instances + dog = Dog("Buddy", "Golden Retriever") + print(dog.make_sound()) + + # Test async + async def main(): + data = await async_fetch(API_ENDPOINT) + print(data) + + # Run async code asyncio.run(main()) \ No newline at end of file diff --git a/test/manual/test-parser-simple.js b/test/manual/test-parser-simple.js index 3ac6391..438a6bd 100644 --- a/test/manual/test-parser-simple.js +++ b/test/manual/test-parser-simple.js @@ -1,45 +1,45 @@ -#!/usr/bin/env node - -// Simple test for tree-sitter Python parser -const Parser = require('tree-sitter'); -const Python = require('tree-sitter-python'); - -// Create parser -const parser = new Parser(); -parser.setLanguage(Python); - -// Simple Python code -const code = ` -def hello(name: str) -> str: - return f"Hello, {name}!" - -class Person: - def __init__(self, name: str): - self.name = name -`; - -// Parse the code -const tree = parser.parse(code); - -console.log('Tree-sitter Python parser test:'); -console.log('================================'); -console.log('Root node type:', tree.rootNode.type); -console.log('Has errors:', tree.rootNode.hasError); -console.log('Child count:', tree.rootNode.childCount); -console.log(''); - -// Walk the tree -function walkTree(node, indent = 0) { - const prefix = ' '.repeat(indent); - console.log(`${prefix}${node.type} [${node.startPosition.row}:${node.startPosition.column}-${node.endPosition.row}:${node.endPosition.column}]`); - - for (let i = 0; i < node.childCount; i++) { - walkTree(node.child(i), indent + 2); - } -} - -console.log('Parse tree:'); -console.log('-----------'); -walkTree(tree.rootNode); - +#!/usr/bin/env node + +// Simple test for tree-sitter Python parser +const Parser = require('tree-sitter'); +const Python = require('tree-sitter-python'); + +// Create parser +const parser = new Parser(); +parser.setLanguage(Python); + +// Simple Python code +const code = ` +def hello(name: str) -> str: + return f"Hello, {name}!" + +class Person: + def __init__(self, name: str): + self.name = name +`; + +// Parse the code +const tree = parser.parse(code); + +console.log('Tree-sitter Python parser test:'); +console.log('================================'); +console.log('Root node type:', tree.rootNode.type); +console.log('Has errors:', tree.rootNode.hasError); +console.log('Child count:', tree.rootNode.childCount); +console.log(''); + +// Walk the tree +function walkTree(node, indent = 0) { + const prefix = ' '.repeat(indent); + console.log(`${prefix}${node.type} [${node.startPosition.row}:${node.startPosition.column}-${node.endPosition.row}:${node.endPosition.column}]`); + + for (let i = 0; i < node.childCount; i++) { + walkTree(node.child(i), indent + 2); + } +} + +console.log('Parse tree:'); +console.log('-----------'); +walkTree(tree.rootNode); + console.log('\n✅ Tree-sitter is working correctly!'); \ No newline at end of file diff --git a/test/manual/test-python-parser.js b/test/manual/test-python-parser.js index 0dd9ab1..2a6545f 100644 --- a/test/manual/test-python-parser.js +++ b/test/manual/test-python-parser.js @@ -1,63 +1,63 @@ -#!/usr/bin/env node - -const { PythonTreeSitterParser } = require('../../dist/parsers/python-tree-sitter'); -const fs = require('fs'); -const path = require('path'); - -// Create parser instance -const parser = new PythonTreeSitterParser(); - -// Read test file -const testFile = path.join(__dirname, '../fixtures/complex_python.py'); -const content = fs.readFileSync(testFile, 'utf8'); - -// Parse the file -console.log('Parsing Python file with tree-sitter...\n'); -const result = parser.parse(content, testFile); - -// Display results -console.log('=== PARSING RESULTS ===\n'); -console.log(`Imports: ${result.imports.length}`); -console.log(`Functions: ${result.functions.length}`); -console.log(`Classes: ${result.classes.length}`); -console.log(`Constants: ${result.constants.length}`); -console.log(`Exports: ${result.exports.length}`); -console.log(`Dependencies: ${result.dependencies.length}`); -console.log(`Errors: ${result.errors.length}`); - -// Show some details -console.log('\n=== FUNCTIONS ==='); -result.functions.slice(0, 5).forEach(func => { - console.log(`- ${func.name}(${func.parameters.map(p => p.name).join(', ')}) -> ${func.returnType}`); - if (func.async) console.log(' [async]'); - if (func.generator) console.log(' [generator]'); - if (func.decorators && func.decorators.length > 0) { - console.log(` Decorators: ${func.decorators.join(', ')}`); - } -}); - -console.log('\n=== CLASSES ==='); -result.classes.slice(0, 5).forEach(cls => { - console.log(`- ${cls.name}`); - if (cls.extends) console.log(` extends: ${cls.extends}`); - if (cls.implements && cls.implements.length > 0) { - console.log(` implements: ${cls.implements.join(', ')}`); - } - if (cls.decorators && cls.decorators.length > 0) { - console.log(` Decorators: ${cls.decorators.join(', ')}`); - } - console.log(` Methods: ${cls.methods.length}`); - console.log(` Properties: ${cls.properties.length}`); -}); - -console.log('\n=== IMPORTS ==='); -result.imports.slice(0, 5).forEach(imp => { - console.log(`- from ${imp.source} import ${imp.specifiers.map(s => s.name).join(', ')}`); -}); - -if (result.errors.length > 0) { - console.log('\n=== ERRORS ==='); - result.errors.forEach(err => console.log(`- ${err}`)); -} - +#!/usr/bin/env node + +const { PythonTreeSitterParser } = require('../../dist/parsers/python-tree-sitter'); +const fs = require('fs'); +const path = require('path'); + +// Create parser instance +const parser = new PythonTreeSitterParser(); + +// Read test file +const testFile = path.join(__dirname, '../fixtures/complex_python.py'); +const content = fs.readFileSync(testFile, 'utf8'); + +// Parse the file +console.log('Parsing Python file with tree-sitter...\n'); +const result = parser.parse(content, testFile); + +// Display results +console.log('=== PARSING RESULTS ===\n'); +console.log(`Imports: ${result.imports.length}`); +console.log(`Functions: ${result.functions.length}`); +console.log(`Classes: ${result.classes.length}`); +console.log(`Constants: ${result.constants.length}`); +console.log(`Exports: ${result.exports.length}`); +console.log(`Dependencies: ${result.dependencies.length}`); +console.log(`Errors: ${result.errors.length}`); + +// Show some details +console.log('\n=== FUNCTIONS ==='); +result.functions.slice(0, 5).forEach(func => { + console.log(`- ${func.name}(${func.parameters.map(p => p.name).join(', ')}) -> ${func.returnType}`); + if (func.async) console.log(' [async]'); + if (func.generator) console.log(' [generator]'); + if (func.decorators && func.decorators.length > 0) { + console.log(` Decorators: ${func.decorators.join(', ')}`); + } +}); + +console.log('\n=== CLASSES ==='); +result.classes.slice(0, 5).forEach(cls => { + console.log(`- ${cls.name}`); + if (cls.extends) console.log(` extends: ${cls.extends}`); + if (cls.implements && cls.implements.length > 0) { + console.log(` implements: ${cls.implements.join(', ')}`); + } + if (cls.decorators && cls.decorators.length > 0) { + console.log(` Decorators: ${cls.decorators.join(', ')}`); + } + console.log(` Methods: ${cls.methods.length}`); + console.log(` Properties: ${cls.properties.length}`); +}); + +console.log('\n=== IMPORTS ==='); +result.imports.slice(0, 5).forEach(imp => { + console.log(`- from ${imp.source} import ${imp.specifiers.map(s => s.name).join(', ')}`); +}); + +if (result.errors.length > 0) { + console.log('\n=== ERRORS ==='); + result.errors.forEach(err => console.log(`- ${err}`)); +} + console.log('\n✅ Tree-sitter Python parser test complete!'); \ No newline at end of file diff --git a/test/parsers/javascript.test.ts b/test/parsers/javascript.test.ts index f75a769..ff62f98 100644 --- a/test/parsers/javascript.test.ts +++ b/test/parsers/javascript.test.ts @@ -1,278 +1,278 @@ -import { JavaScriptParser } from '../../src/parsers/javascript'; - -describe('JavaScriptParser', () => { - let parser: JavaScriptParser; - - beforeEach(() => { - parser = new JavaScriptParser(); - }); - - describe('basic parsing', () => { - it('should parse simple function declarations', () => { - const content = ` - function hello(name) { - return 'Hello, ' + name; - } - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.functions).toHaveLength(1); - expect(result.functions[0].name).toBe('hello'); - expect(result.functions[0].parameters).toHaveLength(1); - expect(result.functions[0].parameters[0].name).toBe('name'); - }); - - it('should parse arrow functions', () => { - const content = ` - const add = (a, b) => a + b; - const multiply = (x, y) => { - return x * y; - }; - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.functions).toHaveLength(2); - expect(result.functions).toContainFunction('add'); - expect(result.functions).toContainFunction('multiply'); - }); - - it('should parse async functions', () => { - const content = ` - async function fetchData() { - const response = await fetch('/api/data'); - return response.json(); - } - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.functions).toHaveLength(1); - expect(result.functions[0].async).toBe(true); - expect(result.functions[0].generator).toBe(false); - }); - - it('should parse generator functions', () => { - const content = ` - function* fibonacci() { - let a = 0, b = 1; - while (true) { - yield a; - [a, b] = [b, a + b]; - } - } - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.functions).toHaveLength(1); - expect(result.functions[0].generator).toBe(true); - }); - }); - - describe('class parsing', () => { - it('should parse class declarations', () => { - const content = ` - class Person { - constructor(name, age) { - this.name = name; - this.age = age; - } - - greet() { - return \`Hello, I'm \${this.name}\`; - } - - static createAnonymous() { - return new Person('Anonymous', 0); - } - } - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.classes).toHaveLength(1); - expect(result.classes[0].name).toBe('Person'); - expect(result.classes[0].methods).toHaveLength(3); - expect(result.classes[0].methods.find(m => m.name === 'createAnonymous')?.static).toBe(true); - }); - - it('should parse class inheritance', () => { - const content = ` - class Animal { - speak() { - return 'Some sound'; - } - } - - class Dog extends Animal { - speak() { - return 'Woof!'; - } - } - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.classes).toHaveLength(2); - expect(result.classes).toContainClass('Animal'); - expect(result.classes).toContainClass('Dog'); - expect(result.classes[1].extends).toBe('Animal'); - }); - - it('should parse class properties', () => { - const content = ` - class Config { - static VERSION = '1.0.0'; - debug = false; - maxRetries = 3; - } - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.classes).toHaveLength(1); - expect(result.classes[0].properties).toHaveLength(3); - expect(result.classes[0].properties.find(p => p.name === 'VERSION')?.static).toBe(true); - }); - }); - - describe('import/export parsing', () => { - it('should parse ES6 imports', () => { - const content = ` - import React from 'react'; - import { useState, useEffect } from 'react'; - import * as utils from './utils'; - import './styles.css'; - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.imports).toHaveLength(4); - expect(result.imports[0].source).toBe('react'); - expect(result.imports[1].specifiers).toHaveLength(2); - expect(result.imports[2].specifiers[0].name).toBe('*'); - expect(result.dependencies).toContain('./utils'); - expect(result.dependencies).toContain('./styles.css'); - }); - - it('should parse CommonJS requires', () => { - const content = ` - const fs = require('fs'); - const path = require('path'); - const config = require('./config.json'); - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.imports).toHaveLength(3); - expect(result.imports[0].type).toBe('commonjs'); - expect(result.dependencies).toContain('./config.json'); - }); - - it('should parse dynamic imports', () => { - const content = ` - async function loadModule() { - const module = await import('./dynamic-module'); - return module; - } - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.imports).toHaveLength(1); - expect(result.imports[0].type).toBe('dynamic'); - expect(result.imports[0].source).toBe('./dynamic-module'); - }); - - it('should parse exports', () => { - const content = ` - export const API_KEY = 'secret'; - export function authenticate() {} - export class User {} - export default function main() {} - export { helper } from './helpers'; - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.exports).toHaveLength(5); - expect(result.exports.find(e => e.name === 'API_KEY')?.type).toBe('variable'); - expect(result.exports.find(e => e.name === 'authenticate')?.type).toBe('function'); - expect(result.exports.find(e => e.name === 'User')?.type).toBe('class'); - expect(result.exports.find(e => e.name === 'default')?.type).toBe('default'); - }); - }); - - describe('constant parsing', () => { - it('should parse const declarations', () => { - const content = ` - const PI = 3.14159; - const CONFIG = { - apiUrl: 'https://api.example.com', - timeout: 5000 - }; - const COLORS = ['red', 'green', 'blue']; - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.constants).toHaveLength(3); - expect(result.constants.find(c => c.name === 'PI')?.value).toBe('3.14159'); - }); - }); - - describe('error handling', () => { - it('should handle syntax errors gracefully', () => { - const content = ` - function broken( { - return 'missing closing paren'; - } - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.errors).toHaveLength(1); - expect(result.errors[0]).toContain('Parse error'); - }); - - it('should handle JSX syntax', () => { - const content = ` - function Component() { - return
Hello World
; - } - `; - - const result = parser.parse(content, 'test.jsx'); - - expect(result.functions).toHaveLength(1); - expect(result.errors).toHaveLength(0); - }); - }); - - describe('line number tracking', () => { - it('should track function line numbers', () => { - const content = ` - // Line 1 - // Line 2 - function firstFunction() { - return 1; - } - - // Line 7 - function secondFunction() { - return 2; - } - `; - - const result = parser.parse(content, 'test.js'); - - expect(result.functions).toHaveLength(2); - expect(result.functions[0].startLine).toBe(3); - expect(result.functions[0].endLine).toBe(5); - expect(result.functions[1].startLine).toBe(8); - expect(result.functions[1].endLine).toBe(10); - }); - }); +import { JavaScriptParser } from '../../src/parsers/javascript'; + +describe('JavaScriptParser', () => { + let parser: JavaScriptParser; + + beforeEach(() => { + parser = new JavaScriptParser(); + }); + + describe('basic parsing', () => { + it('should parse simple function declarations', () => { + const content = ` + function hello(name) { + return 'Hello, ' + name; + } + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.functions).toHaveLength(1); + expect(result.functions[0].name).toBe('hello'); + expect(result.functions[0].parameters).toHaveLength(1); + expect(result.functions[0].parameters[0].name).toBe('name'); + }); + + it('should parse arrow functions', () => { + const content = ` + const add = (a, b) => a + b; + const multiply = (x, y) => { + return x * y; + }; + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.functions).toHaveLength(2); + expect(result.functions).toContainFunction('add'); + expect(result.functions).toContainFunction('multiply'); + }); + + it('should parse async functions', () => { + const content = ` + async function fetchData() { + const response = await fetch('/api/data'); + return response.json(); + } + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.functions).toHaveLength(1); + expect(result.functions[0].async).toBe(true); + expect(result.functions[0].generator).toBe(false); + }); + + it('should parse generator functions', () => { + const content = ` + function* fibonacci() { + let a = 0, b = 1; + while (true) { + yield a; + [a, b] = [b, a + b]; + } + } + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.functions).toHaveLength(1); + expect(result.functions[0].generator).toBe(true); + }); + }); + + describe('class parsing', () => { + it('should parse class declarations', () => { + const content = ` + class Person { + constructor(name, age) { + this.name = name; + this.age = age; + } + + greet() { + return \`Hello, I'm \${this.name}\`; + } + + static createAnonymous() { + return new Person('Anonymous', 0); + } + } + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.classes).toHaveLength(1); + expect(result.classes[0].name).toBe('Person'); + expect(result.classes[0].methods).toHaveLength(3); + expect(result.classes[0].methods.find(m => m.name === 'createAnonymous')?.static).toBe(true); + }); + + it('should parse class inheritance', () => { + const content = ` + class Animal { + speak() { + return 'Some sound'; + } + } + + class Dog extends Animal { + speak() { + return 'Woof!'; + } + } + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.classes).toHaveLength(2); + expect(result.classes).toContainClass('Animal'); + expect(result.classes).toContainClass('Dog'); + expect(result.classes[1].extends).toBe('Animal'); + }); + + it('should parse class properties', () => { + const content = ` + class Config { + static VERSION = '1.0.0'; + debug = false; + maxRetries = 3; + } + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.classes).toHaveLength(1); + expect(result.classes[0].properties).toHaveLength(3); + expect(result.classes[0].properties.find(p => p.name === 'VERSION')?.static).toBe(true); + }); + }); + + describe('import/export parsing', () => { + it('should parse ES6 imports', () => { + const content = ` + import React from 'react'; + import { useState, useEffect } from 'react'; + import * as utils from './utils'; + import './styles.css'; + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.imports).toHaveLength(4); + expect(result.imports[0].source).toBe('react'); + expect(result.imports[1].specifiers).toHaveLength(2); + expect(result.imports[2].specifiers[0].name).toBe('*'); + expect(result.dependencies).toContain('./utils'); + expect(result.dependencies).toContain('./styles.css'); + }); + + it('should parse CommonJS requires', () => { + const content = ` + const fs = require('fs'); + const path = require('path'); + const config = require('./config.json'); + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.imports).toHaveLength(3); + expect(result.imports[0].type).toBe('commonjs'); + expect(result.dependencies).toContain('./config.json'); + }); + + it('should parse dynamic imports', () => { + const content = ` + async function loadModule() { + const module = await import('./dynamic-module'); + return module; + } + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.imports).toHaveLength(1); + expect(result.imports[0].type).toBe('dynamic'); + expect(result.imports[0].source).toBe('./dynamic-module'); + }); + + it('should parse exports', () => { + const content = ` + export const API_KEY = 'secret'; + export function authenticate() {} + export class User {} + export default function main() {} + export { helper } from './helpers'; + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.exports).toHaveLength(5); + expect(result.exports.find(e => e.name === 'API_KEY')?.type).toBe('variable'); + expect(result.exports.find(e => e.name === 'authenticate')?.type).toBe('function'); + expect(result.exports.find(e => e.name === 'User')?.type).toBe('class'); + expect(result.exports.find(e => e.name === 'default')?.type).toBe('default'); + }); + }); + + describe('constant parsing', () => { + it('should parse const declarations', () => { + const content = ` + const PI = 3.14159; + const CONFIG = { + apiUrl: 'https://api.example.com', + timeout: 5000 + }; + const COLORS = ['red', 'green', 'blue']; + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.constants).toHaveLength(3); + expect(result.constants.find(c => c.name === 'PI')?.value).toBe('3.14159'); + }); + }); + + describe('error handling', () => { + it('should handle syntax errors gracefully', () => { + const content = ` + function broken( { + return 'missing closing paren'; + } + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.errors).toHaveLength(1); + expect(result.errors[0]).toContain('Parse error'); + }); + + it('should handle JSX syntax', () => { + const content = ` + function Component() { + return
Hello World
; + } + `; + + const result = parser.parse(content, 'test.jsx'); + + expect(result.functions).toHaveLength(1); + expect(result.errors).toHaveLength(0); + }); + }); + + describe('line number tracking', () => { + it('should track function line numbers', () => { + const content = ` + // Line 1 + // Line 2 + function firstFunction() { + return 1; + } + + // Line 7 + function secondFunction() { + return 2; + } + `; + + const result = parser.parse(content, 'test.js'); + + expect(result.functions).toHaveLength(2); + expect(result.functions[0].startLine).toBe(3); + expect(result.functions[0].endLine).toBe(5); + expect(result.functions[1].startLine).toBe(8); + expect(result.functions[1].endLine).toBe(10); + }); + }); }); \ No newline at end of file diff --git a/test/parsers/python.test.ts b/test/parsers/python.test.ts index 6a90532..f3aa81b 100644 --- a/test/parsers/python.test.ts +++ b/test/parsers/python.test.ts @@ -1,457 +1,457 @@ -import { PythonParser } from '../../src/parsers/python'; -import * as fs from 'fs'; - -describe('PythonParser', () => { - let parser: PythonParser; - - beforeEach(() => { - parser = new PythonParser(); - }); - - describe('parse', () => { - it('should parse simple Python functions', () => { - const content = ` -def hello_world(): - print("Hello, World!") - return True - -def add_numbers(a, b): - """Add two numbers together.""" - return a + b -`; - - const result = parser.parse(content, 'test.py'); - - expect(result.functions).toHaveLength(2); - expect(result.functions[0]).toMatchObject({ - name: 'hello_world', - startLine: expect.any(Number), - endLine: expect.any(Number), - params: [] - }); - expect(result.functions[1]).toMatchObject({ - name: 'add_numbers', - params: ['a', 'b'], - docstring: 'Add two numbers together.' - }); - }); - - it('should parse Python classes with methods', () => { - const content = ` -class Calculator: - """A simple calculator class.""" - - def __init__(self, initial_value=0): - self.value = initial_value - - def add(self, number): - """Add a number to the current value.""" - self.value += number - return self.value - - @property - def current_value(self): - return self.value - - @staticmethod - def multiply(a, b): - return a * b - - @classmethod - def from_string(cls, value_str): - return cls(int(value_str)) -`; - - const result = parser.parse(content, 'calculator.py'); - - expect(result.classes).toHaveLength(1); - expect(result.classes[0]).toMatchObject({ - name: 'Calculator', - docstring: 'A simple calculator class.', - methods: expect.arrayContaining([ - expect.objectContaining({ name: '__init__' }), - expect.objectContaining({ name: 'add' }), - expect.objectContaining({ name: 'current_value' }), - expect.objectContaining({ name: 'multiply' }), - expect.objectContaining({ name: 'from_string' }) - ]) - }); - }); - - it('should detect decorators', () => { - const content = ` -@app.route('/api/users') -@require_auth -@cache(timeout=300) -def get_users(): - return jsonify(users) - -class APIView: - @login_required - @permission_required('admin') - def admin_panel(self): - pass -`; - - const result = parser.parse(content, 'api.py'); - - expect(result.functions[0].decorators).toEqual([ - '@app.route', - '@require_auth', - '@cache' - ]); - expect(result.classes[0].methods[0].decorators).toEqual([ - '@login_required', - '@permission_required' - ]); - }); - - it('should parse imports correctly', () => { - const content = ` -import os -import sys -from datetime import datetime, timedelta -from typing import List, Dict, Optional -import numpy as np -from flask import Flask, jsonify, request as req -from ..utils import helper_function -from . import local_module -import package.submodule.module as alias -`; - - const result = parser.parse(content, 'imports.py'); - - expect(result.imports).toContain('os'); - expect(result.imports).toContain('sys'); - expect(result.imports).toContain('datetime'); - expect(result.imports).toContain('typing'); - expect(result.imports).toContain('numpy'); - expect(result.imports).toContain('flask'); - expect(result.imports).toContain('..utils'); - expect(result.imports).toContain('package.submodule.module'); - }); - - it('should parse exports and __all__', () => { - const content = ` -__all__ = ['public_function', 'PublicClass', 'CONSTANT'] - -def public_function(): - pass - -def _private_function(): - pass - -class PublicClass: - pass - -class _PrivateClass: - pass - -CONSTANT = 42 -`; - - const result = parser.parse(content, 'module.py'); - - expect(result.exports).toEqual(['public_function', 'PublicClass', 'CONSTANT']); - }); - - it('should handle async functions and methods', () => { - const content = ` -async def fetch_data(url): - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - return await response.json() - -class AsyncHandler: - async def process(self, data): - result = await self.transform(data) - return result - - async def transform(self, data): - await asyncio.sleep(1) - return data.upper() -`; - - const result = parser.parse(content, 'async.py'); - - expect(result.functions[0]).toMatchObject({ - name: 'fetch_data', - isAsync: true - }); - expect(result.classes[0].methods.every(m => m.isAsync)).toBe(true); - }); - - it('should parse type hints', () => { - const content = ` -def typed_function( - name: str, - age: int, - scores: List[float], - metadata: Optional[Dict[str, Any]] = None -) -> Tuple[str, bool]: - return (name, True) - -class TypedClass: - name: str - value: int = 0 - items: List[str] = field(default_factory=list) - - def process(self, data: bytes) -> str: - return data.decode() -`; - - const result = parser.parse(content, 'typed.py'); - - expect(result.functions[0].typeHints).toBeDefined(); - expect(result.functions[0].typeHints.params).toMatchObject({ - name: 'str', - age: 'int', - scores: 'List[float]', - metadata: 'Optional[Dict[str, Any]]' - }); - expect(result.functions[0].typeHints.returns).toBe('Tuple[str, bool]'); - }); - - it('should handle nested classes and functions', () => { - const content = ` -class Outer: - class Inner: - class DeepNested: - def deep_method(self): - pass - - def inner_method(self): - def local_function(): - pass - return local_function - - def outer_method(self): - pass -`; - - const result = parser.parse(content, 'nested.py'); - - expect(result.classes).toHaveLength(3); // Outer, Inner, DeepNested - expect(result.classes.find(c => c.name === 'Outer')).toBeDefined(); - expect(result.classes.find(c => c.name === 'Inner')).toBeDefined(); - expect(result.classes.find(c => c.name === 'DeepNested')).toBeDefined(); - }); - - it('should detect metaclasses and inheritance', () => { - const content = ` -class Meta(type): - def __new__(cls, name, bases, attrs): - return super().__new__(cls, name, bases, attrs) - -class Base: - pass - -class Child(Base, metaclass=Meta): - pass - -class Multiple(Base, AnotherBase, ThirdBase): - pass -`; - - const result = parser.parse(content, 'inheritance.py'); - - expect(result.classes.find(c => c.name === 'Child')).toMatchObject({ - name: 'Child', - extends: ['Base'], - metaclass: 'Meta' - }); - expect(result.classes.find(c => c.name === 'Multiple').extends).toEqual([ - 'Base', - 'AnotherBase', - 'ThirdBase' - ]); - }); - - it('should handle generator functions and comprehensions', () => { - const content = ` -def fibonacci(n): - a, b = 0, 1 - while a < n: - yield a - a, b = b, a + b - -squares = [x**2 for x in range(10)] -even_squares = [x**2 for x in range(10) if x % 2 == 0] -matrix = [[i*j for i in range(3)] for j in range(3)] -dict_comp = {k: v**2 for k, v in enumerate(range(5))} -set_comp = {x for x in range(10) if x % 2} -gen_exp = (x**2 for x in range(10)) -`; - - const result = parser.parse(content, 'generators.py'); - - expect(result.functions[0]).toMatchObject({ - name: 'fibonacci', - isGenerator: true - }); - expect(result.variables).toContain('squares'); - expect(result.variables).toContain('even_squares'); - }); - - it('should parse exception handling', () => { - const content = ` -def safe_divide(a, b): - try: - result = a / b - except ZeroDivisionError as e: - print(f"Error: {e}") - return None - except (TypeError, ValueError) as e: - raise CustomError(f"Invalid input: {e}") - else: - return result - finally: - print("Division attempted") - -class CustomError(Exception): - pass -`; - - const result = parser.parse(content, 'exceptions.py'); - - expect(result.functions[0].name).toBe('safe_divide'); - expect(result.classes[0]).toMatchObject({ - name: 'CustomError', - extends: ['Exception'] - }); - }); - - it('should handle context managers', () => { - const content = ` -class FileManager: - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - -with open('file.txt') as f: - content = f.read() - -async with aiofiles.open('async.txt') as f: - content = await f.read() -`; - - const result = parser.parse(content, 'context.py'); - - expect(result.classes[0].methods).toEqual( - expect.arrayContaining([ - expect.objectContaining({ name: '__enter__' }), - expect.objectContaining({ name: '__exit__' }) - ]) - ); - }); - - it('should calculate complexity metrics', () => { - const content = ` -def complex_function(x, y, z): - if x > 0: - if y > 0: - if z > 0: - return x + y + z - elif z < 0: - return x + y - z - else: - return x + y - else: - for i in range(10): - if i % 2 == 0: - x += i - elif x < 0: - while y > 0: - y -= 1 - if y % 3 == 0: - continue - else: - try: - return 1 / y - except: - return 0 - return x -`; - - const result = parser.parse(content, 'complex.py'); - - expect(result.complexity).toBeGreaterThan(10); - expect(result.functions[0].complexity).toBeGreaterThan(10); - }); - - it('should handle dataclasses and enums', () => { - const content = ` -from dataclasses import dataclass, field -from enum import Enum, auto - -@dataclass -class Person: - name: str - age: int - email: str = "" - tags: List[str] = field(default_factory=list) - - def greet(self): - return f"Hello, I'm {self.name}" - -class Status(Enum): - PENDING = "pending" - APPROVED = "approved" - REJECTED = "rejected" - -class Color(Enum): - RED = auto() - GREEN = auto() - BLUE = auto() -`; - - const result = parser.parse(content, 'dataclass.py'); - - expect(result.classes.find(c => c.name === 'Person')).toMatchObject({ - name: 'Person', - decorators: ['@dataclass'], - attributes: expect.arrayContaining([ - expect.objectContaining({ name: 'name', type: 'str' }), - expect.objectContaining({ name: 'age', type: 'int' }), - expect.objectContaining({ name: 'email', type: 'str' }), - expect.objectContaining({ name: 'tags', type: 'List[str]' }) - ]) - }); - expect(result.classes.find(c => c.name === 'Status')).toMatchObject({ - name: 'Status', - extends: ['Enum'] - }); - }); - - it('should handle empty file', () => { - const content = ''; - const result = parser.parse(content, 'empty.py'); - - expect(result).toMatchObject({ - functions: [], - classes: [], - imports: [], - exports: [], - language: 'Python', - linesOfCode: 0 - }); - }); - - it('should handle syntax errors gracefully', () => { - const content = ` -def broken_function( - print("This is broken" - -class IncompleteClass - def method(self): -`; - - expect(() => parser.parse(content, 'broken.py')).not.toThrow(); - const result = parser.parse(content, 'broken.py'); - expect(result.parseErrors).toBeDefined(); - expect(result.parseErrors.length).toBeGreaterThan(0); - }); - }); +import { PythonParser } from '../../src/parsers/python'; +import * as fs from 'fs'; + +describe('PythonParser', () => { + let parser: PythonParser; + + beforeEach(() => { + parser = new PythonParser(); + }); + + describe('parse', () => { + it('should parse simple Python functions', () => { + const content = ` +def hello_world(): + print("Hello, World!") + return True + +def add_numbers(a, b): + """Add two numbers together.""" + return a + b +`; + + const result = parser.parse(content, 'test.py'); + + expect(result.functions).toHaveLength(2); + expect(result.functions[0]).toMatchObject({ + name: 'hello_world', + startLine: expect.any(Number), + endLine: expect.any(Number), + params: [] + }); + expect(result.functions[1]).toMatchObject({ + name: 'add_numbers', + params: ['a', 'b'], + docstring: 'Add two numbers together.' + }); + }); + + it('should parse Python classes with methods', () => { + const content = ` +class Calculator: + """A simple calculator class.""" + + def __init__(self, initial_value=0): + self.value = initial_value + + def add(self, number): + """Add a number to the current value.""" + self.value += number + return self.value + + @property + def current_value(self): + return self.value + + @staticmethod + def multiply(a, b): + return a * b + + @classmethod + def from_string(cls, value_str): + return cls(int(value_str)) +`; + + const result = parser.parse(content, 'calculator.py'); + + expect(result.classes).toHaveLength(1); + expect(result.classes[0]).toMatchObject({ + name: 'Calculator', + docstring: 'A simple calculator class.', + methods: expect.arrayContaining([ + expect.objectContaining({ name: '__init__' }), + expect.objectContaining({ name: 'add' }), + expect.objectContaining({ name: 'current_value' }), + expect.objectContaining({ name: 'multiply' }), + expect.objectContaining({ name: 'from_string' }) + ]) + }); + }); + + it('should detect decorators', () => { + const content = ` +@app.route('/api/users') +@require_auth +@cache(timeout=300) +def get_users(): + return jsonify(users) + +class APIView: + @login_required + @permission_required('admin') + def admin_panel(self): + pass +`; + + const result = parser.parse(content, 'api.py'); + + expect(result.functions[0].decorators).toEqual([ + '@app.route', + '@require_auth', + '@cache' + ]); + expect(result.classes[0].methods[0].decorators).toEqual([ + '@login_required', + '@permission_required' + ]); + }); + + it('should parse imports correctly', () => { + const content = ` +import os +import sys +from datetime import datetime, timedelta +from typing import List, Dict, Optional +import numpy as np +from flask import Flask, jsonify, request as req +from ..utils import helper_function +from . import local_module +import package.submodule.module as alias +`; + + const result = parser.parse(content, 'imports.py'); + + expect(result.imports).toContain('os'); + expect(result.imports).toContain('sys'); + expect(result.imports).toContain('datetime'); + expect(result.imports).toContain('typing'); + expect(result.imports).toContain('numpy'); + expect(result.imports).toContain('flask'); + expect(result.imports).toContain('..utils'); + expect(result.imports).toContain('package.submodule.module'); + }); + + it('should parse exports and __all__', () => { + const content = ` +__all__ = ['public_function', 'PublicClass', 'CONSTANT'] + +def public_function(): + pass + +def _private_function(): + pass + +class PublicClass: + pass + +class _PrivateClass: + pass + +CONSTANT = 42 +`; + + const result = parser.parse(content, 'module.py'); + + expect(result.exports).toEqual(['public_function', 'PublicClass', 'CONSTANT']); + }); + + it('should handle async functions and methods', () => { + const content = ` +async def fetch_data(url): + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + return await response.json() + +class AsyncHandler: + async def process(self, data): + result = await self.transform(data) + return result + + async def transform(self, data): + await asyncio.sleep(1) + return data.upper() +`; + + const result = parser.parse(content, 'async.py'); + + expect(result.functions[0]).toMatchObject({ + name: 'fetch_data', + isAsync: true + }); + expect(result.classes[0].methods.every(m => m.isAsync)).toBe(true); + }); + + it('should parse type hints', () => { + const content = ` +def typed_function( + name: str, + age: int, + scores: List[float], + metadata: Optional[Dict[str, Any]] = None +) -> Tuple[str, bool]: + return (name, True) + +class TypedClass: + name: str + value: int = 0 + items: List[str] = field(default_factory=list) + + def process(self, data: bytes) -> str: + return data.decode() +`; + + const result = parser.parse(content, 'typed.py'); + + expect(result.functions[0].typeHints).toBeDefined(); + expect(result.functions[0].typeHints.params).toMatchObject({ + name: 'str', + age: 'int', + scores: 'List[float]', + metadata: 'Optional[Dict[str, Any]]' + }); + expect(result.functions[0].typeHints.returns).toBe('Tuple[str, bool]'); + }); + + it('should handle nested classes and functions', () => { + const content = ` +class Outer: + class Inner: + class DeepNested: + def deep_method(self): + pass + + def inner_method(self): + def local_function(): + pass + return local_function + + def outer_method(self): + pass +`; + + const result = parser.parse(content, 'nested.py'); + + expect(result.classes).toHaveLength(3); // Outer, Inner, DeepNested + expect(result.classes.find(c => c.name === 'Outer')).toBeDefined(); + expect(result.classes.find(c => c.name === 'Inner')).toBeDefined(); + expect(result.classes.find(c => c.name === 'DeepNested')).toBeDefined(); + }); + + it('should detect metaclasses and inheritance', () => { + const content = ` +class Meta(type): + def __new__(cls, name, bases, attrs): + return super().__new__(cls, name, bases, attrs) + +class Base: + pass + +class Child(Base, metaclass=Meta): + pass + +class Multiple(Base, AnotherBase, ThirdBase): + pass +`; + + const result = parser.parse(content, 'inheritance.py'); + + expect(result.classes.find(c => c.name === 'Child')).toMatchObject({ + name: 'Child', + extends: ['Base'], + metaclass: 'Meta' + }); + expect(result.classes.find(c => c.name === 'Multiple').extends).toEqual([ + 'Base', + 'AnotherBase', + 'ThirdBase' + ]); + }); + + it('should handle generator functions and comprehensions', () => { + const content = ` +def fibonacci(n): + a, b = 0, 1 + while a < n: + yield a + a, b = b, a + b + +squares = [x**2 for x in range(10)] +even_squares = [x**2 for x in range(10) if x % 2 == 0] +matrix = [[i*j for i in range(3)] for j in range(3)] +dict_comp = {k: v**2 for k, v in enumerate(range(5))} +set_comp = {x for x in range(10) if x % 2} +gen_exp = (x**2 for x in range(10)) +`; + + const result = parser.parse(content, 'generators.py'); + + expect(result.functions[0]).toMatchObject({ + name: 'fibonacci', + isGenerator: true + }); + expect(result.variables).toContain('squares'); + expect(result.variables).toContain('even_squares'); + }); + + it('should parse exception handling', () => { + const content = ` +def safe_divide(a, b): + try: + result = a / b + except ZeroDivisionError as e: + print(f"Error: {e}") + return None + except (TypeError, ValueError) as e: + raise CustomError(f"Invalid input: {e}") + else: + return result + finally: + print("Division attempted") + +class CustomError(Exception): + pass +`; + + const result = parser.parse(content, 'exceptions.py'); + + expect(result.functions[0].name).toBe('safe_divide'); + expect(result.classes[0]).toMatchObject({ + name: 'CustomError', + extends: ['Exception'] + }); + }); + + it('should handle context managers', () => { + const content = ` +class FileManager: + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + +with open('file.txt') as f: + content = f.read() + +async with aiofiles.open('async.txt') as f: + content = await f.read() +`; + + const result = parser.parse(content, 'context.py'); + + expect(result.classes[0].methods).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: '__enter__' }), + expect.objectContaining({ name: '__exit__' }) + ]) + ); + }); + + it('should calculate complexity metrics', () => { + const content = ` +def complex_function(x, y, z): + if x > 0: + if y > 0: + if z > 0: + return x + y + z + elif z < 0: + return x + y - z + else: + return x + y + else: + for i in range(10): + if i % 2 == 0: + x += i + elif x < 0: + while y > 0: + y -= 1 + if y % 3 == 0: + continue + else: + try: + return 1 / y + except: + return 0 + return x +`; + + const result = parser.parse(content, 'complex.py'); + + expect(result.complexity).toBeGreaterThan(10); + expect(result.functions[0].complexity).toBeGreaterThan(10); + }); + + it('should handle dataclasses and enums', () => { + const content = ` +from dataclasses import dataclass, field +from enum import Enum, auto + +@dataclass +class Person: + name: str + age: int + email: str = "" + tags: List[str] = field(default_factory=list) + + def greet(self): + return f"Hello, I'm {self.name}" + +class Status(Enum): + PENDING = "pending" + APPROVED = "approved" + REJECTED = "rejected" + +class Color(Enum): + RED = auto() + GREEN = auto() + BLUE = auto() +`; + + const result = parser.parse(content, 'dataclass.py'); + + expect(result.classes.find(c => c.name === 'Person')).toMatchObject({ + name: 'Person', + decorators: ['@dataclass'], + attributes: expect.arrayContaining([ + expect.objectContaining({ name: 'name', type: 'str' }), + expect.objectContaining({ name: 'age', type: 'int' }), + expect.objectContaining({ name: 'email', type: 'str' }), + expect.objectContaining({ name: 'tags', type: 'List[str]' }) + ]) + }); + expect(result.classes.find(c => c.name === 'Status')).toMatchObject({ + name: 'Status', + extends: ['Enum'] + }); + }); + + it('should handle empty file', () => { + const content = ''; + const result = parser.parse(content, 'empty.py'); + + expect(result).toMatchObject({ + functions: [], + classes: [], + imports: [], + exports: [], + language: 'Python', + linesOfCode: 0 + }); + }); + + it('should handle syntax errors gracefully', () => { + const content = ` +def broken_function( + print("This is broken" + +class IncompleteClass + def method(self): +`; + + expect(() => parser.parse(content, 'broken.py')).not.toThrow(); + const result = parser.parse(content, 'broken.py'); + expect(result.parseErrors).toBeDefined(); + expect(result.parseErrors.length).toBeGreaterThan(0); + }); + }); }); \ No newline at end of file diff --git a/test/setup.ts b/test/setup.ts index 92846f2..ed543d0 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -1,125 +1,125 @@ -// Test setup file for Jest -import { afterAll, afterEach, beforeEach, expect, jest } from '@jest/globals'; -import * as fs from 'fs'; -import * as path from 'path'; - -// Set up test environment variables -process.env.NODE_ENV = 'test'; -process.env.INDEXER_TEST = 'true'; - -// Create temp directory for tests -const tempDir = path.join(__dirname, '../.test-temp'); -if (!fs.existsSync(tempDir)) { - fs.mkdirSync(tempDir, { recursive: true }); -} - -// Clean up after all tests -afterAll(() => { - // Clean up temp directory - if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - } -}); - -// Global test utilities -global.createTestFile = (filename: string, content: string): string => { - const filePath = path.join(tempDir, filename); - const dir = path.dirname(filePath); - - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - - fs.writeFileSync(filePath, content); - return filePath; -}; - -global.createTestProject = (structure: Record): string => { - const projectDir = path.join(tempDir, `project-${Date.now()}`); - fs.mkdirSync(projectDir, { recursive: true }); - - for (const [filePath, content] of Object.entries(structure)) { - const fullPath = path.join(projectDir, filePath); - const dir = path.dirname(fullPath); - - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - - fs.writeFileSync(fullPath, content); - } - - return projectDir; -}; - -// Mock console methods for cleaner test output -const originalConsoleLog = console.log; -const originalConsoleError = console.error; -const originalConsoleWarn = console.warn; - -beforeEach(() => { - // Suppress console output during tests unless DEBUG is set - if (!process.env.DEBUG) { - console.log = jest.fn(); - console.error = jest.fn(); - console.warn = jest.fn(); - } -}); - -afterEach(() => { - // Restore console methods - console.log = originalConsoleLog; - console.error = originalConsoleError; - console.warn = originalConsoleWarn; -}); - -// Custom matchers -expect.extend({ - toContainFunction(received: any[], functionName: string) { - const pass = received.some(item => item.name === functionName); - - if (pass) { - return { - message: () => `Expected array not to contain function with name "${functionName}"`, - pass: true - }; - } else { - return { - message: () => `Expected array to contain function with name "${functionName}"`, - pass: false - }; - } - }, - - toContainClass(received: any[], className: string) { - const pass = received.some(item => item.name === className); - - if (pass) { - return { - message: () => `Expected array not to contain class with name "${className}"`, - pass: true - }; - } else { - return { - message: () => `Expected array to contain class with name "${className}"`, - pass: false - }; - } - } -}); - -// Type declarations for custom matchers -declare global { - namespace jest { - interface Matchers { - toContainFunction(functionName: string): R; - toContainClass(className: string): R; - } - } - - function createTestFile(filename: string, content: string): string; - function createTestProject(structure: Record): string; -} - -// Export empty object to make this a module +// Test setup file for Jest +import { afterAll, afterEach, beforeEach, expect, jest } from '@jest/globals'; +import * as fs from 'fs'; +import * as path from 'path'; + +// Set up test environment variables +process.env.NODE_ENV = 'test'; +process.env.INDEXER_TEST = 'true'; + +// Create temp directory for tests +const tempDir = path.join(__dirname, '../.test-temp'); +if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); +} + +// Clean up after all tests +afterAll(() => { + // Clean up temp directory + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } +}); + +// Global test utilities +global.createTestFile = (filename: string, content: string): string => { + const filePath = path.join(tempDir, filename); + const dir = path.dirname(filePath); + + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(filePath, content); + return filePath; +}; + +global.createTestProject = (structure: Record): string => { + const projectDir = path.join(tempDir, `project-${Date.now()}`); + fs.mkdirSync(projectDir, { recursive: true }); + + for (const [filePath, content] of Object.entries(structure)) { + const fullPath = path.join(projectDir, filePath); + const dir = path.dirname(fullPath); + + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(fullPath, content); + } + + return projectDir; +}; + +// Mock console methods for cleaner test output +const originalConsoleLog = console.log; +const originalConsoleError = console.error; +const originalConsoleWarn = console.warn; + +beforeEach(() => { + // Suppress console output during tests unless DEBUG is set + if (!process.env.DEBUG) { + console.log = jest.fn(); + console.error = jest.fn(); + console.warn = jest.fn(); + } +}); + +afterEach(() => { + // Restore console methods + console.log = originalConsoleLog; + console.error = originalConsoleError; + console.warn = originalConsoleWarn; +}); + +// Custom matchers +expect.extend({ + toContainFunction(received: any[], functionName: string) { + const pass = received.some(item => item.name === functionName); + + if (pass) { + return { + message: () => `Expected array not to contain function with name "${functionName}"`, + pass: true + }; + } else { + return { + message: () => `Expected array to contain function with name "${functionName}"`, + pass: false + }; + } + }, + + toContainClass(received: any[], className: string) { + const pass = received.some(item => item.name === className); + + if (pass) { + return { + message: () => `Expected array not to contain class with name "${className}"`, + pass: true + }; + } else { + return { + message: () => `Expected array to contain class with name "${className}"`, + pass: false + }; + } + } +}); + +// Type declarations for custom matchers +declare global { + namespace jest { + interface Matchers { + toContainFunction(functionName: string): R; + toContainClass(className: string): R; + } + } + + function createTestFile(filename: string, content: string): string; + function createTestProject(structure: Record): string; +} + +// Export empty object to make this a module export {}; \ No newline at end of file diff --git a/tests/test-deps.dot b/tests/test-deps.dot index 32a798c..9a6cf8e 100644 --- a/tests/test-deps.dot +++ b/tests/test-deps.dot @@ -1,123 +1,123 @@ -digraph Dependencies { - rankdir=LR; - node [shape=box]; - node [fontname="Arial"]; - subgraph "cluster_." { - label="."; - style=filled; - color=lightgrey; - "jest.config.js" [label="jest.config.js", fillcolor=lightyellow, style=filled]; - "install.js" [label="install.js", fillcolor=lightyellow, style=filled]; - "cypress.config.ts" [label="cypress.config.ts", fillcolor=lightblue, style=filled]; - } - subgraph "cluster_src/utils" { - label="src/utils"; - style=filled; - color=lightgrey; - "src/utils/query.ts" [label="query.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/utils/formatter.ts" [label="formatter.ts", fillcolor=lightblue, style=filled, shape=component]; - } - subgraph "cluster_src/parsers" { - label="src/parsers"; - style=filled; - color=lightgrey; - "src/parsers/yaml.ts" [label="yaml.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/parsers/typescript.ts" [label="typescript.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/parsers/sql.ts" [label="sql.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/parsers/python.ts" [label="python.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/parsers/javascript.ts" [label="javascript.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/parsers/index.ts" [label="index.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/parsers/graphql.ts" [label="graphql.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/parsers/go.ts" [label="go.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/parsers/astro.ts" [label="astro.ts", fillcolor=lightblue, style=filled, shape=component]; - } - subgraph "cluster_src/exporters" { - label="src/exporters"; - style=filled; - color=lightgrey; - "src/exporters/markdown.ts" [label="markdown.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/exporters/json.ts" [label="json.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/exporters/graphviz.ts" [label="graphviz.ts", fillcolor=lightblue, style=filled, shape=component]; - } - subgraph "cluster_src/core" { - label="src/core"; - style=filled; - color=lightgrey; - "src/core/watcher.ts" [label="watcher.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/core/monorepo.ts" [label="monorepo.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/core/indexer.ts" [label="indexer.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/core/config.ts" [label="config.ts", fillcolor=lightblue, style=filled, shape=component]; - } - subgraph "cluster_cypress/support" { - label="cypress/support"; - style=filled; - color=lightgrey; - "cypress/support/e2e.ts" [label="e2e.ts", fillcolor=lightblue, style=filled, shape=component]; - "cypress/support/commands.ts" [label="commands.ts", fillcolor=lightblue, style=filled]; - } - "test/setup.ts" [label="setup.ts", fillcolor=lightblue, style=filled, shape=component]; - "bin/indexer.js" [label="indexer.js", fillcolor=lightyellow, style=filled]; - "src/types/index.ts" [label="index.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/hooks/claude-hook.ts" [label="claude-hook.ts", fillcolor=lightblue, style=filled, shape=component]; - "src/cli/index.ts" [label="index.ts", fillcolor=lightblue, style=filled]; - "cypress/e2e/indexer.cy.ts" [label="indexer.cy.ts", fillcolor=lightblue, style=filled]; - "bin/indexer.js" -> "dist/cli/index.js"; - "src/utils/query.ts" -> "src/types"; - "src/utils/formatter.ts" -> "src/types"; - "src/parsers/yaml.ts" -> "src/types"; - "src/parsers/yaml.ts" -> "src/parsers/index"; - "src/parsers/typescript.ts" -> "src/types"; - "src/parsers/typescript.ts" -> "src/parsers/index"; - "src/parsers/sql.ts" -> "src/types"; - "src/parsers/sql.ts" -> "src/parsers/index"; - "src/parsers/python.ts" -> "src/types"; - "src/parsers/python.ts" -> "src/parsers/index"; - "src/parsers/javascript.ts" -> "src/types"; - "src/parsers/javascript.ts" -> "src/parsers/index"; - "src/parsers/index.ts" -> "src/types"; - "src/parsers/index.ts" -> "src/parsers/javascript"; - "src/parsers/index.ts" -> "src/parsers/typescript"; - "src/parsers/index.ts" -> "src/parsers/python"; - "src/parsers/index.ts" -> "src/parsers/go"; - "src/parsers/index.ts" -> "src/parsers/sql"; - "src/parsers/index.ts" -> "src/parsers/graphql"; - "src/parsers/index.ts" -> "src/parsers/yaml"; - "src/parsers/index.ts" -> "src/parsers/astro"; - "src/parsers/graphql.ts" -> "src/types"; - "src/parsers/graphql.ts" -> "src/parsers/index"; - "src/parsers/go.ts" -> "src/types"; - "src/parsers/go.ts" -> "src/parsers/index"; - "src/parsers/astro.ts" -> "src/parsers/index"; - "src/parsers/astro.ts" -> "src/types"; - "src/exporters/markdown.ts" -> "src/types"; - "src/exporters/json.ts" -> "src/types"; - "src/exporters/graphviz.ts" -> "src/types"; - "src/cli/index.ts" -> "src/core/indexer"; - "src/cli/index.ts" -> "src/core/watcher"; - "src/cli/index.ts" -> "src/core/config"; - "src/cli/index.ts" -> "src/core/monorepo"; - "src/cli/index.ts" -> "src/types"; - "src/cli/index.ts" -> "src/exporters/json"; - "src/cli/index.ts" -> "src/exporters/graphviz"; - "src/cli/index.ts" -> "src/exporters/markdown"; - "src/core/watcher.ts" -> "src/core/indexer"; - "src/core/watcher.ts" -> "src/types"; - "src/core/monorepo.ts" -> "src/types"; - "src/core/indexer.ts" -> "src/core/config"; - "src/core/indexer.ts" -> "src/parsers"; - "src/core/indexer.ts" -> "src/types"; - "src/core/config.ts" -> "src/types"; - "cypress/support/e2e.ts" -> "cypress/support/commands"; - - subgraph cluster_legend { - label="Legend"; - style=dotted; - node [shape=box, style=filled]; - "TypeScript" [fillcolor=lightblue]; - "JavaScript" [fillcolor=lightyellow]; - "Python" [fillcolor=lightgreen]; - "Go" [fillcolor=lightcyan]; - "Astro" [fillcolor=orange]; - "Circular Dep" [shape=point, color=red, style=bold]; - } +digraph Dependencies { + rankdir=LR; + node [shape=box]; + node [fontname="Arial"]; + subgraph "cluster_." { + label="."; + style=filled; + color=lightgrey; + "jest.config.js" [label="jest.config.js", fillcolor=lightyellow, style=filled]; + "install.js" [label="install.js", fillcolor=lightyellow, style=filled]; + "cypress.config.ts" [label="cypress.config.ts", fillcolor=lightblue, style=filled]; + } + subgraph "cluster_src/utils" { + label="src/utils"; + style=filled; + color=lightgrey; + "src/utils/query.ts" [label="query.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/utils/formatter.ts" [label="formatter.ts", fillcolor=lightblue, style=filled, shape=component]; + } + subgraph "cluster_src/parsers" { + label="src/parsers"; + style=filled; + color=lightgrey; + "src/parsers/yaml.ts" [label="yaml.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/parsers/typescript.ts" [label="typescript.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/parsers/sql.ts" [label="sql.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/parsers/python.ts" [label="python.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/parsers/javascript.ts" [label="javascript.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/parsers/index.ts" [label="index.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/parsers/graphql.ts" [label="graphql.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/parsers/go.ts" [label="go.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/parsers/astro.ts" [label="astro.ts", fillcolor=lightblue, style=filled, shape=component]; + } + subgraph "cluster_src/exporters" { + label="src/exporters"; + style=filled; + color=lightgrey; + "src/exporters/markdown.ts" [label="markdown.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/exporters/json.ts" [label="json.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/exporters/graphviz.ts" [label="graphviz.ts", fillcolor=lightblue, style=filled, shape=component]; + } + subgraph "cluster_src/core" { + label="src/core"; + style=filled; + color=lightgrey; + "src/core/watcher.ts" [label="watcher.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/core/monorepo.ts" [label="monorepo.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/core/indexer.ts" [label="indexer.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/core/config.ts" [label="config.ts", fillcolor=lightblue, style=filled, shape=component]; + } + subgraph "cluster_cypress/support" { + label="cypress/support"; + style=filled; + color=lightgrey; + "cypress/support/e2e.ts" [label="e2e.ts", fillcolor=lightblue, style=filled, shape=component]; + "cypress/support/commands.ts" [label="commands.ts", fillcolor=lightblue, style=filled]; + } + "test/setup.ts" [label="setup.ts", fillcolor=lightblue, style=filled, shape=component]; + "bin/indexer.js" [label="indexer.js", fillcolor=lightyellow, style=filled]; + "src/types/index.ts" [label="index.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/hooks/claude-hook.ts" [label="claude-hook.ts", fillcolor=lightblue, style=filled, shape=component]; + "src/cli/index.ts" [label="index.ts", fillcolor=lightblue, style=filled]; + "cypress/e2e/indexer.cy.ts" [label="indexer.cy.ts", fillcolor=lightblue, style=filled]; + "bin/indexer.js" -> "dist/cli/index.js"; + "src/utils/query.ts" -> "src/types"; + "src/utils/formatter.ts" -> "src/types"; + "src/parsers/yaml.ts" -> "src/types"; + "src/parsers/yaml.ts" -> "src/parsers/index"; + "src/parsers/typescript.ts" -> "src/types"; + "src/parsers/typescript.ts" -> "src/parsers/index"; + "src/parsers/sql.ts" -> "src/types"; + "src/parsers/sql.ts" -> "src/parsers/index"; + "src/parsers/python.ts" -> "src/types"; + "src/parsers/python.ts" -> "src/parsers/index"; + "src/parsers/javascript.ts" -> "src/types"; + "src/parsers/javascript.ts" -> "src/parsers/index"; + "src/parsers/index.ts" -> "src/types"; + "src/parsers/index.ts" -> "src/parsers/javascript"; + "src/parsers/index.ts" -> "src/parsers/typescript"; + "src/parsers/index.ts" -> "src/parsers/python"; + "src/parsers/index.ts" -> "src/parsers/go"; + "src/parsers/index.ts" -> "src/parsers/sql"; + "src/parsers/index.ts" -> "src/parsers/graphql"; + "src/parsers/index.ts" -> "src/parsers/yaml"; + "src/parsers/index.ts" -> "src/parsers/astro"; + "src/parsers/graphql.ts" -> "src/types"; + "src/parsers/graphql.ts" -> "src/parsers/index"; + "src/parsers/go.ts" -> "src/types"; + "src/parsers/go.ts" -> "src/parsers/index"; + "src/parsers/astro.ts" -> "src/parsers/index"; + "src/parsers/astro.ts" -> "src/types"; + "src/exporters/markdown.ts" -> "src/types"; + "src/exporters/json.ts" -> "src/types"; + "src/exporters/graphviz.ts" -> "src/types"; + "src/cli/index.ts" -> "src/core/indexer"; + "src/cli/index.ts" -> "src/core/watcher"; + "src/cli/index.ts" -> "src/core/config"; + "src/cli/index.ts" -> "src/core/monorepo"; + "src/cli/index.ts" -> "src/types"; + "src/cli/index.ts" -> "src/exporters/json"; + "src/cli/index.ts" -> "src/exporters/graphviz"; + "src/cli/index.ts" -> "src/exporters/markdown"; + "src/core/watcher.ts" -> "src/core/indexer"; + "src/core/watcher.ts" -> "src/types"; + "src/core/monorepo.ts" -> "src/types"; + "src/core/indexer.ts" -> "src/core/config"; + "src/core/indexer.ts" -> "src/parsers"; + "src/core/indexer.ts" -> "src/types"; + "src/core/config.ts" -> "src/types"; + "cypress/support/e2e.ts" -> "cypress/support/commands"; + + subgraph cluster_legend { + label="Legend"; + style=dotted; + node [shape=box, style=filled]; + "TypeScript" [fillcolor=lightblue]; + "JavaScript" [fillcolor=lightyellow]; + "Python" [fillcolor=lightgreen]; + "Go" [fillcolor=lightcyan]; + "Astro" [fillcolor=orange]; + "Circular Dep" [shape=point, color=red, style=bold]; + } } \ No newline at end of file diff --git a/tests/test.astro b/tests/test.astro index 1fe0ae9..5cfc1cd 100644 --- a/tests/test.astro +++ b/tests/test.astro @@ -1,42 +1,42 @@ ---- -import Layout from './Layout.astro'; -import Button from './Button.astro'; - -const { title } = Astro.props; -const greeting = 'Hello, Astro!'; - -export const metadata = { - title: 'Test Page', - version: '1.0.0' -}; ---- - - -
-

{greeting}

- -
-
- - - - \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 990e9d9..bac22c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,37 +1,37 @@ -{ - "compilerOptions": { - "target": "ES2023", - "module": "commonjs", - "lib": ["ES2023"], - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "strictNullChecks": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "resolveJsonModule": true, - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "types": ["node"], - "useDefineForClassFields": true, - "exactOptionalPropertyTypes": false, - "noPropertyAccessFromIndexSignature": false, - "noUncheckedIndexedAccess": false, - "allowUnusedLabels": false, - "allowUnreachableCode": false, - "noImplicitOverride": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src/**/*"], - "exclude": [ - "node_modules", - "dist", - "tests", - "**/*.test.ts", - "**/*.spec.ts" - ] +{ + "compilerOptions": { + "target": "ES2023", + "module": "commonjs", + "lib": ["ES2023"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "strictNullChecks": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "types": ["node"], + "useDefineForClassFields": true, + "exactOptionalPropertyTypes": false, + "noPropertyAccessFromIndexSignature": false, + "noUncheckedIndexedAccess": false, + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*"], + "exclude": [ + "node_modules", + "dist", + "tests", + "**/*.test.ts", + "**/*.spec.ts" + ] } \ No newline at end of file diff --git a/tsconfig.minimal.json b/tsconfig.minimal.json index 3d32c53..662bd54 100644 --- a/tsconfig.minimal.json +++ b/tsconfig.minimal.json @@ -1,41 +1,41 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "commonjs", - "lib": ["ES2022"], - "outDir": "./dist", - "rootDir": "./src", - "strict": false, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": false, - "sourceMap": true, - "resolveJsonModule": true, - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "types": ["node"], - "noEmitOnError": false - }, - "include": [ - "src/cli/index.ts", - "src/core/*.ts", - "src/parsers/*.ts", - "src/exporters/*.ts", - "src/types/*.ts", - "src/utils/*.ts", - "src/search/*.ts" - ], - "exclude": [ - "node_modules", - "dist", - "tests", - "src/ai/**", - "src/api/**", - "src/vscode/**", - "src/cursor/**", - "src/parsers/wasm/**", - "src/hooks/**", - "src/integrations/**" - ] +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": false, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": false, + "sourceMap": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "types": ["node"], + "noEmitOnError": false + }, + "include": [ + "src/cli/index.ts", + "src/core/*.ts", + "src/parsers/*.ts", + "src/exporters/*.ts", + "src/types/*.ts", + "src/utils/*.ts", + "src/search/*.ts" + ], + "exclude": [ + "node_modules", + "dist", + "tests", + "src/ai/**", + "src/api/**", + "src/vscode/**", + "src/cursor/**", + "src/parsers/wasm/**", + "src/hooks/**", + "src/integrations/**" + ] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 22df576..aa10b2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,6507 +1,6507 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@anthropic-ai/claude-code@^1.0.89": - version "1.0.95" - resolved "https://registry.yarnpkg.com/@anthropic-ai/claude-code/-/claude-code-1.0.95.tgz#471d8933d58912677a8615a0fd0a1fac0c3ac2cc" - integrity sha512-VLwfezJ8vnNpkdUdyMORQ1JXx/C7GFd8MLiJTXXThvMTxhNEr8IhAY/bExNrtdkWx2+aipGocRLgNZrUon9HMw== - optionalDependencies: - "@img/sharp-darwin-arm64" "^0.33.5" - "@img/sharp-darwin-x64" "^0.33.5" - "@img/sharp-linux-arm" "^0.33.5" - "@img/sharp-linux-arm64" "^0.33.5" - "@img/sharp-linux-x64" "^0.33.5" - "@img/sharp-win32-x64" "^0.33.5" - -"@apollo/cache-control-types@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz#5da62cf64c3b4419dabfef4536b57a40c8ff0b47" - integrity sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g== - -"@apollo/protobufjs@1.2.6": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.6.tgz#d601e65211e06ae1432bf5993a1a0105f2862f27" - integrity sha512-Wqo1oSHNUj/jxmsVp4iR3I480p6qdqHikn38lKrFhfzcDJ7lwd7Ck7cHRl4JE81tWNArl77xhnG/OkZhxKBYOw== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.0" - "@types/node" "^10.1.0" - long "^4.0.0" - -"@apollo/protobufjs@1.2.7": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.7.tgz#3a8675512817e4a046a897e5f4f16415f16a7d8a" - integrity sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.0" - long "^4.0.0" - -"@apollo/server-gateway-interface@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@apollo/server-gateway-interface/-/server-gateway-interface-2.0.0.tgz#16e375f8edd302641ac60815f6c448e93a2a48a0" - integrity sha512-3HEMD6fSantG2My3jWkb9dvfkF9vJ4BDLRjMgsnD790VINtuPaEp+h3Hg9HOHiWkML6QsOhnaRqZ+gvhp3y8Nw== - dependencies: - "@apollo/usage-reporting-protobuf" "^4.1.1" - "@apollo/utils.fetcher" "^3.0.0" - "@apollo/utils.keyvaluecache" "^4.0.0" - "@apollo/utils.logger" "^3.0.0" - -"@apollo/server@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@apollo/server/-/server-5.0.0.tgz#c7d120e22db66743aa4b21d380062f51b194a47e" - integrity sha512-PHopOm7pr69k7eDJvCBU4cZy9Z19qyCFKB9/luLnf2YCatu2WOYhoQPNr3dAoe//xv0RZFhxXbRcnK6IXIP7Nw== - dependencies: - "@apollo/cache-control-types" "^1.0.3" - "@apollo/server-gateway-interface" "^2.0.0" - "@apollo/usage-reporting-protobuf" "^4.1.1" - "@apollo/utils.createhash" "^3.0.0" - "@apollo/utils.fetcher" "^3.0.0" - "@apollo/utils.isnodelike" "^3.0.0" - "@apollo/utils.keyvaluecache" "^4.0.0" - "@apollo/utils.logger" "^3.0.0" - "@apollo/utils.usagereporting" "^2.1.0" - "@apollo/utils.withrequired" "^3.0.0" - "@graphql-tools/schema" "^10.0.0" - async-retry "^1.2.1" - body-parser "^2.2.0" - cors "^2.8.5" - finalhandler "^2.1.0" - loglevel "^1.6.8" - lru-cache "^11.1.0" - negotiator "^1.0.0" - uuid "^11.1.0" - whatwg-mimetype "^4.0.0" - -"@apollo/usage-reporting-protobuf@^4.0.0", "@apollo/usage-reporting-protobuf@^4.1.0", "@apollo/usage-reporting-protobuf@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz#407c3d18c7fbed7a264f3b9a3812620b93499de1" - integrity sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA== - dependencies: - "@apollo/protobufjs" "1.2.7" - -"@apollo/utils.createhash@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@apollo/utils.createhash/-/utils.createhash-3.0.1.tgz#19fc6507e71d44303c2fffa51052305d689d288f" - integrity sha512-CKrlySj4eQYftBE5MJ8IzKwIibQnftDT7yGfsJy5KSEEnLlPASX0UTpbKqkjlVEwPPd4mEwI7WOM7XNxEuO05A== - dependencies: - "@apollo/utils.isnodelike" "^3.0.0" - sha.js "^2.4.11" - -"@apollo/utils.dropunuseddefinitions@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-1.1.0.tgz#02b04006442eaf037f4c4624146b12775d70d929" - integrity sha512-jU1XjMr6ec9pPoL+BFWzEPW7VHHulVdGKMkPAMiCigpVIT11VmCbnij0bWob8uS3ODJ65tZLYKAh/55vLw2rbg== - -"@apollo/utils.dropunuseddefinitions@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-2.0.1.tgz#916cd912cbd88769d3b0eab2d24f4674eeda8124" - integrity sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA== - -"@apollo/utils.fetcher@^3.0.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.fetcher/-/utils.fetcher-3.1.0.tgz#0639c30a5ac57e3e62784b180495023f5e886fe0" - integrity sha512-Z3QAyrsQkvrdTuHAFwWDNd+0l50guwoQUoaDQssLOjkmnmVuvXlJykqlEJolio+4rFwBnWdoY1ByFdKaQEcm7A== - -"@apollo/utils.isnodelike@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.isnodelike/-/utils.isnodelike-3.0.0.tgz#0b51cb20e24850e49851f8b038fae24fd4a8beaf" - integrity sha512-xrjyjfkzunZ0DeF6xkHaK5IKR8F1FBq6qV+uZ+h9worIF/2YSzA0uoBxGv6tbTeo9QoIQnRW4PVFzGix5E7n/g== - -"@apollo/utils.keyvaluecache@^1.0.1": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-1.0.2.tgz#2bfe358c4d82f3a0950518451996758c52613f57" - integrity sha512-p7PVdLPMnPzmXSQVEsy27cYEjVON+SH/Wb7COyW3rQN8+wJgT1nv9jZouYtztWW8ZgTkii5T6tC9qfoDREd4mg== - dependencies: - "@apollo/utils.logger" "^1.0.0" - lru-cache "7.10.1 - 7.13.1" - -"@apollo/utils.keyvaluecache@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-4.0.0.tgz#c183fcc87263589e9e402651b2bb692e9f2353e0" - integrity sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw== - dependencies: - "@apollo/utils.logger" "^3.0.0" - lru-cache "^11.0.0" - -"@apollo/utils.logger@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@apollo/utils.logger/-/utils.logger-1.0.1.tgz#aea0d1bb7ceb237f506c6bbf38f10a555b99a695" - integrity sha512-XdlzoY7fYNK4OIcvMD2G94RoFZbzTQaNP0jozmqqMudmaGo2I/2Jx71xlDJ801mWA/mbYRihyaw6KJii7k5RVA== - -"@apollo/utils.logger@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.logger/-/utils.logger-3.0.0.tgz#5b67de1e476bdfa8be70bfaa05c7e65a6a427960" - integrity sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg== - -"@apollo/utils.printwithreducedwhitespace@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-1.1.0.tgz#c466299a4766eef8577a2a64c8f27712e8bd7e30" - integrity sha512-GfFSkAv3n1toDZ4V6u2d7L4xMwLA+lv+6hqXicMN9KELSJ9yy9RzuEXaX73c/Ry+GzRsBy/fdSUGayGqdHfT2Q== - -"@apollo/utils.printwithreducedwhitespace@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-2.0.1.tgz#f4fadea0ae849af2c19c339cc5420d1ddfaa905e" - integrity sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg== - -"@apollo/utils.removealiases@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.removealiases/-/utils.removealiases-1.0.0.tgz#75f6d83098af1fcae2d3beb4f515ad4a8452a8c1" - integrity sha512-6cM8sEOJW2LaGjL/0vHV0GtRaSekrPQR4DiywaApQlL9EdROASZU5PsQibe2MWeZCOhNrPRuHh4wDMwPsWTn8A== - -"@apollo/utils.removealiases@2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@apollo/utils.removealiases/-/utils.removealiases-2.0.1.tgz#2873c93d72d086c60fc0d77e23d0f75e66a2598f" - integrity sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA== - -"@apollo/utils.sortast@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.sortast/-/utils.sortast-1.1.0.tgz#93218c7008daf3e2a0725196085a33f5aab5ad07" - integrity sha512-VPlTsmUnOwzPK5yGZENN069y6uUHgeiSlpEhRnLFYwYNoJHsuJq2vXVwIaSmts015WTPa2fpz1inkLYByeuRQA== - dependencies: - lodash.sortby "^4.7.0" - -"@apollo/utils.sortast@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@apollo/utils.sortast/-/utils.sortast-2.0.1.tgz#58c90bb8bd24726346b61fa51ba7fcf06e922ef7" - integrity sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw== - dependencies: - lodash.sortby "^4.7.0" - -"@apollo/utils.stripsensitiveliterals@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-1.2.0.tgz#4920651f36beee8e260e12031a0c5863ad0c7b28" - integrity sha512-E41rDUzkz/cdikM5147d8nfCFVKovXxKBcjvLEQ7bjZm/cg9zEcXvS6vFY8ugTubI3fn6zoqo0CyU8zT+BGP9w== - -"@apollo/utils.stripsensitiveliterals@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-2.0.1.tgz#2f3350483be376a98229f90185eaf19888323132" - integrity sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA== - -"@apollo/utils.usagereporting@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@apollo/utils.usagereporting/-/utils.usagereporting-1.0.1.tgz#3c70b49e554771659576fe35381c7a4b321d27fd" - integrity sha512-6dk+0hZlnDbahDBB2mP/PZ5ybrtCJdLMbeNJD+TJpKyZmSY6bA3SjI8Cr2EM9QA+AdziywuWg+SgbWUF3/zQqQ== - dependencies: - "@apollo/usage-reporting-protobuf" "^4.0.0" - "@apollo/utils.dropunuseddefinitions" "^1.1.0" - "@apollo/utils.printwithreducedwhitespace" "^1.1.0" - "@apollo/utils.removealiases" "1.0.0" - "@apollo/utils.sortast" "^1.1.0" - "@apollo/utils.stripsensitiveliterals" "^1.2.0" - -"@apollo/utils.usagereporting@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.usagereporting/-/utils.usagereporting-2.1.0.tgz#11bca6a61fcbc6e6d812004503b38916e74313f4" - integrity sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ== - dependencies: - "@apollo/usage-reporting-protobuf" "^4.1.0" - "@apollo/utils.dropunuseddefinitions" "^2.0.1" - "@apollo/utils.printwithreducedwhitespace" "^2.0.1" - "@apollo/utils.removealiases" "2.0.1" - "@apollo/utils.sortast" "^2.0.1" - "@apollo/utils.stripsensitiveliterals" "^2.0.1" - -"@apollo/utils.withrequired@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@apollo/utils.withrequired/-/utils.withrequired-3.0.0.tgz#1342bab438528d09c41e43ce33cb976fa9c99e52" - integrity sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA== - -"@apollographql/apollo-tools@^0.5.3": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@apollographql/apollo-tools/-/apollo-tools-0.5.4.tgz#cb3998c6cf12e494b90c733f44dd9935e2d8196c" - integrity sha512-shM3q7rUbNyXVVRkQJQseXv6bnYM3BUma/eZhwXR4xsuM+bqWnJKvW7SAfRjP7LuSCocrexa5AXhjjawNHrIlw== - -"@apollographql/graphql-playground-html@1.6.29": - version "1.6.29" - resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz#a7a646614a255f62e10dcf64a7f68ead41dec453" - integrity sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA== - dependencies: - xss "^1.0.8" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.27.2": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" - integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== - -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.27.4", "@babel/core@^7.7.5": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.3.tgz#aceddde69c5d1def69b839d09efa3e3ff59c97cb" - integrity sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.3" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helpers" "^7.28.3" - "@babel/parser" "^7.28.3" - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.3" - "@babel/types" "^7.28.2" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.27.5", "@babel/generator@^7.28.3", "@babel/generator@^7.7.2": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" - integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== - dependencies: - "@babel/parser" "^7.28.3" - "@babel/types" "^7.28.2" - "@jridgewell/gen-mapping" "^0.3.12" - "@jridgewell/trace-mapping" "^0.3.28" - jsesc "^3.0.2" - -"@babel/helper-compilation-targets@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== - dependencies: - "@babel/compat-data" "^7.27.2" - "@babel/helper-validator-option" "^7.27.1" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-globals@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" - integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== - -"@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-module-transforms@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" - integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.28.3" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== - -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== - -"@babel/helper-validator-option@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" - integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - -"@babel/helpers@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.3.tgz#b83156c0a2232c133d1b535dd5d3452119c7e441" - integrity sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw== - dependencies: - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.2" - -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" - integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== - dependencies: - "@babel/types" "^7.28.2" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" - integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-import-meta@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" - integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-typescript@^7.27.1", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" - integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/template@^7.27.2", "@babel/template@^7.3.3": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" - -"@babel/traverse@^7.24.0", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.3.tgz#6911a10795d2cce43ec6a28cffc440cca2593434" - integrity sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.3" - "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.3" - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.2" - debug "^4.3.1" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.3.3": - version "7.28.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" - integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@cypress/code-coverage@^3.12.0": - version "3.14.6" - resolved "https://registry.yarnpkg.com/@cypress/code-coverage/-/code-coverage-3.14.6.tgz#9ea7f1211e714f0ec314d4ea939cd20929950cbd" - integrity sha512-aGDJG89uR5CRFbAIs+uFb3f6yPQTf/cMD6D9BZBo/gJRS1vZXli4lIGYU2DoJK4gMP+x9mAQSRexN91oizCusg== - dependencies: - "@cypress/webpack-preprocessor" "^6.0.0" - chalk "4.1.2" - dayjs "1.11.13" - debug "4.4.0" - execa "4.1.0" - globby "11.1.0" - istanbul-lib-coverage "^3.0.0" - js-yaml "4.1.0" - nyc "15.1.0" - -"@cypress/request@^3.0.6": - version "3.0.9" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.9.tgz#8ed6e08fea0c62998b5552301023af7268f11625" - integrity sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~4.0.4" - http-signature "~1.4.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - performance-now "^2.1.0" - qs "6.14.0" - safe-buffer "^5.1.2" - tough-cookie "^5.0.0" - tunnel-agent "^0.6.0" - uuid "^8.3.2" - -"@cypress/webpack-preprocessor@^6.0.0": - version "6.0.4" - resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-6.0.4.tgz#537f6bee2a971979adf35d925609a86f9e4aff30" - integrity sha512-ly+EcabWWbhrSPr2J/njQX7Y3da+QqOmFg8Og/MVmLxhDLKIzr2WhTdgzDYviPTLx/IKsdb41cc2RLYp6mSBRA== - dependencies: - bluebird "3.7.1" - debug "^4.3.4" - lodash "^4.17.20" - semver "^7.3.2" - -"@cypress/xvfb@^1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" - integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== - dependencies: - debug "^3.1.0" - lodash.once "^4.1.1" - -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" - integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== - dependencies: - eslint-visitor-keys "^3.4.3" - -"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== - -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" - integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== - -"@graphql-tools/merge@8.3.1": - version "8.3.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.1.tgz#06121942ad28982a14635dbc87b5d488a041d722" - integrity sha512-BMm99mqdNZbEYeTPK3it9r9S6rsZsQKtlqJsSBknAclXq2pGEfOxjcIZi+kBSkHZKPKCRrYDd5vY0+rUmIHVLg== - dependencies: - "@graphql-tools/utils" "8.9.0" - tslib "^2.4.0" - -"@graphql-tools/merge@^8.4.1": - version "8.4.2" - resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.4.2.tgz#95778bbe26b635e8d2f60ce9856b388f11fe8288" - integrity sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw== - dependencies: - "@graphql-tools/utils" "^9.2.1" - tslib "^2.4.0" - -"@graphql-tools/merge@^9.1.1": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-9.1.1.tgz#6a51775f5ba6bdef213def1fd277536f9468b44c" - integrity sha512-BJ5/7Y7GOhTuvzzO5tSBFL4NGr7PVqTJY3KeIDlVTT8YLcTXtBR+hlrC3uyEym7Ragn+zyWdHeJ9ev+nRX1X2w== - dependencies: - "@graphql-tools/utils" "^10.9.1" - tslib "^2.4.0" - -"@graphql-tools/mock@^8.1.2": - version "8.7.20" - resolved "https://registry.yarnpkg.com/@graphql-tools/mock/-/mock-8.7.20.tgz#c83ae0f1940d194a3982120c9c85f3ac6b4f7f20" - integrity sha512-ljcHSJWjC/ZyzpXd5cfNhPI7YljRVvabKHPzKjEs5ElxWu2cdlLGvyNYepApXDsM/OJG/2xuhGM+9GWu5gEAPQ== - dependencies: - "@graphql-tools/schema" "^9.0.18" - "@graphql-tools/utils" "^9.2.1" - fast-json-stable-stringify "^2.1.0" - tslib "^2.4.0" - -"@graphql-tools/schema@^10.0.0", "@graphql-tools/schema@^10.0.25": - version "10.0.25" - resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-10.0.25.tgz#73de08bc765f482cde334562a1bebe95a2e5a3c4" - integrity sha512-/PqE8US8kdQ7lB9M5+jlW8AyVjRGCKU7TSktuW3WNKSKmDO0MK1wakvb5gGdyT49MjAIb4a3LWxIpwo5VygZuw== - dependencies: - "@graphql-tools/merge" "^9.1.1" - "@graphql-tools/utils" "^10.9.1" - tslib "^2.4.0" - -"@graphql-tools/schema@^8.0.0": - version "8.5.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.5.1.tgz#c2f2ff1448380919a330312399c9471db2580b58" - integrity sha512-0Esilsh0P/qYcB5DKQpiKeQs/jevzIadNTaT0jeWklPMwNbT7yMX4EqZany7mbeRRlSRwMzNzL5olyFdffHBZg== - dependencies: - "@graphql-tools/merge" "8.3.1" - "@graphql-tools/utils" "8.9.0" - tslib "^2.4.0" - value-or-promise "1.0.11" - -"@graphql-tools/schema@^9.0.18": - version "9.0.19" - resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-9.0.19.tgz#c4ad373b5e1b8a0cf365163435b7d236ebdd06e7" - integrity sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w== - dependencies: - "@graphql-tools/merge" "^8.4.1" - "@graphql-tools/utils" "^9.2.1" - tslib "^2.4.0" - value-or-promise "^1.0.12" - -"@graphql-tools/utils@8.9.0": - version "8.9.0" - resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.9.0.tgz#c6aa5f651c9c99e1aca55510af21b56ec296cdb7" - integrity sha512-pjJIWH0XOVnYGXCqej8g/u/tsfV4LvLlj0eATKQu5zwnxd/TiTHq7Cg313qUPTFFHZ3PP5wJ15chYVtLDwaymg== - dependencies: - tslib "^2.4.0" - -"@graphql-tools/utils@^10.9.1": - version "10.9.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-10.9.1.tgz#ff4067256f2080db0c66f4475858f29a8da65ecf" - integrity sha512-B1wwkXk9UvU7LCBkPs8513WxOQ2H8Fo5p8HR1+Id9WmYE5+bd51vqN+MbrqvWczHCH2gwkREgHJN88tE0n1FCw== - dependencies: - "@graphql-typed-document-node/core" "^3.1.1" - "@whatwg-node/promise-helpers" "^1.0.0" - cross-inspect "1.0.1" - dset "^3.1.4" - tslib "^2.4.0" - -"@graphql-tools/utils@^9.2.1": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-9.2.1.tgz#1b3df0ef166cfa3eae706e3518b17d5922721c57" - integrity sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A== - dependencies: - "@graphql-typed-document-node/core" "^3.1.1" - tslib "^2.4.0" - -"@graphql-typed-document-node/core@^3.1.0", "@graphql-typed-document-node/core@^3.1.1": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" - integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== - -"@humanwhocodes/config-array@^0.13.0": - version "0.13.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" - integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== - dependencies: - "@humanwhocodes/object-schema" "^2.0.3" - debug "^4.3.1" - minimatch "^3.0.5" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== - -"@img/sharp-darwin-arm64@^0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" - integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== - optionalDependencies: - "@img/sharp-libvips-darwin-arm64" "1.0.4" - -"@img/sharp-darwin-x64@^0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" - integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== - optionalDependencies: - "@img/sharp-libvips-darwin-x64" "1.0.4" - -"@img/sharp-libvips-darwin-arm64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f" - integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== - -"@img/sharp-libvips-darwin-x64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" - integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== - -"@img/sharp-libvips-linux-arm64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" - integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== - -"@img/sharp-libvips-linux-arm@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" - integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== - -"@img/sharp-libvips-linux-x64@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" - integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== - -"@img/sharp-linux-arm64@^0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" - integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== - optionalDependencies: - "@img/sharp-libvips-linux-arm64" "1.0.4" - -"@img/sharp-linux-arm@^0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" - integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== - optionalDependencies: - "@img/sharp-libvips-linux-arm" "1.0.5" - -"@img/sharp-linux-x64@^0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" - integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== - optionalDependencies: - "@img/sharp-libvips-linux-x64" "1.0.4" - -"@img/sharp-win32-x64@^0.33.5": - version "0.33.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" - integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" - integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - -"@jest/core@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" - integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== - dependencies: - "@jest/console" "^29.7.0" - "@jest/reporters" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - ci-info "^3.2.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-changed-files "^29.7.0" - jest-config "^29.7.0" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-resolve-dependencies "^29.7.0" - jest-runner "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - jest-watcher "^29.7.0" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/diff-sequences@30.0.1": - version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" - integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== - -"@jest/environment@30.1.1": - version "30.1.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.1.1.tgz#ce8f245a14ff47c8fbc2ac17e2fbe9b984df245d" - integrity sha512-yWHbU+3j7ehQE+NRpnxRvHvpUhoohIjMePBbIr8lfe0cWVb0WeTf80DNux1GPJa18CDHiIU5DtksGUfxcDE+Rw== - dependencies: - "@jest/fake-timers" "30.1.1" - "@jest/types" "30.0.5" - "@types/node" "*" - jest-mock "30.0.5" - -"@jest/environment@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" - integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== - dependencies: - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - -"@jest/expect-utils@30.1.1": - version "30.1.1" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.1.1.tgz#f57553f708b445a8d20c5b365bc9c84f87cba2ac" - integrity sha512-5YUHr27fpJ64dnvtu+tt11ewATynrHkGYD+uSFgRr8V2eFJis/vEXgToyLwccIwqBihVfz9jwio+Zr1ab1Zihw== - dependencies: - "@jest/get-type" "30.1.0" - -"@jest/expect-utils@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" - integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== - dependencies: - jest-get-type "^29.6.3" - -"@jest/expect@30.1.1": - version "30.1.1" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.1.1.tgz#d9274c0dc6af430ab4c5b1e6692e62cec757d229" - integrity sha512-3vHIHsF+qd3D8FU2c7U5l3rg1fhDwAYcGyHyZAi94YIlTwcJ+boNhRyJf373cl4wxbOX+0Q7dF40RTrTFTSuig== - dependencies: - expect "30.1.1" - jest-snapshot "30.1.1" - -"@jest/expect@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" - integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== - dependencies: - expect "^29.7.0" - jest-snapshot "^29.7.0" - -"@jest/fake-timers@30.1.1": - version "30.1.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.1.1.tgz#eb0cce02f8ca5a69cc9754780836068461ccaa45" - integrity sha512-fK/25dNgBNYPw3eLi2CRs57g1H04qBAFNMsUY3IRzkfx/m4THe0E1zF+yGQBOMKKc2XQVdc9EYbJ4hEm7/2UtA== - dependencies: - "@jest/types" "30.0.5" - "@sinonjs/fake-timers" "^13.0.0" - "@types/node" "*" - jest-message-util "30.1.0" - jest-mock "30.0.5" - jest-util "30.0.5" - -"@jest/fake-timers@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -"@jest/get-type@30.1.0": - version "30.1.0" - resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" - integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== - -"@jest/globals@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" - integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/types" "^29.6.3" - jest-mock "^29.7.0" - -"@jest/globals@^30.0.5": - version "30.1.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.1.1.tgz#0d1cdfb7f6d73ee1a6e9f679fcd706971804b39e" - integrity sha512-NNUUkHT2TU/xztZl6r1UXvJL+zvCwmZsQDmK69fVHHcB9fBtlu3FInnzOve/ZoyKnWY8JXWJNT+Lkmu1+ubXUA== - dependencies: - "@jest/environment" "30.1.1" - "@jest/expect" "30.1.1" - "@jest/types" "30.0.5" - jest-mock "30.0.5" - -"@jest/pattern@30.0.1": - version "30.0.1" - resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" - integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== - dependencies: - "@types/node" "*" - jest-regex-util "30.0.1" - -"@jest/reporters@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" - integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - "@types/node" "*" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^6.0.0" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.1.3" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - jest-worker "^29.7.0" - slash "^3.0.0" - string-length "^4.0.1" - strip-ansi "^6.0.0" - v8-to-istanbul "^9.0.1" - -"@jest/schemas@30.0.5": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" - integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== - dependencies: - "@sinclair/typebox" "^0.34.0" - -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - -"@jest/snapshot-utils@30.1.1": - version "30.1.1" - resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.1.1.tgz#cfb8eaff6e487954437335613646009b530e9959" - integrity sha512-TkVBc9wuN22TT8hESRFmjjg/xIMu7z0J3UDYtIRydzCqlLPTB7jK1DDBKdnTUZ4zL3z3rnPpzV6rL1Uzh87sXg== - dependencies: - "@jest/types" "30.0.5" - chalk "^4.1.2" - graceful-fs "^4.2.11" - natural-compare "^1.4.0" - -"@jest/source-map@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" - integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - callsites "^3.0.0" - graceful-fs "^4.2.9" - -"@jest/test-result@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" - integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== - dependencies: - "@jest/console" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" - integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== - dependencies: - "@jest/test-result" "^29.7.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - slash "^3.0.0" - -"@jest/transform@30.1.1": - version "30.1.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.1.1.tgz#9ed736ee0e8787d5648401193603e0026d11f0b0" - integrity sha512-PHIA2AbAASBfk6evkNifvmx9lkOSkmvaQoO6VSpuL8+kQqDMHeDoJ7RU3YP1wWAMD7AyQn9UL5iheuFYCC4lqQ== - dependencies: - "@babel/core" "^7.27.4" - "@jest/types" "30.0.5" - "@jridgewell/trace-mapping" "^0.3.25" - babel-plugin-istanbul "^7.0.0" - chalk "^4.1.2" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.11" - jest-haste-map "30.1.0" - jest-regex-util "30.0.1" - jest-util "30.0.5" - micromatch "^4.0.8" - pirates "^4.0.7" - slash "^3.0.0" - write-file-atomic "^5.0.1" - -"@jest/transform@^29.7.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" - integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== - dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.6.3" - "@jridgewell/trace-mapping" "^0.3.18" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^2.0.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.2" - -"@jest/types@30.0.5": - version "30.0.5" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" - integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== - dependencies: - "@jest/pattern" "30.0.1" - "@jest/schemas" "30.0.5" - "@types/istanbul-lib-coverage" "^2.0.6" - "@types/istanbul-reports" "^3.0.4" - "@types/node" "*" - "@types/yargs" "^17.0.33" - chalk "^4.1.2" - -"@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - -"@josephg/resolvable@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb" - integrity sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg== - -"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" - integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.30" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99" - integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@linear/sdk@^25.0.0": - version "25.0.0" - resolved "https://registry.yarnpkg.com/@linear/sdk/-/sdk-25.0.0.tgz#f15c76a855f2aa2139ac46416b4b4c6e45a4f0e2" - integrity sha512-GjD0NXTyLVLzSweYhqTi0XXkUiRQQ2uQVVPDbc1MzknrEBMT+DaBNSwfR8j22xMxfUhthq8leq0OSc5o5zo2fA== - dependencies: - "@graphql-typed-document-node/core" "^3.1.0" - graphql "^15.4.0" - isomorphic-unfetch "^3.1.0" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - -"@pkgr/core@^0.2.9": - version "0.2.9" - resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" - integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== - -"@sinclair/typebox@^0.27.8": - version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" - integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== - -"@sinclair/typebox@^0.34.0": - version "0.34.40" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.40.tgz#740056ea8d8aaada2ac1ce414c2f074798283b92" - integrity sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw== - -"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" - integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^10.0.2": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" - integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== - dependencies: - "@sinonjs/commons" "^3.0.0" - -"@sinonjs/fake-timers@^13.0.0": - version "13.0.5" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" - integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== - dependencies: - "@sinonjs/commons" "^3.0.1" - -"@slack/events-api@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@slack/events-api/-/events-api-3.0.1.tgz#f7db24fa69ab79c97e4eb4355d428af7bf1b5cc6" - integrity sha512-ReJzZRpCgwGtKrAT0tRMppO3zm72jmxsOlTgR7PGajv2oq/tOJSeVRm7RcGiwn3EPIuovKkD/mr4TTN4n801fQ== - dependencies: - "@types/debug" "^4.1.4" - "@types/express" "^4.17.0" - "@types/lodash.isstring" "^4.0.6" - "@types/node" ">=12.13.0 < 13" - "@types/yargs" "^15.0.4" - debug "^2.6.1" - lodash.isstring "^4.0.1" - raw-body "^2.3.3" - tsscmp "^1.0.6" - yargs "^15.3.1" - optionalDependencies: - express "^4.0.0" - -"@slack/logger@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@slack/logger/-/logger-3.0.0.tgz#b736d4e1c112c22a10ffab0c2d364620aedcb714" - integrity sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA== - dependencies: - "@types/node" ">=12.0.0" - -"@slack/types@^2.11.0": - version "2.16.0" - resolved "https://registry.yarnpkg.com/@slack/types/-/types-2.16.0.tgz#92ba59f9e970440b524423ad694eba4fa4995a86" - integrity sha512-bICnyukvdklXhwxprR3uF1+ZFkTvWTZge4evlCS4G1H1HU6QLY68AcjqzQRymf7/5gNt6Y4OBb4NdviheyZcAg== - -"@slack/web-api@^6.11.0": - version "6.13.0" - resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.13.0.tgz#3df65a9b7b0cef5d7bbd6860a0741ce6b8091b9b" - integrity sha512-dv65crIgdh9ZYHrevLU6XFHTQwTyDmNqEqzuIrV+Vqe/vgiG6w37oex5ePDU1RGm2IJ90H8iOvHFvzdEO/vB+g== - dependencies: - "@slack/logger" "^3.0.0" - "@slack/types" "^2.11.0" - "@types/is-stream" "^1.1.0" - "@types/node" ">=12.0.0" - axios "^1.7.4" - eventemitter3 "^3.1.0" - form-data "^2.5.0" - is-electron "2.2.2" - is-stream "^1.1.0" - p-queue "^6.6.1" - p-retry "^4.0.0" - -"@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@types/accepts@^1.3.5": - version "1.3.7" - resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.7.tgz#3b98b1889d2b2386604c2bbbe62e4fb51e95b265" - integrity sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ== - dependencies: - "@types/node" "*" - -"@types/babel__core@^7.1.14": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" - integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6", "@types/babel__traverse@^7.20.5": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" - integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== - dependencies: - "@babel/types" "^7.28.2" - -"@types/body-parser@*": - version "1.19.6" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" - integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/body-parser@1.19.2": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/cors@2.8.12": - version "2.8.12" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" - integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== - -"@types/debug@^4.1.4": - version "4.1.12" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" - integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== - dependencies: - "@types/ms" "*" - -"@types/express-serve-static-core@4.17.31": - version "4.17.31" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" - integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express-serve-static-core@^4.17.18", "@types/express-serve-static-core@^4.17.33": - version "4.19.6" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" - integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@4.17.14": - version "4.17.14" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" - integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/express@^4.17.0": - version "4.17.23" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef" - integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/graceful-fs@^4.1.3": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - -"@types/http-errors@*": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" - integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== - -"@types/is-stream@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" - integrity sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg== - dependencies: - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" - integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== - -"@types/istanbul-lib-report@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" - integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0", "@types/istanbul-reports@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" - integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^30.0.0": - version "30.0.0" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" - integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== - dependencies: - expect "^30.0.0" - pretty-format "^30.0.0" - -"@types/js-yaml@^4.0.9": - version "4.0.9" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" - integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg== - -"@types/json-schema@^7.0.12": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/lodash.isstring@^4.0.6": - version "4.0.9" - resolved "https://registry.yarnpkg.com/@types/lodash.isstring/-/lodash.isstring-4.0.9.tgz#ebee88b1a01a4d934a2479f2318da8c0ec8adec9" - integrity sha512-sjGPpa15VBpMns/4s6Blm567JgxLVVu/eCYCe7h/TdQyPCz9lIhaLSISjN7ZC9cDXmUT2IM/4mNRw8OtYirziw== - dependencies: - "@types/lodash" "*" - -"@types/lodash@*": - version "4.17.20" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.20.tgz#1ca77361d7363432d29f5e55950d9ec1e1c6ea93" - integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== - -"@types/long@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" - integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/ms@*": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" - integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== - -"@types/node@*", "@types/node@>=12.0.0": - version "24.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.3.0.tgz#89b09f45cb9a8ee69466f18ee5864e4c3eb84dec" - integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow== - dependencies: - undici-types "~7.10.0" - -"@types/node@>=12.13.0 < 13": - version "12.20.55" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" - integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== - -"@types/node@^10.1.0": - version "10.17.60" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" - integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== - -"@types/node@^20.19.11": - version "20.19.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.11.tgz#728cab53092bd5f143beed7fbba7ba99de3c16c4" - integrity sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow== - dependencies: - undici-types "~6.21.0" - -"@types/qs@*": - version "6.14.0" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" - integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - -"@types/semver@^7.5.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.0.tgz#64c441bdae033b378b6eef7d0c3d77c329b9378e" - integrity sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA== - -"@types/send@*": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" - integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-static@*": - version "1.15.8" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" - integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "*" - -"@types/sinonjs__fake-timers@8.1.1": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" - integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== - -"@types/sizzle@^2.3.2": - version "2.3.10" - resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.10.tgz#277a542aff6776d8a9b15f2ac682a663e3e94bbd" - integrity sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww== - -"@types/stack-utils@^2.0.0", "@types/stack-utils@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" - integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== - -"@types/yargs-parser@*": - version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" - integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== - -"@types/yargs@^15.0.4": - version "15.0.19" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.19.tgz#328fb89e46109ecbdb70c295d96ff2f46dfd01b9" - integrity sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA== - dependencies: - "@types/yargs-parser" "*" - -"@types/yargs@^17.0.33", "@types/yargs@^17.0.8": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" - integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== - dependencies: - "@types/yargs-parser" "*" - -"@types/yauzl@^2.9.1": - version "2.10.3" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" - integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== - dependencies: - "@types/node" "*" - -"@typescript-eslint/eslint-plugin@^6.19.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" - integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== - dependencies: - "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/type-utils" "6.21.0" - "@typescript-eslint/utils" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" - debug "^4.3.4" - graphemer "^1.4.0" - ignore "^5.2.4" - natural-compare "^1.4.0" - semver "^7.5.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/parser@^6.19.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" - integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== - dependencies: - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/typescript-estree" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" - integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== - dependencies: - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" - -"@typescript-eslint/type-utils@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" - integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== - dependencies: - "@typescript-eslint/typescript-estree" "6.21.0" - "@typescript-eslint/utils" "6.21.0" - debug "^4.3.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/types@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" - integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== - -"@typescript-eslint/typescript-estree@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" - integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== - dependencies: - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - minimatch "9.0.3" - semver "^7.5.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/utils@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" - integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.12" - "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/typescript-estree" "6.21.0" - semver "^7.5.4" - -"@typescript-eslint/visitor-keys@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" - integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== - dependencies: - "@typescript-eslint/types" "6.21.0" - eslint-visitor-keys "^3.4.1" - -"@ungap/structured-clone@^1.2.0", "@ungap/structured-clone@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" - integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== - -"@whatwg-node/promise-helpers@^1.0.0": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz#3b54987ad6517ef6db5920c66a6f0dada606587d" - integrity sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA== - dependencies: - tslib "^2.6.3" - -accepts@^1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -accepts@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" - integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== - dependencies: - mime-types "^3.0.0" - negotiator "^1.0.0" - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-walk@^8.1.1: - version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== - dependencies: - acorn "^8.11.0" - -acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-colors@^4.1.1: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" - integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== - -ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.0.tgz#2f302e7550431b1b7762705fffb52cf1ffa20447" - integrity sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^5.0.0, ansi-styles@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" - integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== - -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -anymatch@^3.0.3, anymatch@^3.1.3, anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -apollo-datasource@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-3.3.2.tgz#5711f8b38d4b7b53fb788cb4dbd4a6a526ea74c8" - integrity sha512-L5TiS8E2Hn/Yz7SSnWIVbZw0ZfEIXZCa5VUiVxD9P53JvSrf4aStvsFDlGWPvpIdCR+aly2CfoB79B9/JjKFqg== - dependencies: - "@apollo/utils.keyvaluecache" "^1.0.1" - apollo-server-env "^4.2.1" - -apollo-reporting-protobuf@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.4.0.tgz#6edd31f09d4a3704d9e808d1db30eca2229ded26" - integrity sha512-h0u3EbC/9RpihWOmcSsvTW2O6RXVaD/mPEjfrPkxRPTEPWqncsgOoRJw+wih4OqfH3PvTJvoEIf4LwKrUaqWog== - dependencies: - "@apollo/protobufjs" "1.2.6" - -apollo-server-core@^3.13.0: - version "3.13.0" - resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-3.13.0.tgz#ad6601fbb34cc97eedca27a9fb0b5738d11cd27d" - integrity sha512-v/g6DR6KuHn9DYSdtQijz8dLOkP78I5JSVJzPkARhDbhpH74QNwrQ2PP2URAPPEDJ2EeZNQDX8PvbYkAKqg+kg== - dependencies: - "@apollo/utils.keyvaluecache" "^1.0.1" - "@apollo/utils.logger" "^1.0.0" - "@apollo/utils.usagereporting" "^1.0.0" - "@apollographql/apollo-tools" "^0.5.3" - "@apollographql/graphql-playground-html" "1.6.29" - "@graphql-tools/mock" "^8.1.2" - "@graphql-tools/schema" "^8.0.0" - "@josephg/resolvable" "^1.0.0" - apollo-datasource "^3.3.2" - apollo-reporting-protobuf "^3.4.0" - apollo-server-env "^4.2.1" - apollo-server-errors "^3.3.1" - apollo-server-plugin-base "^3.7.2" - apollo-server-types "^3.8.0" - async-retry "^1.2.1" - fast-json-stable-stringify "^2.1.0" - graphql-tag "^2.11.0" - loglevel "^1.6.8" - lru-cache "^6.0.0" - node-abort-controller "^3.0.1" - sha.js "^2.4.11" - uuid "^9.0.0" - whatwg-mimetype "^3.0.0" - -apollo-server-env@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-4.2.1.tgz#ea5b1944accdbdba311f179e4dfaeca482c20185" - integrity sha512-vm/7c7ld+zFMxibzqZ7SSa5tBENc4B0uye9LTfjJwGoQFY5xsUPH5FpO5j0bMUDZ8YYNbrF9SNtzc5Cngcr90g== - dependencies: - node-fetch "^2.6.7" - -apollo-server-errors@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz#ba5c00cdaa33d4cbd09779f8cb6f47475d1cd655" - integrity sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA== - -apollo-server-express@^3.13.0: - version "3.13.0" - resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-3.13.0.tgz#0d8d9bbba3b8b8264912d215f63fd44e74d5f42a" - integrity sha512-iSxICNbDUyebOuM8EKb3xOrpIwOQgKxGbR2diSr4HP3IW8T3njKFOoMce50vr+moOCe1ev8BnLcw9SNbuUtf7g== - dependencies: - "@types/accepts" "^1.3.5" - "@types/body-parser" "1.19.2" - "@types/cors" "2.8.12" - "@types/express" "4.17.14" - "@types/express-serve-static-core" "4.17.31" - accepts "^1.3.5" - apollo-server-core "^3.13.0" - apollo-server-types "^3.8.0" - body-parser "^1.19.0" - cors "^2.8.5" - parseurl "^1.3.3" - -apollo-server-plugin-base@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-3.7.2.tgz#c19cd137bc4c993ba2490ba2b571b0f3ce60a0cd" - integrity sha512-wE8dwGDvBOGehSsPTRZ8P/33Jan6/PmL0y0aN/1Z5a5GcbFhDaaJCjK5cav6npbbGL2DPKK0r6MPXi3k3N45aw== - dependencies: - apollo-server-types "^3.8.0" - -apollo-server-types@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-3.8.0.tgz#d976b6967878681f715fe2b9e4dad9ba86b1346f" - integrity sha512-ZI/8rTE4ww8BHktsVpb91Sdq7Cb71rdSkXELSwdSR0eXu600/sY+1UXhTWdiJvk+Eq5ljqoHLwLbY2+Clq2b9A== - dependencies: - "@apollo/utils.keyvaluecache" "^1.0.1" - "@apollo/utils.logger" "^1.0.0" - apollo-reporting-protobuf "^3.4.0" - apollo-server-env "^4.2.1" - -append-transform@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" - integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== - dependencies: - default-require-extensions "^3.0.0" - -arch@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" - integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== - -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -asn1@~0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" - integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - -async-retry@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" - integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== - dependencies: - retry "0.13.1" - -async@^3.2.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== - -aws4@^1.8.0: - version "1.13.2" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" - integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== - -axios@^1.7.4: - version "1.11.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.11.0.tgz#c2ec219e35e414c025b2095e8b8280278478fdb6" - integrity sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.4" - proxy-from-env "^1.1.0" - -babel-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" - integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== - dependencies: - "@jest/transform" "^29.7.0" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.6.3" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-plugin-istanbul@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" - test-exclude "^6.0.0" - -babel-plugin-istanbul@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz#629a178f63b83dc9ecee46fd20266283b1f11280" - integrity sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.3" - istanbul-lib-instrument "^6.0.2" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" - integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.1.14" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0, babel-preset-current-node-syntax@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" - integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - -babel-preset-jest@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" - integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== - dependencies: - babel-plugin-jest-hoist "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -basic-auth@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" - integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== - dependencies: - safe-buffer "5.1.2" - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== - dependencies: - tweetnacl "^0.14.3" - -bcrypt@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-6.0.0.tgz#86643fddde9bcd0ad91400b063003fa4b0312835" - integrity sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg== - dependencies: - node-addon-api "^8.3.0" - node-gyp-build "^4.8.4" - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -blob-util@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" - integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== - -bluebird@3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de" - integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg== - -bluebird@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -body-parser@1.20.3, body-parser@^1.19.0: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -body-parser@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" - integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg== - dependencies: - bytes "^3.1.2" - content-type "^1.0.5" - debug "^4.4.0" - http-errors "^2.0.0" - iconv-lite "^0.6.3" - on-finished "^2.4.1" - qs "^6.14.0" - raw-body "^3.0.0" - type-is "^2.0.0" - -brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" - integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.3, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browserslist@^4.24.0: - version "4.25.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.3.tgz#9167c9cbb40473f15f75f85189290678b99b16c5" - integrity sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ== - dependencies: - caniuse-lite "^1.0.30001735" - electron-to-chromium "^1.5.204" - node-releases "^2.0.19" - update-browserslist-db "^1.1.3" - -bs-logger@^0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - -buffer-equal-constant-time@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -bytes@3.1.2, bytes@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -cachedir@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d" - integrity sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ== - -caching-transform@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" - integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== - dependencies: - hasha "^5.0.0" - make-dir "^3.0.0" - package-hash "^4.0.0" - write-file-atomic "^3.0.0" - -call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - -call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001735: - version "1.0.30001737" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz#8292bb7591932ff09e9a765f12fdf5629a241ccc" - integrity sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw== - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== - -chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -check-more-types@^2.24.0: - version "2.24.0" - resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" - integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA== - -chokidar@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== - -ci-info@^4.0.0, ci-info@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" - integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== - -cjs-module-lexer@^1.0.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" - integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-table3@~0.6.1: - version "0.6.5" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" - integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== - dependencies: - string-width "^4.2.0" - optionalDependencies: - "@colors/colors" "1.5.0" - -cli-truncate@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" - integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== - dependencies: - slice-ansi "^3.0.0" - string-width "^4.2.0" - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== - -collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colorette@^2.0.16: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -combined-stream@^1.0.8, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^12.0.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" - integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== - -commander@^2.20.3: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== - -common-tags@^1.8.0: - version "1.8.2" - resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" - integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-disposition@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" - integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== - dependencies: - safe-buffer "5.2.1" - -content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie-signature@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" - integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== - -cookie@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" - integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== - -cookie@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" - integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== - -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -create-jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" - integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.9" - jest-config "^29.7.0" - jest-util "^29.7.0" - prompts "^2.0.1" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-inspect@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cross-inspect/-/cross-inspect-1.0.1.tgz#15f6f65e4ca963cf4cc1a2b5fef18f6ca328712b" - integrity sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A== - dependencies: - tslib "^2.4.0" - -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cssfilter@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" - integrity sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw== - -cypress@^13.6.3: - version "13.17.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.17.0.tgz#34c3d68080c4497eace0f353bd1629587a5f600d" - integrity sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA== - dependencies: - "@cypress/request" "^3.0.6" - "@cypress/xvfb" "^1.2.4" - "@types/sinonjs__fake-timers" "8.1.1" - "@types/sizzle" "^2.3.2" - arch "^2.2.0" - blob-util "^2.0.2" - bluebird "^3.7.2" - buffer "^5.7.1" - cachedir "^2.3.0" - chalk "^4.1.0" - check-more-types "^2.24.0" - ci-info "^4.0.0" - cli-cursor "^3.1.0" - cli-table3 "~0.6.1" - commander "^6.2.1" - common-tags "^1.8.0" - dayjs "^1.10.4" - debug "^4.3.4" - enquirer "^2.3.6" - eventemitter2 "6.4.7" - execa "4.1.0" - executable "^4.1.1" - extract-zip "2.0.1" - figures "^3.2.0" - fs-extra "^9.1.0" - getos "^3.2.1" - is-installed-globally "~0.4.0" - lazy-ass "^1.6.0" - listr2 "^3.8.3" - lodash "^4.17.21" - log-symbols "^4.0.0" - minimist "^1.2.8" - ospath "^1.2.2" - pretty-bytes "^5.6.0" - process "^0.11.10" - proxy-from-env "1.0.0" - request-progress "^3.0.0" - semver "^7.5.3" - supports-color "^8.1.1" - tmp "~0.2.3" - tree-kill "1.2.2" - untildify "^4.0.0" - yauzl "^2.10.0" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== - dependencies: - assert-plus "^1.0.0" - -dayjs@1.11.13: - version "1.11.13" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" - integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== - -dayjs@^1.10.4: - version "1.11.14" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.14.tgz#aa47cd445471acac25d55deb101557dd827eff60" - integrity sha512-E8fIdSxUlyqSA8XYGnNa3IkIzxtEmFjI+JU/6ic0P1zmSqyL6HyG5jHnpPjRguDNiaHLpfvHKWFiohNsJLqcJQ== - -debug@2.6.9, debug@^2.6.1: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== - dependencies: - ms "^2.1.3" - -debug@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0: - version "4.4.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" - integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== - dependencies: - ms "^2.1.3" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -dedent@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" - integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== - -default-require-extensions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.1.tgz#bfae00feeaeada68c2ae256c62540f60b80625bd" - integrity sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw== - dependencies: - strip-bom "^4.0.0" - -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -depd@2.0.0, depd@^2.0.0, depd@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" - integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dotenv@^17.2.1: - version "17.2.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.1.tgz#6f32e10faf014883515538dc922a0fb8765d9b32" - integrity sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ== - -dset@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.4.tgz#f8eaf5f023f068a036d08cd07dc9ffb7d0065248" - integrity sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.5.204: - version "1.5.211" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz#749317bf9cf894c06f67980940cf8074e5eb08ca" - integrity sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw== - -emittery@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" - integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -encodeurl@^2.0.0, encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -end-of-stream@^1.1.0: - version "1.4.5" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" - integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== - dependencies: - once "^1.4.0" - -enquirer@^2.3.6: - version "2.4.1" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" - integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== - dependencies: - ansi-colors "^4.1.1" - strip-ansi "^6.0.1" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-define-property@^1.0.0, es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -es6-error@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - -escalade@^3.1.1, escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-html@^1.0.3, escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint@^8.56.0: - version "8.57.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" - integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.1" - "@humanwhocodes/config-array" "^0.13.0" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@^1.8.1, etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventemitter2@6.4.7: - version "6.4.7" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" - integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== - -eventemitter3@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" - integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== - -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -execa@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -executable@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" - integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== - dependencies: - pify "^2.2.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expect@30.1.1, expect@^30.0.0: - version "30.1.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-30.1.1.tgz#165bbdf514880bc9d4377b5b73716a38ab97b2ad" - integrity sha512-OKe7cdic4qbfWd/CcgwJvvCrNX2KWfuMZee9AfJHL1gTYmvqjBjZG1a2NwfhspBzxzlXwsN75WWpKTYfsJpBxg== - dependencies: - "@jest/expect-utils" "30.1.1" - "@jest/get-type" "30.1.0" - jest-matcher-utils "30.1.1" - jest-message-util "30.1.0" - jest-mock "30.0.5" - jest-util "30.0.5" - -expect@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" - integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== - dependencies: - "@jest/expect-utils" "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - -express-rate-limit@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-8.0.1.tgz#3bc13aaf9f448085686180ef60679a68ea89654d" - integrity sha512-aZVCnybn7TVmxO4BtlmnvX+nuz8qHW124KKJ8dumsBsmv5ZLxE0pYu7S2nwyRBGHHCAzdmnGyrc5U/rksSPO7Q== - dependencies: - ip-address "10.0.1" - -express@^4.0.0: - version "4.21.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" - integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.3" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.7.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.3.1" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.12" - proxy-addr "~2.0.7" - qs "6.13.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.2" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -express@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9" - integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== - dependencies: - accepts "^2.0.0" - body-parser "^2.2.0" - content-disposition "^1.0.0" - content-type "^1.0.5" - cookie "^0.7.1" - cookie-signature "^1.2.1" - debug "^4.4.0" - encodeurl "^2.0.0" - escape-html "^1.0.3" - etag "^1.8.1" - finalhandler "^2.1.0" - fresh "^2.0.0" - http-errors "^2.0.0" - merge-descriptors "^2.0.0" - mime-types "^3.0.0" - on-finished "^2.4.1" - once "^1.4.0" - parseurl "^1.3.3" - proxy-addr "^2.0.7" - qs "^6.14.0" - range-parser "^1.2.1" - router "^2.2.0" - send "^1.1.0" - serve-static "^2.2.0" - statuses "^2.0.1" - type-is "^2.0.1" - vary "^1.1.2" - -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extract-zip@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== - -extsprintf@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" - integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^3.2.9: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== - dependencies: - reusify "^1.0.4" - -fb-watchman@^2.0.0, fb-watchman@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" - integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== - dependencies: - bser "2.1.1" - -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== - dependencies: - pend "~1.2.0" - -figures@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== - dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -finalhandler@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f" - integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q== - dependencies: - debug "^4.4.0" - encodeurl "^2.0.0" - escape-html "^1.0.3" - on-finished "^2.4.1" - parseurl "^1.3.3" - statuses "^2.0.1" - -find-cache-dir@^3.2.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" - integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" - -flatted@^3.2.9: - version "3.3.3" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" - integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== - -follow-redirects@^1.15.6: - version "1.15.11" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" - integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== - -for-each@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" - integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== - dependencies: - is-callable "^1.2.7" - -foreground-child@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" - integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^3.0.2" - -foreground-child@^3.1.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" - integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== - dependencies: - cross-spawn "^7.0.6" - signal-exit "^4.0.1" - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== - -form-data@^2.5.0: - version "2.5.5" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.5.tgz#a5f6364ad7e4e67e95b4a07e2d8c6f711c74f624" - integrity sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.35" - safe-buffer "^5.2.1" - -form-data@^4.0.4, form-data@~4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" - integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.12" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" - integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== - -fromentries@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" - integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== - -fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^2.3.2, fsevents@^2.3.3, fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.1, get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -get-stream@^5.0.0, get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -getos@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" - integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== - dependencies: - async "^3.2.0" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== - dependencies: - assert-plus "^1.0.0" - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^10.3.10: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - -glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-dirs@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" - integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== - dependencies: - ini "2.0.0" - -globals@^13.19.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" - -globby@11.1.0, globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -gopd@^1.0.1, gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.9: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -graphql-subscriptions@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-3.0.0.tgz#820c846ef271414c08f64827b5c9a192801e1b6f" - integrity sha512-kZCdevgmzDjGAOqH7GlDmQXYAkuHoKpMlJrqF40HMPhUhM5ZWSFSxCwD/nSi6AkaijmMfsFhoJRGJ27UseCvRA== - -graphql-tag@^2.11.0: - version "2.12.6" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" - integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== - dependencies: - tslib "^2.1.0" - -graphql@^15.4.0: - version "15.10.1" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.10.1.tgz#e9ff3bb928749275477f748b14aa5c30dcad6f2f" - integrity sha512-BL/Xd/T9baO6NFzoMpiMD7YUZ62R6viR5tp/MULVEnbYJXZA//kRNW7J0j1w/wXArgL0sCxhDfK5dczSKn3+cg== - -graphql@^16.11.0: - version "16.11.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.11.0.tgz#96d17f66370678027fdf59b2d4c20b4efaa8a633" - integrity sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw== - -handlebars@^4.7.8: - version "4.7.8" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" - integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== - dependencies: - minimist "^1.2.5" - neo-async "^2.6.2" - source-map "^0.6.1" - wordwrap "^1.0.0" - optionalDependencies: - uglify-js "^3.1.4" - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hasha@^5.0.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" - integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== - dependencies: - is-stream "^2.0.0" - type-fest "^0.8.0" - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -helmet@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-8.1.0.tgz#f96d23fedc89e9476ecb5198181009c804b8b38c" - integrity sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg== - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -http-errors@2.0.0, http-errors@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-signature@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.4.0.tgz#dee5a9ba2bf49416abc544abd6d967f6a94c8c3f" - integrity sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg== - dependencies: - assert-plus "^1.0.0" - jsprim "^2.0.2" - sshpk "^1.18.0" - -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@0.6.3, iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -import-fresh@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" - integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - -ip-address@10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.0.1.tgz#a8180b783ce7788777d796286d61bce4276818ed" - integrity sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-core-module@^2.16.0: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== - dependencies: - hasown "^2.0.2" - -is-electron@2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/is-electron/-/is-electron-2.2.2.tgz#3778902a2044d76de98036f5dc58089ac4d80bb9" - integrity sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-installed-globally@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-path-inside@^3.0.2, is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-promise@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" - integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-typed-array@^1.1.14: - version "1.1.15" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" - integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== - dependencies: - which-typed-array "^1.1.16" - -is-typedarray@^1.0.0, is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isomorphic-unfetch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" - integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== - dependencies: - node-fetch "^2.6.1" - unfetch "^4.2.0" - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-hook@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" - integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== - dependencies: - append-transform "^2.0.0" - -istanbul-lib-instrument@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" - integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== - dependencies: - "@babel/core" "^7.7.5" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" - semver "^6.3.0" - -istanbul-lib-instrument@^5.0.4: - version "5.2.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" - integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== - dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" - -istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" - integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== - dependencies: - "@babel/core" "^7.23.9" - "@babel/parser" "^7.23.9" - "@istanbuljs/schema" "^0.1.3" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - -istanbul-lib-processinfo@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz#366d454cd0dcb7eb6e0e419378e60072c8626169" - integrity sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg== - dependencies: - archy "^1.0.0" - cross-spawn "^7.0.3" - istanbul-lib-coverage "^3.2.0" - p-map "^3.0.0" - rimraf "^3.0.0" - uuid "^8.3.2" - -istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.0.2, istanbul-reports@^3.1.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" - integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -jest-changed-files@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" - integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== - dependencies: - execa "^5.0.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - -jest-circus@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" - integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/expect" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - dedent "^1.0.0" - is-generator-fn "^2.0.0" - jest-each "^29.7.0" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-runtime "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - p-limit "^3.1.0" - pretty-format "^29.7.0" - pure-rand "^6.0.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-cli@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" - integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== - dependencies: - "@jest/core" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - chalk "^4.0.0" - create-jest "^29.7.0" - exit "^0.1.2" - import-local "^3.0.2" - jest-config "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - yargs "^17.3.1" - -jest-config@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" - integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== - dependencies: - "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.7.0" - "@jest/types" "^29.6.3" - babel-jest "^29.7.0" - chalk "^4.0.0" - ci-info "^3.2.0" - deepmerge "^4.2.2" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-circus "^29.7.0" - jest-environment-node "^29.7.0" - jest-get-type "^29.6.3" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-runner "^29.7.0" - jest-util "^29.7.0" - jest-validate "^29.7.0" - micromatch "^4.0.4" - parse-json "^5.2.0" - pretty-format "^29.7.0" - slash "^3.0.0" - strip-json-comments "^3.1.1" - -jest-diff@30.1.1: - version "30.1.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.1.1.tgz#cfe8327c059178affac17d4c003e7096ad19583c" - integrity sha512-LUU2Gx8EhYxpdzTR6BmjL1ifgOAQJQELTHOiPv9KITaKjZvJ9Jmgigx01tuZ49id37LorpGc9dPBPlXTboXScw== - dependencies: - "@jest/diff-sequences" "30.0.1" - "@jest/get-type" "30.1.0" - chalk "^4.1.2" - pretty-format "30.0.5" - -jest-diff@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" - integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.6.3" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-docblock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" - integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== - dependencies: - detect-newline "^3.0.0" - -jest-each@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" - integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== - dependencies: - "@jest/types" "^29.6.3" - chalk "^4.0.0" - jest-get-type "^29.6.3" - jest-util "^29.7.0" - pretty-format "^29.7.0" - -jest-environment-node@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" - integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" - -jest-get-type@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" - integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== - -jest-haste-map@30.1.0: - version "30.1.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.1.0.tgz#e54d84e07fac15ea3a98903b735048e36d7d2ed3" - integrity sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg== - dependencies: - "@jest/types" "30.0.5" - "@types/node" "*" - anymatch "^3.1.3" - fb-watchman "^2.0.2" - graceful-fs "^4.2.11" - jest-regex-util "30.0.1" - jest-util "30.0.5" - jest-worker "30.1.0" - micromatch "^4.0.8" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.3" - -jest-haste-map@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" - integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== - dependencies: - "@jest/types" "^29.6.3" - "@types/graceful-fs" "^4.1.3" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.9" - jest-regex-util "^29.6.3" - jest-util "^29.7.0" - jest-worker "^29.7.0" - micromatch "^4.0.4" - walker "^1.0.8" - optionalDependencies: - fsevents "^2.3.2" - -jest-leak-detector@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" - integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== - dependencies: - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-matcher-utils@30.1.1: - version "30.1.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.1.1.tgz#e45419d966cd2e5e7d7ade6da747035c6a3b8afc" - integrity sha512-SuH2QVemK48BNTqReti6FtjsMPFsSOD/ZzRxU1TttR7RiRsRSe78d03bb4Cx6D4bQC/80Q8U4VnaaAH9FlbZ9w== - dependencies: - "@jest/get-type" "30.1.0" - chalk "^4.1.2" - jest-diff "30.1.1" - pretty-format "30.0.5" - -jest-matcher-utils@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" - integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== - dependencies: - chalk "^4.0.0" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - pretty-format "^29.7.0" - -jest-message-util@30.1.0: - version "30.1.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.1.0.tgz#653a9bb1a33306eddf13455ce0666ba621b767c4" - integrity sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg== - dependencies: - "@babel/code-frame" "^7.27.1" - "@jest/types" "30.0.5" - "@types/stack-utils" "^2.0.3" - chalk "^4.1.2" - graceful-fs "^4.2.11" - micromatch "^4.0.8" - pretty-format "30.0.5" - slash "^3.0.0" - stack-utils "^2.0.6" - -jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@30.0.5: - version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" - integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== - dependencies: - "@jest/types" "30.0.5" - "@types/node" "*" - jest-util "30.0.5" - -jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - -jest-pnp-resolver@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" - integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== - -jest-regex-util@30.0.1: - version "30.0.1" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" - integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== - -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - -jest-resolve-dependencies@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" - integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== - dependencies: - jest-regex-util "^29.6.3" - jest-snapshot "^29.7.0" - -jest-resolve@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" - integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== - dependencies: - chalk "^4.0.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-pnp-resolver "^1.2.2" - jest-util "^29.7.0" - jest-validate "^29.7.0" - resolve "^1.20.0" - resolve.exports "^2.0.0" - slash "^3.0.0" - -jest-runner@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" - integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== - dependencies: - "@jest/console" "^29.7.0" - "@jest/environment" "^29.7.0" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.13.1" - graceful-fs "^4.2.9" - jest-docblock "^29.7.0" - jest-environment-node "^29.7.0" - jest-haste-map "^29.7.0" - jest-leak-detector "^29.7.0" - jest-message-util "^29.7.0" - jest-resolve "^29.7.0" - jest-runtime "^29.7.0" - jest-util "^29.7.0" - jest-watcher "^29.7.0" - jest-worker "^29.7.0" - p-limit "^3.1.0" - source-map-support "0.5.13" - -jest-runtime@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" - integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== - dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/globals" "^29.7.0" - "@jest/source-map" "^29.6.3" - "@jest/test-result" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - cjs-module-lexer "^1.0.0" - collect-v8-coverage "^1.0.0" - glob "^7.1.3" - graceful-fs "^4.2.9" - jest-haste-map "^29.7.0" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-regex-util "^29.6.3" - jest-resolve "^29.7.0" - jest-snapshot "^29.7.0" - jest-util "^29.7.0" - slash "^3.0.0" - strip-bom "^4.0.0" - -jest-snapshot@30.1.1: - version "30.1.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.1.1.tgz#ef9bfdc22b4e807622e14fe32fd40745a2c031e5" - integrity sha512-7/iBEzoJqEt2TjkQY+mPLHP8cbPhLReZVkkxjTMzIzoTC4cZufg7HzKo/n9cIkXKj2LG0x3mmBHsZto+7TOmFg== - dependencies: - "@babel/core" "^7.27.4" - "@babel/generator" "^7.27.5" - "@babel/plugin-syntax-jsx" "^7.27.1" - "@babel/plugin-syntax-typescript" "^7.27.1" - "@babel/types" "^7.27.3" - "@jest/expect-utils" "30.1.1" - "@jest/get-type" "30.1.0" - "@jest/snapshot-utils" "30.1.1" - "@jest/transform" "30.1.1" - "@jest/types" "30.0.5" - babel-preset-current-node-syntax "^1.1.0" - chalk "^4.1.2" - expect "30.1.1" - graceful-fs "^4.2.11" - jest-diff "30.1.1" - jest-matcher-utils "30.1.1" - jest-message-util "30.1.0" - jest-util "30.0.5" - pretty-format "30.0.5" - semver "^7.7.2" - synckit "^0.11.8" - -jest-snapshot@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" - integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== - dependencies: - "@babel/core" "^7.11.6" - "@babel/generator" "^7.7.2" - "@babel/plugin-syntax-jsx" "^7.7.2" - "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.7.0" - "@jest/transform" "^29.7.0" - "@jest/types" "^29.6.3" - babel-preset-current-node-syntax "^1.0.0" - chalk "^4.0.0" - expect "^29.7.0" - graceful-fs "^4.2.9" - jest-diff "^29.7.0" - jest-get-type "^29.6.3" - jest-matcher-utils "^29.7.0" - jest-message-util "^29.7.0" - jest-util "^29.7.0" - natural-compare "^1.4.0" - pretty-format "^29.7.0" - semver "^7.5.3" - -jest-util@30.0.5: - version "30.0.5" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" - integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== - dependencies: - "@jest/types" "30.0.5" - "@types/node" "*" - chalk "^4.1.2" - ci-info "^4.2.0" - graceful-fs "^4.2.11" - picomatch "^4.0.2" - -jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-validate@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" - integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== - dependencies: - "@jest/types" "^29.6.3" - camelcase "^6.2.0" - chalk "^4.0.0" - jest-get-type "^29.6.3" - leven "^3.1.0" - pretty-format "^29.7.0" - -jest-watcher@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" - integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== - dependencies: - "@jest/test-result" "^29.7.0" - "@jest/types" "^29.6.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.13.1" - jest-util "^29.7.0" - string-length "^4.0.1" - -jest-worker@30.1.0: - version "30.1.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.1.0.tgz#a89c36772be449d4bdb60697fb695a1673b12ac2" - integrity sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA== - dependencies: - "@types/node" "*" - "@ungap/structured-clone" "^1.3.0" - jest-util "30.0.5" - merge-stream "^2.0.0" - supports-color "^8.1.1" - -jest-worker@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" - integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== - dependencies: - "@types/node" "*" - jest-util "^29.7.0" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" - integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== - dependencies: - "@jest/core" "^29.7.0" - "@jest/types" "^29.6.3" - import-local "^3.0.2" - jest-cli "^29.7.0" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@4.1.0, js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== - -jsesc@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" - integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonfile@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" - integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jsonwebtoken@^9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" - integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^7.5.4" - -jsprim@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" - integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.4.0" - verror "1.10.0" - -jwa@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" - integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== - dependencies: - buffer-equal-constant-time "^1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - -keyv@^4.5.3: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -lazy-ass@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" - integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -listr2@^3.8.3: - version "3.14.0" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" - integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== - dependencies: - cli-truncate "^2.1.0" - colorette "^2.0.16" - log-update "^4.0.0" - p-map "^4.0.0" - rfdc "^1.3.0" - rxjs "^7.5.1" - through "^2.3.8" - wrap-ansi "^7.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.flattendeep@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" - integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== - -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.once@^4.0.0, lodash.once@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== - -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== - -lodash@^4.17.20, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -log-update@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" - integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== - dependencies: - ansi-escapes "^4.3.0" - cli-cursor "^3.1.0" - slice-ansi "^4.0.0" - wrap-ansi "^6.2.0" - -loglevel@^1.6.8: - version "1.9.2" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08" - integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg== - -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - -"lru-cache@7.10.1 - 7.13.1": - version "7.13.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4" - integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ== - -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - -lru-cache@^11.0.0, lru-cache@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117" - integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^3.0.0, make-dir@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -make-error@^1.1.1, make-error@^1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -media-typer@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" - integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -merge-descriptors@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" - integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.4, micromatch@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-db@^1.54.0: - version "1.54.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" - integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - -mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime-types@^3.0.0, mime-types@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" - integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== - dependencies: - mime-db "^1.54.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@9.0.3: - version "9.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.5, minimist@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - -morgan@^1.10.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.1.tgz#4e02e6a4465a48e26af540191593955d17f61570" - integrity sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A== - dependencies: - basic-auth "~2.0.1" - debug "2.6.9" - depd "~2.0.0" - on-finished "~2.3.0" - on-headers "~1.1.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.3, ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -negotiator@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" - integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-abort-controller@^3.0.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" - integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== - -node-addon-api@^8.3.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.5.0.tgz#c91b2d7682fa457d2e1c388150f0dff9aafb8f3f" - integrity sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A== - -node-dogstatsd@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/node-dogstatsd/-/node-dogstatsd-0.0.7.tgz#544d274c91a413426a2f3c40ff7b8e8813cfd930" - integrity sha512-Ssr+5ySG89xL3ToV2KuFPqK/+lCOx68Wx0fbVvJUx+7eBKxihGRia/Rfik21BxPNdZb1Dn+4RsGZyglyaj56ew== - -node-fetch@^2.6.1, node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - -node-gyp-build@^4.8.4: - version "4.8.4" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" - integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== - -node-preload@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" - integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== - dependencies: - process-on-spawn "^1.0.0" - -node-releases@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" - integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.0, npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nyc@15.1.0: - version "15.1.0" - resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" - integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== - dependencies: - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - caching-transform "^4.0.0" - convert-source-map "^1.7.0" - decamelize "^1.2.0" - find-cache-dir "^3.2.0" - find-up "^4.1.0" - foreground-child "^2.0.0" - get-package-type "^0.1.0" - glob "^7.1.6" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-hook "^3.0.0" - istanbul-lib-instrument "^4.0.0" - istanbul-lib-processinfo "^2.0.2" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - make-dir "^3.0.0" - node-preload "^0.2.1" - p-map "^3.0.0" - process-on-spawn "^1.0.0" - resolve-from "^5.0.0" - rimraf "^3.0.0" - signal-exit "^3.0.2" - spawn-wrap "^2.0.0" - test-exclude "^6.0.0" - yargs "^15.0.2" - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.3: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -on-finished@2.4.1, on-finished@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== - dependencies: - ee-first "1.1.1" - -on-headers@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" - integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.0, onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" - -ospath@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" - integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-limit@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985" - integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ== - dependencies: - yocto-queue "^1.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== - dependencies: - aggregate-error "^3.0.0" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-queue@^6.6.1: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-retry@^4.0.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - -p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-hash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" - integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== - dependencies: - graceful-fs "^4.1.15" - hasha "^5.0.0" - lodash.flattendeep "^4.4.0" - release-zalgo "^1.0.0" - -package-json-from-dist@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" - integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parseurl@^1.3.3, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-scurry@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -path-to-regexp@0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== - -path-to-regexp@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" - integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== - -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" - integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== - -pify@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -pirates@^4.0.4, pirates@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" - integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== - -pkg-dir@^4.1.0, pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -possible-typed-array-names@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" - integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier@^3.2.4: - version "3.6.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393" - integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== - -pretty-bytes@^5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" - integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== - -pretty-format@30.0.5, pretty-format@^30.0.0: - version "30.0.5" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" - integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== - dependencies: - "@jest/schemas" "30.0.5" - ansi-styles "^5.2.0" - react-is "^18.3.1" - -pretty-format@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" - integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== - dependencies: - "@jest/schemas" "^29.6.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - -process-on-spawn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.1.0.tgz#9d5999ba87b3bf0a8acb05322d69f2f5aa4fb763" - integrity sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q== - dependencies: - fromentries "^1.2.0" - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -proxy-addr@^2.0.7, proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -proxy-from-env@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" - integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A== - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -pump@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" - integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -pure-rand@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" - integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== - -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - -qs@6.14.0, qs@^6.14.0: - version "6.14.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" - integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== - dependencies: - side-channel "^1.1.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.2, raw-body@^2.3.3: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -raw-body@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" - integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.6.3" - unpipe "1.0.0" - -react-is@^18.0.0, react-is@^18.3.1: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -release-zalgo@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" - integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA== - dependencies: - es6-error "^4.0.1" - -request-progress@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" - integrity sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg== - dependencies: - throttleit "^1.0.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve.exports@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" - integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== - -resolve@^1.20.0: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== - dependencies: - is-core-module "^2.16.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -retry@0.13.1, retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - -reusify@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" - integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== - -rfdc@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" - integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== - -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -router@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" - integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== - dependencies: - debug "^4.4.0" - depd "^2.0.0" - is-promise "^4.0.0" - parseurl "^1.3.3" - path-to-regexp "^8.0.0" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^7.5.1: - version "7.8.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" - integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== - dependencies: - tslib "^2.1.0" - -safe-buffer@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.2, semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: - version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" - integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== - -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -send@^1.1.0, send@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" - integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw== - dependencies: - debug "^4.3.5" - encodeurl "^2.0.0" - escape-html "^1.0.3" - etag "^1.8.1" - fresh "^2.0.0" - http-errors "^2.0.0" - mime-types "^3.0.1" - ms "^2.1.3" - on-finished "^2.4.1" - range-parser "^1.2.1" - statuses "^2.0.1" - -serve-static@1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.19.0" - -serve-static@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9" - integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ== - dependencies: - encodeurl "^2.0.0" - escape-html "^1.0.3" - parseurl "^1.3.3" - send "^1.2.0" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -set-function-length@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -sha.js@^2.4.11: - version "2.4.12" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f" - integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w== - dependencies: - inherits "^2.0.4" - safe-buffer "^5.2.1" - to-buffer "^1.2.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.0.6, side-channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slice-ansi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" - integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -source-map-support@0.5.13: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spawn-wrap@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" - integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== - dependencies: - foreground-child "^2.0.0" - is-windows "^1.0.2" - make-dir "^3.0.0" - rimraf "^3.0.0" - signal-exit "^3.0.2" - which "^2.0.1" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -sshpk@^1.18.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" - integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -stack-utils@^2.0.3, stack-utils@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" - integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== - dependencies: - escape-string-regexp "^2.0.0" - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -statuses@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" - integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0, supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -synckit@^0.11.8: - version "0.11.11" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0" - integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw== - dependencies: - "@pkgr/core" "^0.2.9" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -throttleit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.1.tgz#304ec51631c3b770c65c6c6f76938b384000f4d5" - integrity sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ== - -through@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -tldts-core@^6.1.86: - version "6.1.86" - resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.86.tgz#a93e6ed9d505cb54c542ce43feb14c73913265d8" - integrity sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA== - -tldts@^6.1.32: - version "6.1.86" - resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.86.tgz#087e0555b31b9725ee48ca7e77edc56115cd82f7" - integrity sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ== - dependencies: - tldts-core "^6.1.86" - -tmp@~0.2.3: - version "0.2.5" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" - integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== - -tmpl@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-buffer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.1.tgz#2ce650cdb262e9112a18e65dc29dcb513c8155e0" - integrity sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ== - dependencies: - isarray "^2.0.5" - safe-buffer "^5.2.1" - typed-array-buffer "^1.0.3" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -tough-cookie@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.2.tgz#66d774b4a1d9e12dc75089725af3ac75ec31bed7" - integrity sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A== - dependencies: - tldts "^6.1.32" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -tree-kill@1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - -tree-sitter-python@^0.23.6: - version "0.23.6" - resolved "https://registry.yarnpkg.com/tree-sitter-python/-/tree-sitter-python-0.23.6.tgz#807e50c415a761c613614f902432f563e85b51b6" - integrity sha512-yIM9z0oxKIxT7bAtPOhgoVl6gTXlmlIhue7liFT4oBPF/lha7Ha4dQBS82Av6hMMRZoVnFJI8M6mL+SwWoLD3A== - dependencies: - node-addon-api "^8.3.0" - node-gyp-build "^4.8.4" - -tree-sitter@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/tree-sitter/-/tree-sitter-0.25.0.tgz#d9d94ba00b501df49826c10c0f74037b890788eb" - integrity sha512-PGZZzFW63eElZJDe/b/R/LbsjDDYJa5UEjLZJB59RQsMX+fo0j54fqBPn1MGKav/QNa0JR0zBiVaikYDWCj5KQ== - dependencies: - node-addon-api "^8.3.0" - node-gyp-build "^4.8.4" - -ts-api-utils@^1.0.1: - version "1.4.3" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" - integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== - -ts-jest@^29.1.1: - version "29.4.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.1.tgz#42d33beb74657751d315efb9a871fe99e3b9b519" - integrity sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw== - dependencies: - bs-logger "^0.2.6" - fast-json-stable-stringify "^2.1.0" - handlebars "^4.7.8" - json5 "^2.2.3" - lodash.memoize "^4.1.2" - make-error "^1.3.6" - semver "^7.7.2" - type-fest "^4.41.0" - yargs-parser "^21.1.1" - -ts-node@^10.9.2: - version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - -tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.3: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -tsscmp@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" - integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^0.8.0: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -type-fest@^4.41.0: - version "4.41.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" - integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== - -type-is@^2.0.0, type-is@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" - integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== - dependencies: - content-type "^1.0.5" - media-typer "^1.1.0" - mime-types "^3.0.0" - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typed-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" - integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== - dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - is-typed-array "^1.1.14" - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typescript@^5.3.3: - version "5.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" - integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== - -uglify-js@^3.1.4: - version "3.19.3" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" - integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== - -undici-types@~6.21.0: - version "6.21.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" - integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== - -undici-types@~7.10.0: - version "7.10.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.10.0.tgz#4ac2e058ce56b462b056e629cc6a02393d3ff350" - integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag== - -unfetch@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" - integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== - -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -untildify@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" - integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== - -update-browserslist-db@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" - integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912" - integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -uuid@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" - integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== - -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - -v8-to-istanbul@^9.0.1: - version "9.3.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" - integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== - dependencies: - "@jridgewell/trace-mapping" "^0.3.12" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" - -value-or-promise@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.11.tgz#3e90299af31dd014fe843fe309cefa7c1d94b140" - integrity sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg== - -value-or-promise@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.12.tgz#0e5abfeec70148c78460a849f6b003ea7986f15c" - integrity sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q== - -vary@^1, vary@^1.1.2, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -walker@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-mimetype@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" - integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== - -whatwg-mimetype@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" - integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which-module@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" - integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== - -which-typed-array@^1.1.16: - version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" - integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.8" - call-bound "^1.0.4" - for-each "^0.3.5" - get-proto "^1.0.1" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -write-file-atomic@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" - integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^3.0.7" - -write-file-atomic@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" - integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== - dependencies: - imurmurhash "^0.1.4" - signal-exit "^4.0.1" - -ws@^8.18.3: - version "8.18.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" - integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== - -xss@^1.0.8: - version "1.0.15" - resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.15.tgz#96a0e13886f0661063028b410ed1b18670f4e59a" - integrity sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg== - dependencies: - commander "^2.20.3" - cssfilter "0.0.10" - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^15.0.2, yargs@^15.3.1: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" - -yargs@^17.3.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -yocto-queue@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.2.1.tgz#36d7c4739f775b3cbc28e6136e21aa057adec418" - integrity sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg== +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@anthropic-ai/claude-code@^1.0.89": + version "1.0.95" + resolved "https://registry.yarnpkg.com/@anthropic-ai/claude-code/-/claude-code-1.0.95.tgz#471d8933d58912677a8615a0fd0a1fac0c3ac2cc" + integrity sha512-VLwfezJ8vnNpkdUdyMORQ1JXx/C7GFd8MLiJTXXThvMTxhNEr8IhAY/bExNrtdkWx2+aipGocRLgNZrUon9HMw== + optionalDependencies: + "@img/sharp-darwin-arm64" "^0.33.5" + "@img/sharp-darwin-x64" "^0.33.5" + "@img/sharp-linux-arm" "^0.33.5" + "@img/sharp-linux-arm64" "^0.33.5" + "@img/sharp-linux-x64" "^0.33.5" + "@img/sharp-win32-x64" "^0.33.5" + +"@apollo/cache-control-types@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@apollo/cache-control-types/-/cache-control-types-1.0.3.tgz#5da62cf64c3b4419dabfef4536b57a40c8ff0b47" + integrity sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g== + +"@apollo/protobufjs@1.2.6": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.6.tgz#d601e65211e06ae1432bf5993a1a0105f2862f27" + integrity sha512-Wqo1oSHNUj/jxmsVp4iR3I480p6qdqHikn38lKrFhfzcDJ7lwd7Ck7cHRl4JE81tWNArl77xhnG/OkZhxKBYOw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.0" + "@types/node" "^10.1.0" + long "^4.0.0" + +"@apollo/protobufjs@1.2.7": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.7.tgz#3a8675512817e4a046a897e5f4f16415f16a7d8a" + integrity sha512-Lahx5zntHPZia35myYDBRuF58tlwPskwHc5CWBZC/4bMKB6siTBWwtMrkqXcsNwQiFSzSx5hKdRPUmemrEp3Gg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.0" + long "^4.0.0" + +"@apollo/server-gateway-interface@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@apollo/server-gateway-interface/-/server-gateway-interface-2.0.0.tgz#16e375f8edd302641ac60815f6c448e93a2a48a0" + integrity sha512-3HEMD6fSantG2My3jWkb9dvfkF9vJ4BDLRjMgsnD790VINtuPaEp+h3Hg9HOHiWkML6QsOhnaRqZ+gvhp3y8Nw== + dependencies: + "@apollo/usage-reporting-protobuf" "^4.1.1" + "@apollo/utils.fetcher" "^3.0.0" + "@apollo/utils.keyvaluecache" "^4.0.0" + "@apollo/utils.logger" "^3.0.0" + +"@apollo/server@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@apollo/server/-/server-5.0.0.tgz#c7d120e22db66743aa4b21d380062f51b194a47e" + integrity sha512-PHopOm7pr69k7eDJvCBU4cZy9Z19qyCFKB9/luLnf2YCatu2WOYhoQPNr3dAoe//xv0RZFhxXbRcnK6IXIP7Nw== + dependencies: + "@apollo/cache-control-types" "^1.0.3" + "@apollo/server-gateway-interface" "^2.0.0" + "@apollo/usage-reporting-protobuf" "^4.1.1" + "@apollo/utils.createhash" "^3.0.0" + "@apollo/utils.fetcher" "^3.0.0" + "@apollo/utils.isnodelike" "^3.0.0" + "@apollo/utils.keyvaluecache" "^4.0.0" + "@apollo/utils.logger" "^3.0.0" + "@apollo/utils.usagereporting" "^2.1.0" + "@apollo/utils.withrequired" "^3.0.0" + "@graphql-tools/schema" "^10.0.0" + async-retry "^1.2.1" + body-parser "^2.2.0" + cors "^2.8.5" + finalhandler "^2.1.0" + loglevel "^1.6.8" + lru-cache "^11.1.0" + negotiator "^1.0.0" + uuid "^11.1.0" + whatwg-mimetype "^4.0.0" + +"@apollo/usage-reporting-protobuf@^4.0.0", "@apollo/usage-reporting-protobuf@^4.1.0", "@apollo/usage-reporting-protobuf@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.1.tgz#407c3d18c7fbed7a264f3b9a3812620b93499de1" + integrity sha512-u40dIUePHaSKVshcedO7Wp+mPiZsaU6xjv9J+VyxpoU/zL6Jle+9zWeG98tr/+SZ0nZ4OXhrbb8SNr0rAPpIDA== + dependencies: + "@apollo/protobufjs" "1.2.7" + +"@apollo/utils.createhash@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@apollo/utils.createhash/-/utils.createhash-3.0.1.tgz#19fc6507e71d44303c2fffa51052305d689d288f" + integrity sha512-CKrlySj4eQYftBE5MJ8IzKwIibQnftDT7yGfsJy5KSEEnLlPASX0UTpbKqkjlVEwPPd4mEwI7WOM7XNxEuO05A== + dependencies: + "@apollo/utils.isnodelike" "^3.0.0" + sha.js "^2.4.11" + +"@apollo/utils.dropunuseddefinitions@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-1.1.0.tgz#02b04006442eaf037f4c4624146b12775d70d929" + integrity sha512-jU1XjMr6ec9pPoL+BFWzEPW7VHHulVdGKMkPAMiCigpVIT11VmCbnij0bWob8uS3ODJ65tZLYKAh/55vLw2rbg== + +"@apollo/utils.dropunuseddefinitions@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@apollo/utils.dropunuseddefinitions/-/utils.dropunuseddefinitions-2.0.1.tgz#916cd912cbd88769d3b0eab2d24f4674eeda8124" + integrity sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA== + +"@apollo/utils.fetcher@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.fetcher/-/utils.fetcher-3.1.0.tgz#0639c30a5ac57e3e62784b180495023f5e886fe0" + integrity sha512-Z3QAyrsQkvrdTuHAFwWDNd+0l50guwoQUoaDQssLOjkmnmVuvXlJykqlEJolio+4rFwBnWdoY1ByFdKaQEcm7A== + +"@apollo/utils.isnodelike@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.isnodelike/-/utils.isnodelike-3.0.0.tgz#0b51cb20e24850e49851f8b038fae24fd4a8beaf" + integrity sha512-xrjyjfkzunZ0DeF6xkHaK5IKR8F1FBq6qV+uZ+h9worIF/2YSzA0uoBxGv6tbTeo9QoIQnRW4PVFzGix5E7n/g== + +"@apollo/utils.keyvaluecache@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-1.0.2.tgz#2bfe358c4d82f3a0950518451996758c52613f57" + integrity sha512-p7PVdLPMnPzmXSQVEsy27cYEjVON+SH/Wb7COyW3rQN8+wJgT1nv9jZouYtztWW8ZgTkii5T6tC9qfoDREd4mg== + dependencies: + "@apollo/utils.logger" "^1.0.0" + lru-cache "7.10.1 - 7.13.1" + +"@apollo/utils.keyvaluecache@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-4.0.0.tgz#c183fcc87263589e9e402651b2bb692e9f2353e0" + integrity sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw== + dependencies: + "@apollo/utils.logger" "^3.0.0" + lru-cache "^11.0.0" + +"@apollo/utils.logger@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@apollo/utils.logger/-/utils.logger-1.0.1.tgz#aea0d1bb7ceb237f506c6bbf38f10a555b99a695" + integrity sha512-XdlzoY7fYNK4OIcvMD2G94RoFZbzTQaNP0jozmqqMudmaGo2I/2Jx71xlDJ801mWA/mbYRihyaw6KJii7k5RVA== + +"@apollo/utils.logger@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.logger/-/utils.logger-3.0.0.tgz#5b67de1e476bdfa8be70bfaa05c7e65a6a427960" + integrity sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg== + +"@apollo/utils.printwithreducedwhitespace@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-1.1.0.tgz#c466299a4766eef8577a2a64c8f27712e8bd7e30" + integrity sha512-GfFSkAv3n1toDZ4V6u2d7L4xMwLA+lv+6hqXicMN9KELSJ9yy9RzuEXaX73c/Ry+GzRsBy/fdSUGayGqdHfT2Q== + +"@apollo/utils.printwithreducedwhitespace@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@apollo/utils.printwithreducedwhitespace/-/utils.printwithreducedwhitespace-2.0.1.tgz#f4fadea0ae849af2c19c339cc5420d1ddfaa905e" + integrity sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg== + +"@apollo/utils.removealiases@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.removealiases/-/utils.removealiases-1.0.0.tgz#75f6d83098af1fcae2d3beb4f515ad4a8452a8c1" + integrity sha512-6cM8sEOJW2LaGjL/0vHV0GtRaSekrPQR4DiywaApQlL9EdROASZU5PsQibe2MWeZCOhNrPRuHh4wDMwPsWTn8A== + +"@apollo/utils.removealiases@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@apollo/utils.removealiases/-/utils.removealiases-2.0.1.tgz#2873c93d72d086c60fc0d77e23d0f75e66a2598f" + integrity sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA== + +"@apollo/utils.sortast@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.sortast/-/utils.sortast-1.1.0.tgz#93218c7008daf3e2a0725196085a33f5aab5ad07" + integrity sha512-VPlTsmUnOwzPK5yGZENN069y6uUHgeiSlpEhRnLFYwYNoJHsuJq2vXVwIaSmts015WTPa2fpz1inkLYByeuRQA== + dependencies: + lodash.sortby "^4.7.0" + +"@apollo/utils.sortast@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@apollo/utils.sortast/-/utils.sortast-2.0.1.tgz#58c90bb8bd24726346b61fa51ba7fcf06e922ef7" + integrity sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw== + dependencies: + lodash.sortby "^4.7.0" + +"@apollo/utils.stripsensitiveliterals@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-1.2.0.tgz#4920651f36beee8e260e12031a0c5863ad0c7b28" + integrity sha512-E41rDUzkz/cdikM5147d8nfCFVKovXxKBcjvLEQ7bjZm/cg9zEcXvS6vFY8ugTubI3fn6zoqo0CyU8zT+BGP9w== + +"@apollo/utils.stripsensitiveliterals@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@apollo/utils.stripsensitiveliterals/-/utils.stripsensitiveliterals-2.0.1.tgz#2f3350483be376a98229f90185eaf19888323132" + integrity sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA== + +"@apollo/utils.usagereporting@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@apollo/utils.usagereporting/-/utils.usagereporting-1.0.1.tgz#3c70b49e554771659576fe35381c7a4b321d27fd" + integrity sha512-6dk+0hZlnDbahDBB2mP/PZ5ybrtCJdLMbeNJD+TJpKyZmSY6bA3SjI8Cr2EM9QA+AdziywuWg+SgbWUF3/zQqQ== + dependencies: + "@apollo/usage-reporting-protobuf" "^4.0.0" + "@apollo/utils.dropunuseddefinitions" "^1.1.0" + "@apollo/utils.printwithreducedwhitespace" "^1.1.0" + "@apollo/utils.removealiases" "1.0.0" + "@apollo/utils.sortast" "^1.1.0" + "@apollo/utils.stripsensitiveliterals" "^1.2.0" + +"@apollo/utils.usagereporting@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.usagereporting/-/utils.usagereporting-2.1.0.tgz#11bca6a61fcbc6e6d812004503b38916e74313f4" + integrity sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ== + dependencies: + "@apollo/usage-reporting-protobuf" "^4.1.0" + "@apollo/utils.dropunuseddefinitions" "^2.0.1" + "@apollo/utils.printwithreducedwhitespace" "^2.0.1" + "@apollo/utils.removealiases" "2.0.1" + "@apollo/utils.sortast" "^2.0.1" + "@apollo/utils.stripsensitiveliterals" "^2.0.1" + +"@apollo/utils.withrequired@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@apollo/utils.withrequired/-/utils.withrequired-3.0.0.tgz#1342bab438528d09c41e43ce33cb976fa9c99e52" + integrity sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA== + +"@apollographql/apollo-tools@^0.5.3": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@apollographql/apollo-tools/-/apollo-tools-0.5.4.tgz#cb3998c6cf12e494b90c733f44dd9935e2d8196c" + integrity sha512-shM3q7rUbNyXVVRkQJQseXv6bnYM3BUma/eZhwXR4xsuM+bqWnJKvW7SAfRjP7LuSCocrexa5AXhjjawNHrIlw== + +"@apollographql/graphql-playground-html@1.6.29": + version "1.6.29" + resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz#a7a646614a255f62e10dcf64a7f68ead41dec453" + integrity sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA== + dependencies: + xss "^1.0.8" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" + integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.27.4", "@babel/core@^7.7.5": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.3.tgz#aceddde69c5d1def69b839d09efa3e3ff59c97cb" + integrity sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.3" + "@babel/parser" "^7.28.3" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.3" + "@babel/types" "^7.28.2" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.27.5", "@babel/generator@^7.28.3", "@babel/generator@^7.7.2": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.3.tgz#b83156c0a2232c133d1b535dd5d3452119c7e441" + integrity sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.2" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" + integrity sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA== + dependencies: + "@babel/types" "^7.28.2" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.27.1", "@babel/plugin-syntax-jsx@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.27.1", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/template@^7.27.2", "@babel/template@^7.3.3": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.24.0", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.3.tgz#6911a10795d2cce43ec6a28cffc440cca2593434" + integrity sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.3" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.2" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.0", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.3.3": + version "7.28.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" + integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@cypress/code-coverage@^3.12.0": + version "3.14.6" + resolved "https://registry.yarnpkg.com/@cypress/code-coverage/-/code-coverage-3.14.6.tgz#9ea7f1211e714f0ec314d4ea939cd20929950cbd" + integrity sha512-aGDJG89uR5CRFbAIs+uFb3f6yPQTf/cMD6D9BZBo/gJRS1vZXli4lIGYU2DoJK4gMP+x9mAQSRexN91oizCusg== + dependencies: + "@cypress/webpack-preprocessor" "^6.0.0" + chalk "4.1.2" + dayjs "1.11.13" + debug "4.4.0" + execa "4.1.0" + globby "11.1.0" + istanbul-lib-coverage "^3.0.0" + js-yaml "4.1.0" + nyc "15.1.0" + +"@cypress/request@^3.0.6": + version "3.0.9" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.9.tgz#8ed6e08fea0c62998b5552301023af7268f11625" + integrity sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~4.0.4" + http-signature "~1.4.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + performance-now "^2.1.0" + qs "6.14.0" + safe-buffer "^5.1.2" + tough-cookie "^5.0.0" + tunnel-agent "^0.6.0" + uuid "^8.3.2" + +"@cypress/webpack-preprocessor@^6.0.0": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-6.0.4.tgz#537f6bee2a971979adf35d925609a86f9e4aff30" + integrity sha512-ly+EcabWWbhrSPr2J/njQX7Y3da+QqOmFg8Og/MVmLxhDLKIzr2WhTdgzDYviPTLx/IKsdb41cc2RLYp6mSBRA== + dependencies: + bluebird "3.7.1" + debug "^4.3.4" + lodash "^4.17.20" + semver "^7.3.2" + +"@cypress/xvfb@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" + integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== + dependencies: + debug "^3.1.0" + lodash.once "^4.1.1" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" + integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@graphql-tools/merge@8.3.1": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.1.tgz#06121942ad28982a14635dbc87b5d488a041d722" + integrity sha512-BMm99mqdNZbEYeTPK3it9r9S6rsZsQKtlqJsSBknAclXq2pGEfOxjcIZi+kBSkHZKPKCRrYDd5vY0+rUmIHVLg== + dependencies: + "@graphql-tools/utils" "8.9.0" + tslib "^2.4.0" + +"@graphql-tools/merge@^8.4.1": + version "8.4.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.4.2.tgz#95778bbe26b635e8d2f60ce9856b388f11fe8288" + integrity sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw== + dependencies: + "@graphql-tools/utils" "^9.2.1" + tslib "^2.4.0" + +"@graphql-tools/merge@^9.1.1": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-9.1.1.tgz#6a51775f5ba6bdef213def1fd277536f9468b44c" + integrity sha512-BJ5/7Y7GOhTuvzzO5tSBFL4NGr7PVqTJY3KeIDlVTT8YLcTXtBR+hlrC3uyEym7Ragn+zyWdHeJ9ev+nRX1X2w== + dependencies: + "@graphql-tools/utils" "^10.9.1" + tslib "^2.4.0" + +"@graphql-tools/mock@^8.1.2": + version "8.7.20" + resolved "https://registry.yarnpkg.com/@graphql-tools/mock/-/mock-8.7.20.tgz#c83ae0f1940d194a3982120c9c85f3ac6b4f7f20" + integrity sha512-ljcHSJWjC/ZyzpXd5cfNhPI7YljRVvabKHPzKjEs5ElxWu2cdlLGvyNYepApXDsM/OJG/2xuhGM+9GWu5gEAPQ== + dependencies: + "@graphql-tools/schema" "^9.0.18" + "@graphql-tools/utils" "^9.2.1" + fast-json-stable-stringify "^2.1.0" + tslib "^2.4.0" + +"@graphql-tools/schema@^10.0.0", "@graphql-tools/schema@^10.0.25": + version "10.0.25" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-10.0.25.tgz#73de08bc765f482cde334562a1bebe95a2e5a3c4" + integrity sha512-/PqE8US8kdQ7lB9M5+jlW8AyVjRGCKU7TSktuW3WNKSKmDO0MK1wakvb5gGdyT49MjAIb4a3LWxIpwo5VygZuw== + dependencies: + "@graphql-tools/merge" "^9.1.1" + "@graphql-tools/utils" "^10.9.1" + tslib "^2.4.0" + +"@graphql-tools/schema@^8.0.0": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.5.1.tgz#c2f2ff1448380919a330312399c9471db2580b58" + integrity sha512-0Esilsh0P/qYcB5DKQpiKeQs/jevzIadNTaT0jeWklPMwNbT7yMX4EqZany7mbeRRlSRwMzNzL5olyFdffHBZg== + dependencies: + "@graphql-tools/merge" "8.3.1" + "@graphql-tools/utils" "8.9.0" + tslib "^2.4.0" + value-or-promise "1.0.11" + +"@graphql-tools/schema@^9.0.18": + version "9.0.19" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-9.0.19.tgz#c4ad373b5e1b8a0cf365163435b7d236ebdd06e7" + integrity sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w== + dependencies: + "@graphql-tools/merge" "^8.4.1" + "@graphql-tools/utils" "^9.2.1" + tslib "^2.4.0" + value-or-promise "^1.0.12" + +"@graphql-tools/utils@8.9.0": + version "8.9.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.9.0.tgz#c6aa5f651c9c99e1aca55510af21b56ec296cdb7" + integrity sha512-pjJIWH0XOVnYGXCqej8g/u/tsfV4LvLlj0eATKQu5zwnxd/TiTHq7Cg313qUPTFFHZ3PP5wJ15chYVtLDwaymg== + dependencies: + tslib "^2.4.0" + +"@graphql-tools/utils@^10.9.1": + version "10.9.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-10.9.1.tgz#ff4067256f2080db0c66f4475858f29a8da65ecf" + integrity sha512-B1wwkXk9UvU7LCBkPs8513WxOQ2H8Fo5p8HR1+Id9WmYE5+bd51vqN+MbrqvWczHCH2gwkREgHJN88tE0n1FCw== + dependencies: + "@graphql-typed-document-node/core" "^3.1.1" + "@whatwg-node/promise-helpers" "^1.0.0" + cross-inspect "1.0.1" + dset "^3.1.4" + tslib "^2.4.0" + +"@graphql-tools/utils@^9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-9.2.1.tgz#1b3df0ef166cfa3eae706e3518b17d5922721c57" + integrity sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A== + dependencies: + "@graphql-typed-document-node/core" "^3.1.1" + tslib "^2.4.0" + +"@graphql-typed-document-node/core@^3.1.0", "@graphql-typed-document-node/core@^3.1.1": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" + integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@img/sharp-darwin-arm64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" + integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.4" + +"@img/sharp-darwin-x64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" + integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.4" + +"@img/sharp-libvips-darwin-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f" + integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== + +"@img/sharp-libvips-darwin-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" + integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== + +"@img/sharp-libvips-linux-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" + integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== + +"@img/sharp-libvips-linux-arm@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" + integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== + +"@img/sharp-libvips-linux-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" + integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== + +"@img/sharp-linux-arm64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" + integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.4" + +"@img/sharp-linux-arm@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" + integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.5" + +"@img/sharp-linux-x64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" + integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.4" + +"@img/sharp-win32-x64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" + integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/diff-sequences@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + +"@jest/environment@30.1.1": + version "30.1.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.1.1.tgz#ce8f245a14ff47c8fbc2ac17e2fbe9b984df245d" + integrity sha512-yWHbU+3j7ehQE+NRpnxRvHvpUhoohIjMePBbIr8lfe0cWVb0WeTf80DNux1GPJa18CDHiIU5DtksGUfxcDE+Rw== + dependencies: + "@jest/fake-timers" "30.1.1" + "@jest/types" "30.0.5" + "@types/node" "*" + jest-mock "30.0.5" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@30.1.1": + version "30.1.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.1.1.tgz#f57553f708b445a8d20c5b365bc9c84f87cba2ac" + integrity sha512-5YUHr27fpJ64dnvtu+tt11ewATynrHkGYD+uSFgRr8V2eFJis/vEXgToyLwccIwqBihVfz9jwio+Zr1ab1Zihw== + dependencies: + "@jest/get-type" "30.1.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@30.1.1": + version "30.1.1" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.1.1.tgz#d9274c0dc6af430ab4c5b1e6692e62cec757d229" + integrity sha512-3vHIHsF+qd3D8FU2c7U5l3rg1fhDwAYcGyHyZAi94YIlTwcJ+boNhRyJf373cl4wxbOX+0Q7dF40RTrTFTSuig== + dependencies: + expect "30.1.1" + jest-snapshot "30.1.1" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@30.1.1": + version "30.1.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.1.1.tgz#eb0cce02f8ca5a69cc9754780836068461ccaa45" + integrity sha512-fK/25dNgBNYPw3eLi2CRs57g1H04qBAFNMsUY3IRzkfx/m4THe0E1zF+yGQBOMKKc2XQVdc9EYbJ4hEm7/2UtA== + dependencies: + "@jest/types" "30.0.5" + "@sinonjs/fake-timers" "^13.0.0" + "@types/node" "*" + jest-message-util "30.1.0" + jest-mock "30.0.5" + jest-util "30.0.5" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/get-type@30.1.0": + version "30.1.0" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" + integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/globals@^30.0.5": + version "30.1.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.1.1.tgz#0d1cdfb7f6d73ee1a6e9f679fcd706971804b39e" + integrity sha512-NNUUkHT2TU/xztZl6r1UXvJL+zvCwmZsQDmK69fVHHcB9fBtlu3FInnzOve/ZoyKnWY8JXWJNT+Lkmu1+ubXUA== + dependencies: + "@jest/environment" "30.1.1" + "@jest/expect" "30.1.1" + "@jest/types" "30.0.5" + jest-mock "30.0.5" + +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/snapshot-utils@30.1.1": + version "30.1.1" + resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.1.1.tgz#cfb8eaff6e487954437335613646009b530e9959" + integrity sha512-TkVBc9wuN22TT8hESRFmjjg/xIMu7z0J3UDYtIRydzCqlLPTB7jK1DDBKdnTUZ4zL3z3rnPpzV6rL1Uzh87sXg== + dependencies: + "@jest/types" "30.0.5" + chalk "^4.1.2" + graceful-fs "^4.2.11" + natural-compare "^1.4.0" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@30.1.1": + version "30.1.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.1.1.tgz#9ed736ee0e8787d5648401193603e0026d11f0b0" + integrity sha512-PHIA2AbAASBfk6evkNifvmx9lkOSkmvaQoO6VSpuL8+kQqDMHeDoJ7RU3YP1wWAMD7AyQn9UL5iheuFYCC4lqQ== + dependencies: + "@babel/core" "^7.27.4" + "@jest/types" "30.0.5" + "@jridgewell/trace-mapping" "^0.3.25" + babel-plugin-istanbul "^7.0.0" + chalk "^4.1.2" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.11" + jest-haste-map "30.1.0" + jest-regex-util "30.0.1" + jest-util "30.0.5" + micromatch "^4.0.8" + pirates "^4.0.7" + slash "^3.0.0" + write-file-atomic "^5.0.1" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@josephg/resolvable@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb" + integrity sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg== + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.30" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz#4a76c4daeee5df09f5d3940e087442fb36ce2b99" + integrity sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@linear/sdk@^25.0.0": + version "25.0.0" + resolved "https://registry.yarnpkg.com/@linear/sdk/-/sdk-25.0.0.tgz#f15c76a855f2aa2139ac46416b4b4c6e45a4f0e2" + integrity sha512-GjD0NXTyLVLzSweYhqTi0XXkUiRQQ2uQVVPDbc1MzknrEBMT+DaBNSwfR8j22xMxfUhthq8leq0OSc5o5zo2fA== + dependencies: + "@graphql-typed-document-node/core" "^3.1.0" + graphql "^15.4.0" + isomorphic-unfetch "^3.1.0" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@pkgr/core@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" + integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinclair/typebox@^0.34.0": + version "0.34.40" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.40.tgz#740056ea8d8aaada2ac1ce414c2f074798283b92" + integrity sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw== + +"@sinonjs/commons@^3.0.0", "@sinonjs/commons@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/fake-timers@^13.0.0": + version "13.0.5" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" + integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== + dependencies: + "@sinonjs/commons" "^3.0.1" + +"@slack/events-api@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@slack/events-api/-/events-api-3.0.1.tgz#f7db24fa69ab79c97e4eb4355d428af7bf1b5cc6" + integrity sha512-ReJzZRpCgwGtKrAT0tRMppO3zm72jmxsOlTgR7PGajv2oq/tOJSeVRm7RcGiwn3EPIuovKkD/mr4TTN4n801fQ== + dependencies: + "@types/debug" "^4.1.4" + "@types/express" "^4.17.0" + "@types/lodash.isstring" "^4.0.6" + "@types/node" ">=12.13.0 < 13" + "@types/yargs" "^15.0.4" + debug "^2.6.1" + lodash.isstring "^4.0.1" + raw-body "^2.3.3" + tsscmp "^1.0.6" + yargs "^15.3.1" + optionalDependencies: + express "^4.0.0" + +"@slack/logger@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@slack/logger/-/logger-3.0.0.tgz#b736d4e1c112c22a10ffab0c2d364620aedcb714" + integrity sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA== + dependencies: + "@types/node" ">=12.0.0" + +"@slack/types@^2.11.0": + version "2.16.0" + resolved "https://registry.yarnpkg.com/@slack/types/-/types-2.16.0.tgz#92ba59f9e970440b524423ad694eba4fa4995a86" + integrity sha512-bICnyukvdklXhwxprR3uF1+ZFkTvWTZge4evlCS4G1H1HU6QLY68AcjqzQRymf7/5gNt6Y4OBb4NdviheyZcAg== + +"@slack/web-api@^6.11.0": + version "6.13.0" + resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.13.0.tgz#3df65a9b7b0cef5d7bbd6860a0741ce6b8091b9b" + integrity sha512-dv65crIgdh9ZYHrevLU6XFHTQwTyDmNqEqzuIrV+Vqe/vgiG6w37oex5ePDU1RGm2IJ90H8iOvHFvzdEO/vB+g== + dependencies: + "@slack/logger" "^3.0.0" + "@slack/types" "^2.11.0" + "@types/is-stream" "^1.1.0" + "@types/node" ">=12.0.0" + axios "^1.7.4" + eventemitter3 "^3.1.0" + form-data "^2.5.0" + is-electron "2.2.2" + is-stream "^1.1.0" + p-queue "^6.6.1" + p-retry "^4.0.0" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/accepts@^1.3.5": + version "1.3.7" + resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.7.tgz#3b98b1889d2b2386604c2bbbe62e4fb51e95b265" + integrity sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ== + dependencies: + "@types/node" "*" + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6", "@types/babel__traverse@^7.20.5": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/body-parser@1.19.2": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cors@2.8.12": + version "2.8.12" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" + integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== + +"@types/debug@^4.1.4": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + +"@types/express-serve-static-core@4.17.31": + version "4.17.31" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" + integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express-serve-static-core@^4.17.18", "@types/express-serve-static-core@^4.17.33": + version "4.19.6" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" + integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@4.17.14": + version "4.17.14" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" + integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/express@^4.17.0": + version "4.17.23" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef" + integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + +"@types/is-stream@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" + integrity sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0", "@types/istanbul-reports@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + +"@types/js-yaml@^4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" + integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg== + +"@types/json-schema@^7.0.12": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/lodash.isstring@^4.0.6": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/lodash.isstring/-/lodash.isstring-4.0.9.tgz#ebee88b1a01a4d934a2479f2318da8c0ec8adec9" + integrity sha512-sjGPpa15VBpMns/4s6Blm567JgxLVVu/eCYCe7h/TdQyPCz9lIhaLSISjN7ZC9cDXmUT2IM/4mNRw8OtYirziw== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.17.20" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.20.tgz#1ca77361d7363432d29f5e55950d9ec1e1c6ea93" + integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== + +"@types/long@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + +"@types/node@*", "@types/node@>=12.0.0": + version "24.3.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.3.0.tgz#89b09f45cb9a8ee69466f18ee5864e4c3eb84dec" + integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow== + dependencies: + undici-types "~7.10.0" + +"@types/node@>=12.13.0 < 13": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + +"@types/node@^10.1.0": + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== + +"@types/node@^20.19.11": + version "20.19.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.11.tgz#728cab53092bd5f143beed7fbba7ba99de3c16c4" + integrity sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow== + dependencies: + undici-types "~6.21.0" + +"@types/qs@*": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/semver@^7.5.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.0.tgz#64c441bdae033b378b6eef7d0c3d77c329b9378e" + integrity sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA== + +"@types/send@*": + version "0.17.5" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" + integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" + integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== + +"@types/sizzle@^2.3.2": + version "2.3.10" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.10.tgz#277a542aff6776d8a9b15f2ac682a663e3e94bbd" + integrity sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww== + +"@types/stack-utils@^2.0.0", "@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^15.0.4": + version "15.0.19" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.19.tgz#328fb89e46109ecbdb70c295d96ff2f46dfd01b9" + integrity sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA== + dependencies: + "@types/yargs-parser" "*" + +"@types/yargs@^17.0.33", "@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@types/yauzl@^2.9.1": + version "2.10.3" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" + integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== + dependencies: + "@types/node" "*" + +"@typescript-eslint/eslint-plugin@^6.19.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" + integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/type-utils" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.19.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== + dependencies: + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + +"@typescript-eslint/type-utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" + integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== + dependencies: + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== + +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" + integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== + dependencies: + "@typescript-eslint/types" "6.21.0" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0", "@ungap/structured-clone@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@whatwg-node/promise-helpers@^1.0.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz#3b54987ad6517ef6db5920c66a6f0dada606587d" + integrity sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA== + dependencies: + tslib "^2.6.3" + +accepts@^1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.0.tgz#2f302e7550431b1b7762705fffb52cf1ffa20447" + integrity sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0, ansi-styles@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +anymatch@^3.0.3, anymatch@^3.1.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +apollo-datasource@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-3.3.2.tgz#5711f8b38d4b7b53fb788cb4dbd4a6a526ea74c8" + integrity sha512-L5TiS8E2Hn/Yz7SSnWIVbZw0ZfEIXZCa5VUiVxD9P53JvSrf4aStvsFDlGWPvpIdCR+aly2CfoB79B9/JjKFqg== + dependencies: + "@apollo/utils.keyvaluecache" "^1.0.1" + apollo-server-env "^4.2.1" + +apollo-reporting-protobuf@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.4.0.tgz#6edd31f09d4a3704d9e808d1db30eca2229ded26" + integrity sha512-h0u3EbC/9RpihWOmcSsvTW2O6RXVaD/mPEjfrPkxRPTEPWqncsgOoRJw+wih4OqfH3PvTJvoEIf4LwKrUaqWog== + dependencies: + "@apollo/protobufjs" "1.2.6" + +apollo-server-core@^3.13.0: + version "3.13.0" + resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-3.13.0.tgz#ad6601fbb34cc97eedca27a9fb0b5738d11cd27d" + integrity sha512-v/g6DR6KuHn9DYSdtQijz8dLOkP78I5JSVJzPkARhDbhpH74QNwrQ2PP2URAPPEDJ2EeZNQDX8PvbYkAKqg+kg== + dependencies: + "@apollo/utils.keyvaluecache" "^1.0.1" + "@apollo/utils.logger" "^1.0.0" + "@apollo/utils.usagereporting" "^1.0.0" + "@apollographql/apollo-tools" "^0.5.3" + "@apollographql/graphql-playground-html" "1.6.29" + "@graphql-tools/mock" "^8.1.2" + "@graphql-tools/schema" "^8.0.0" + "@josephg/resolvable" "^1.0.0" + apollo-datasource "^3.3.2" + apollo-reporting-protobuf "^3.4.0" + apollo-server-env "^4.2.1" + apollo-server-errors "^3.3.1" + apollo-server-plugin-base "^3.7.2" + apollo-server-types "^3.8.0" + async-retry "^1.2.1" + fast-json-stable-stringify "^2.1.0" + graphql-tag "^2.11.0" + loglevel "^1.6.8" + lru-cache "^6.0.0" + node-abort-controller "^3.0.1" + sha.js "^2.4.11" + uuid "^9.0.0" + whatwg-mimetype "^3.0.0" + +apollo-server-env@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-4.2.1.tgz#ea5b1944accdbdba311f179e4dfaeca482c20185" + integrity sha512-vm/7c7ld+zFMxibzqZ7SSa5tBENc4B0uye9LTfjJwGoQFY5xsUPH5FpO5j0bMUDZ8YYNbrF9SNtzc5Cngcr90g== + dependencies: + node-fetch "^2.6.7" + +apollo-server-errors@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz#ba5c00cdaa33d4cbd09779f8cb6f47475d1cd655" + integrity sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA== + +apollo-server-express@^3.13.0: + version "3.13.0" + resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-3.13.0.tgz#0d8d9bbba3b8b8264912d215f63fd44e74d5f42a" + integrity sha512-iSxICNbDUyebOuM8EKb3xOrpIwOQgKxGbR2diSr4HP3IW8T3njKFOoMce50vr+moOCe1ev8BnLcw9SNbuUtf7g== + dependencies: + "@types/accepts" "^1.3.5" + "@types/body-parser" "1.19.2" + "@types/cors" "2.8.12" + "@types/express" "4.17.14" + "@types/express-serve-static-core" "4.17.31" + accepts "^1.3.5" + apollo-server-core "^3.13.0" + apollo-server-types "^3.8.0" + body-parser "^1.19.0" + cors "^2.8.5" + parseurl "^1.3.3" + +apollo-server-plugin-base@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-3.7.2.tgz#c19cd137bc4c993ba2490ba2b571b0f3ce60a0cd" + integrity sha512-wE8dwGDvBOGehSsPTRZ8P/33Jan6/PmL0y0aN/1Z5a5GcbFhDaaJCjK5cav6npbbGL2DPKK0r6MPXi3k3N45aw== + dependencies: + apollo-server-types "^3.8.0" + +apollo-server-types@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-3.8.0.tgz#d976b6967878681f715fe2b9e4dad9ba86b1346f" + integrity sha512-ZI/8rTE4ww8BHktsVpb91Sdq7Cb71rdSkXELSwdSR0eXu600/sY+1UXhTWdiJvk+Eq5ljqoHLwLbY2+Clq2b9A== + dependencies: + "@apollo/utils.keyvaluecache" "^1.0.1" + "@apollo/utils.logger" "^1.0.0" + apollo-reporting-protobuf "^3.4.0" + apollo-server-env "^4.2.1" + +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== + dependencies: + default-require-extensions "^3.0.0" + +arch@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== + +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-retry@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" + integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw== + dependencies: + retry "0.13.1" + +async@^3.2.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.13.2" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" + integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== + +axios@^1.7.4: + version "1.11.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.11.0.tgz#c2ec219e35e414c025b2095e8b8280278478fdb6" + integrity sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.4" + proxy-from-env "^1.1.0" + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-istanbul@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz#629a178f63b83dc9ecee46fd20266283b1f11280" + integrity sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-instrument "^6.0.2" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0, babel-preset-current-node-syntax@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +basic-auth@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + +bcrypt@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-6.0.0.tgz#86643fddde9bcd0ad91400b063003fa4b0312835" + integrity sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg== + dependencies: + node-addon-api "^8.3.0" + node-gyp-build "^4.8.4" + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +blob-util@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" + integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== + +bluebird@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de" + integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg== + +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +body-parser@1.20.3, body-parser@^1.19.0: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.13.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +body-parser@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" + integrity sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg== + dependencies: + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.0" + http-errors "^2.0.0" + iconv-lite "^0.6.3" + on-finished "^2.4.1" + qs "^6.14.0" + raw-body "^3.0.0" + type-is "^2.0.0" + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.25.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.3.tgz#9167c9cbb40473f15f75f85189290678b99b16c5" + integrity sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ== + dependencies: + caniuse-lite "^1.0.30001735" + electron-to-chromium "^1.5.204" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +buffer-equal-constant-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +bytes@3.1.2, bytes@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +cachedir@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d" + integrity sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ== + +caching-transform@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== + dependencies: + hasha "^5.0.0" + make-dir "^3.0.0" + package-hash "^4.0.0" + write-file-atomic "^3.0.0" + +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001735: + version "1.0.30001737" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz#8292bb7591932ff09e9a765f12fdf5629a241ccc" + integrity sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + +chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +check-more-types@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" + integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA== + +chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +ci-info@^4.0.0, ci-info@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-table3@~0.6.1: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colorette@^2.0.16: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +combined-stream@^1.0.8, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^12.0.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + +commander@^2.20.3: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +common-tags@^1.8.0: + version "1.8.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-disposition@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.0.tgz#844426cb398f934caefcbb172200126bc7ceace2" + integrity sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg== + dependencies: + safe-buffer "5.2.1" + +content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== + +cookie@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-inspect@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cross-inspect/-/cross-inspect-1.0.1.tgz#15f6f65e4ca963cf4cc1a2b5fef18f6ca328712b" + integrity sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A== + dependencies: + tslib "^2.4.0" + +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cssfilter@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" + integrity sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw== + +cypress@^13.6.3: + version "13.17.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.17.0.tgz#34c3d68080c4497eace0f353bd1629587a5f600d" + integrity sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA== + dependencies: + "@cypress/request" "^3.0.6" + "@cypress/xvfb" "^1.2.4" + "@types/sinonjs__fake-timers" "8.1.1" + "@types/sizzle" "^2.3.2" + arch "^2.2.0" + blob-util "^2.0.2" + bluebird "^3.7.2" + buffer "^5.7.1" + cachedir "^2.3.0" + chalk "^4.1.0" + check-more-types "^2.24.0" + ci-info "^4.0.0" + cli-cursor "^3.1.0" + cli-table3 "~0.6.1" + commander "^6.2.1" + common-tags "^1.8.0" + dayjs "^1.10.4" + debug "^4.3.4" + enquirer "^2.3.6" + eventemitter2 "6.4.7" + execa "4.1.0" + executable "^4.1.1" + extract-zip "2.0.1" + figures "^3.2.0" + fs-extra "^9.1.0" + getos "^3.2.1" + is-installed-globally "~0.4.0" + lazy-ass "^1.6.0" + listr2 "^3.8.3" + lodash "^4.17.21" + log-symbols "^4.0.0" + minimist "^1.2.8" + ospath "^1.2.2" + pretty-bytes "^5.6.0" + process "^0.11.10" + proxy-from-env "1.0.0" + request-progress "^3.0.0" + semver "^7.5.3" + supports-color "^8.1.1" + tmp "~0.2.3" + tree-kill "1.2.2" + untildify "^4.0.0" + yauzl "^2.10.0" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + +dayjs@1.11.13: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== + +dayjs@^1.10.4: + version "1.11.14" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.14.tgz#aa47cd445471acac25d55deb101557dd827eff60" + integrity sha512-E8fIdSxUlyqSA8XYGnNa3IkIzxtEmFjI+JU/6ic0P1zmSqyL6HyG5jHnpPjRguDNiaHLpfvHKWFiohNsJLqcJQ== + +debug@2.6.9, debug@^2.6.1: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +debug@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +dedent@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" + integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +default-require-extensions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.1.tgz#bfae00feeaeada68c2ae256c62540f60b80625bd" + integrity sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw== + dependencies: + strip-bom "^4.0.0" + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +depd@2.0.0, depd@^2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dotenv@^17.2.1: + version "17.2.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.1.tgz#6f32e10faf014883515538dc922a0fb8765d9b32" + integrity sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ== + +dset@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.4.tgz#f8eaf5f023f068a036d08cd07dc9ffb7d0065248" + integrity sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.5.204: + version "1.5.211" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz#749317bf9cf894c06f67980940cf8074e5eb08ca" + integrity sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encodeurl@^2.0.0, encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +end-of-stream@^1.1.0: + version "1.4.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== + dependencies: + once "^1.4.0" + +enquirer@^2.3.6: + version "2.4.1" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" + integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== + dependencies: + ansi-colors "^4.1.1" + strip-ansi "^6.0.1" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@^1.0.3, escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.56.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@^1.8.1, etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eventemitter2@6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" + integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== + +eventemitter3@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== + +eventemitter3@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +execa@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +executable@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" + integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== + dependencies: + pify "^2.2.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@30.1.1, expect@^30.0.0: + version "30.1.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.1.1.tgz#165bbdf514880bc9d4377b5b73716a38ab97b2ad" + integrity sha512-OKe7cdic4qbfWd/CcgwJvvCrNX2KWfuMZee9AfJHL1gTYmvqjBjZG1a2NwfhspBzxzlXwsN75WWpKTYfsJpBxg== + dependencies: + "@jest/expect-utils" "30.1.1" + "@jest/get-type" "30.1.0" + jest-matcher-utils "30.1.1" + jest-message-util "30.1.0" + jest-mock "30.0.5" + jest-util "30.0.5" + +expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +express-rate-limit@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-8.0.1.tgz#3bc13aaf9f448085686180ef60679a68ea89654d" + integrity sha512-aZVCnybn7TVmxO4BtlmnvX+nuz8qHW124KKJ8dumsBsmv5ZLxE0pYu7S2nwyRBGHHCAzdmnGyrc5U/rksSPO7Q== + dependencies: + ip-address "10.0.1" + +express@^4.0.0: + version "4.21.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" + integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.3" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.7.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.3.1" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.3" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.12" + proxy-addr "~2.0.7" + qs "6.13.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.19.0" + serve-static "1.16.2" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +express@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/express/-/express-5.1.0.tgz#d31beaf715a0016f0d53f47d3b4d7acf28c75cc9" + integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.0" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extract-zip@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0, fb-watchman@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + +figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== + dependencies: + debug "2.6.9" + encodeurl "~2.0.0" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +finalhandler@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.0.tgz#72306373aa89d05a8242ed569ed86a1bff7c561f" + integrity sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q== + dependencies: + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + +find-cache-dir@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +follow-redirects@^1.15.6: + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== + +for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" + +foreground-child@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + +form-data@^2.5.0: + version "2.5.5" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.5.tgz#a5f6364ad7e4e67e95b4a07e2d8c6f711c74f624" + integrity sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.35" + safe-buffer "^5.2.1" + +form-data@^4.0.4, form-data@~4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" + integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + +fromentries@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2, fsevents@^2.3.3, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.1, get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stream@^5.0.0, get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +getos@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" + integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== + dependencies: + async "^3.2.0" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^10.3.10: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-dirs@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" + integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== + dependencies: + ini "2.0.0" + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globby@11.1.0, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +graphql-subscriptions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-3.0.0.tgz#820c846ef271414c08f64827b5c9a192801e1b6f" + integrity sha512-kZCdevgmzDjGAOqH7GlDmQXYAkuHoKpMlJrqF40HMPhUhM5ZWSFSxCwD/nSi6AkaijmMfsFhoJRGJ27UseCvRA== + +graphql-tag@^2.11.0: + version "2.12.6" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" + integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== + dependencies: + tslib "^2.1.0" + +graphql@^15.4.0: + version "15.10.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.10.1.tgz#e9ff3bb928749275477f748b14aa5c30dcad6f2f" + integrity sha512-BL/Xd/T9baO6NFzoMpiMD7YUZ62R6viR5tp/MULVEnbYJXZA//kRNW7J0j1w/wXArgL0sCxhDfK5dczSKn3+cg== + +graphql@^16.11.0: + version "16.11.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.11.0.tgz#96d17f66370678027fdf59b2d4c20b4efaa8a633" + integrity sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw== + +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasha@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +helmet@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-8.1.0.tgz#f96d23fedc89e9476ecb5198181009c804b8b38c" + integrity sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-errors@2.0.0, http-errors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-signature@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.4.0.tgz#dee5a9ba2bf49416abc544abd6d967f6a94c8c3f" + integrity sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg== + dependencies: + assert-plus "^1.0.0" + jsprim "^2.0.2" + sshpk "^1.18.0" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@0.6.3, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ip-address@10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.0.1.tgz#a8180b783ce7788777d796286d61bce4276818ed" + integrity sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-electron@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-electron/-/is-electron-2.2.2.tgz#3778902a2044d76de98036f5dc58089ac4d80bb9" + integrity sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-installed-globally@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.2, is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typed-array@^1.1.14: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isomorphic-unfetch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" + integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== + dependencies: + node-fetch "^2.6.1" + unfetch "^4.2.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-processinfo@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz#366d454cd0dcb7eb6e0e419378e60072c8626169" + integrity sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg== + dependencies: + archy "^1.0.0" + cross-spawn "^7.0.3" + istanbul-lib-coverage "^3.2.0" + p-map "^3.0.0" + rimraf "^3.0.0" + uuid "^8.3.2" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2, istanbul-reports@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@30.1.1: + version "30.1.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.1.1.tgz#cfe8327c059178affac17d4c003e7096ad19583c" + integrity sha512-LUU2Gx8EhYxpdzTR6BmjL1ifgOAQJQELTHOiPv9KITaKjZvJ9Jmgigx01tuZ49id37LorpGc9dPBPlXTboXScw== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + pretty-format "30.0.5" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.1.0.tgz#e54d84e07fac15ea3a98903b735048e36d7d2ed3" + integrity sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + anymatch "^3.1.3" + fb-watchman "^2.0.2" + graceful-fs "^4.2.11" + jest-regex-util "30.0.1" + jest-util "30.0.5" + jest-worker "30.1.0" + micromatch "^4.0.8" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.3" + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@30.1.1: + version "30.1.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.1.1.tgz#e45419d966cd2e5e7d7ade6da747035c6a3b8afc" + integrity sha512-SuH2QVemK48BNTqReti6FtjsMPFsSOD/ZzRxU1TttR7RiRsRSe78d03bb4Cx6D4bQC/80Q8U4VnaaAH9FlbZ9w== + dependencies: + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + jest-diff "30.1.1" + pretty-format "30.0.5" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.1.0.tgz#653a9bb1a33306eddf13455ce0666ba621b767c4" + integrity sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + jest-util "30.0.5" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@30.1.1: + version "30.1.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.1.1.tgz#ef9bfdc22b4e807622e14fe32fd40745a2c031e5" + integrity sha512-7/iBEzoJqEt2TjkQY+mPLHP8cbPhLReZVkkxjTMzIzoTC4cZufg7HzKo/n9cIkXKj2LG0x3mmBHsZto+7TOmFg== + dependencies: + "@babel/core" "^7.27.4" + "@babel/generator" "^7.27.5" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.27.1" + "@babel/types" "^7.27.3" + "@jest/expect-utils" "30.1.1" + "@jest/get-type" "30.1.0" + "@jest/snapshot-utils" "30.1.1" + "@jest/transform" "30.1.1" + "@jest/types" "30.0.5" + babel-preset-current-node-syntax "^1.1.0" + chalk "^4.1.2" + expect "30.1.1" + graceful-fs "^4.2.11" + jest-diff "30.1.1" + jest-matcher-utils "30.1.1" + jest-message-util "30.1.0" + jest-util "30.0.5" + pretty-format "30.0.5" + semver "^7.7.2" + synckit "^0.11.8" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.1.0.tgz#a89c36772be449d4bdb60697fb695a1673b12ac2" + integrity sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA== + dependencies: + "@types/node" "*" + "@ungap/structured-clone" "^1.3.0" + jest-util "30.0.5" + merge-stream "^2.0.0" + supports-color "^8.1.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonwebtoken@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + +jwa@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" + integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== + dependencies: + buffer-equal-constant-time "^1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +lazy-ass@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" + integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +listr2@^3.8.3: + version "3.14.0" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" + integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.1" + through "^2.3.8" + wrap-ansi "^7.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.once@^4.0.0, lodash.once@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== + +lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +loglevel@^1.6.8: + version "1.9.2" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08" + integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg== + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +"lru-cache@7.10.1 - 7.13.1": + version "7.13.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4" + integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ== + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^11.0.0, lru-cache@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117" + integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime-types@^3.0.0, mime-types@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce" + integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA== + dependencies: + mime-db "^1.54.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.5, minimist@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +morgan@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.1.tgz#4e02e6a4465a48e26af540191593955d17f61570" + integrity sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A== + dependencies: + basic-auth "~2.0.1" + debug "2.6.9" + depd "~2.0.0" + on-finished "~2.3.0" + on-headers "~1.1.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3, ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-abort-controller@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" + integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== + +node-addon-api@^8.3.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.5.0.tgz#c91b2d7682fa457d2e1c388150f0dff9aafb8f3f" + integrity sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A== + +node-dogstatsd@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/node-dogstatsd/-/node-dogstatsd-0.0.7.tgz#544d274c91a413426a2f3c40ff7b8e8813cfd930" + integrity sha512-Ssr+5ySG89xL3ToV2KuFPqK/+lCOx68Wx0fbVvJUx+7eBKxihGRia/Rfik21BxPNdZb1Dn+4RsGZyglyaj56ew== + +node-fetch@^2.6.1, node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-gyp-build@^4.8.4: + version "4.8.4" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" + integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-preload@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== + dependencies: + process-on-spawn "^1.0.0" + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.0, npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nyc@15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" + integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + caching-transform "^4.0.0" + convert-source-map "^1.7.0" + decamelize "^1.2.0" + find-cache-dir "^3.2.0" + find-up "^4.1.0" + foreground-child "^2.0.0" + get-package-type "^0.1.0" + glob "^7.1.6" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-processinfo "^2.0.2" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + make-dir "^3.0.0" + node-preload "^0.2.1" + p-map "^3.0.0" + process-on-spawn "^1.0.0" + resolve-from "^5.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + spawn-wrap "^2.0.0" + test-exclude "^6.0.0" + yargs "^15.0.2" + +object-assign@^4: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +on-finished@2.4.1, on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== + dependencies: + ee-first "1.1.1" + +on-headers@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" + integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +ospath@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" + integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-limit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985" + integrity sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ== + dependencies: + yocto-queue "^1.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-queue@^6.6.1: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + +p-retry@^4.0.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parseurl@^1.3.3, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-to-regexp@0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== + +path-to-regexp@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +pify@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pirates@^4.0.4, pirates@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@^3.2.4: + version "3.6.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393" + integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== + +pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + +pretty-format@30.0.5, pretty-format@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +process-on-spawn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.1.0.tgz#9d5999ba87b3bf0a8acb05322d69f2f5aa4fb763" + integrity sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q== + dependencies: + fromentries "^1.2.0" + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-addr@^2.0.7, proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +proxy-from-env@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" + integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + +qs@6.14.0, qs@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2, raw-body@^2.3.3: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +raw-body@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.0.tgz#25b3476f07a51600619dae3fe82ddc28a36e5e0f" + integrity sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.6.3" + unpipe "1.0.0" + +react-is@^18.0.0, react-is@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA== + dependencies: + es6-error "^4.0.1" + +request-progress@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" + integrity sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg== + dependencies: + throttleit "^1.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +retry@0.13.1, retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rfdc@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +router@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^7.5.1: + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.2, semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +send@^1.1.0, send@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" + integrity sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw== + dependencies: + debug "^4.3.5" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.0" + mime-types "^3.0.1" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.1" + +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.19.0" + +serve-static@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.0.tgz#9c02564ee259bdd2251b82d659a2e7e1938d66f9" + integrity sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +sha.js@^2.4.11: + version "2.4.12" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f" + integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w== + dependencies: + inherits "^2.0.4" + safe-buffer "^5.2.1" + to-buffer "^1.2.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.0.6, side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spawn-wrap@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" + integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== + dependencies: + foreground-child "^2.0.0" + is-windows "^1.0.2" + make-dir "^3.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + which "^2.0.1" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +sshpk@^1.18.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-utils@^2.0.3, stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +statuses@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +synckit@^0.11.8: + version "0.11.11" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0" + integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw== + dependencies: + "@pkgr/core" "^0.2.9" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +throttleit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.1.tgz#304ec51631c3b770c65c6c6f76938b384000f4d5" + integrity sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ== + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tldts-core@^6.1.86: + version "6.1.86" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.86.tgz#a93e6ed9d505cb54c542ce43feb14c73913265d8" + integrity sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA== + +tldts@^6.1.32: + version "6.1.86" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.86.tgz#087e0555b31b9725ee48ca7e77edc56115cd82f7" + integrity sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ== + dependencies: + tldts-core "^6.1.86" + +tmp@~0.2.3: + version "0.2.5" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" + integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-buffer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.1.tgz#2ce650cdb262e9112a18e65dc29dcb513c8155e0" + integrity sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ== + dependencies: + isarray "^2.0.5" + safe-buffer "^5.2.1" + typed-array-buffer "^1.0.3" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tough-cookie@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.2.tgz#66d774b4a1d9e12dc75089725af3ac75ec31bed7" + integrity sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A== + dependencies: + tldts "^6.1.32" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +tree-kill@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +tree-sitter-python@^0.23.6: + version "0.23.6" + resolved "https://registry.yarnpkg.com/tree-sitter-python/-/tree-sitter-python-0.23.6.tgz#807e50c415a761c613614f902432f563e85b51b6" + integrity sha512-yIM9z0oxKIxT7bAtPOhgoVl6gTXlmlIhue7liFT4oBPF/lha7Ha4dQBS82Av6hMMRZoVnFJI8M6mL+SwWoLD3A== + dependencies: + node-addon-api "^8.3.0" + node-gyp-build "^4.8.4" + +tree-sitter@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/tree-sitter/-/tree-sitter-0.25.0.tgz#d9d94ba00b501df49826c10c0f74037b890788eb" + integrity sha512-PGZZzFW63eElZJDe/b/R/LbsjDDYJa5UEjLZJB59RQsMX+fo0j54fqBPn1MGKav/QNa0JR0zBiVaikYDWCj5KQ== + dependencies: + node-addon-api "^8.3.0" + node-gyp-build "^4.8.4" + +ts-api-utils@^1.0.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== + +ts-jest@^29.1.1: + version "29.4.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.1.tgz#42d33beb74657751d315efb9a871fe99e3b9b519" + integrity sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw== + dependencies: + bs-logger "^0.2.6" + fast-json-stable-stringify "^2.1.0" + handlebars "^4.7.8" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.2" + type-fest "^4.41.0" + yargs-parser "^21.1.1" + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.3: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +tsscmp@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" + integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +type-is@^2.0.0, type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^5.3.3: + version "5.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" + integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== + +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici-types@~7.10.0: + version "7.10.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.10.0.tgz#4ac2e058ce56b462b056e629cc6a02393d3ff350" + integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag== + +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912" + integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +value-or-promise@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.11.tgz#3e90299af31dd014fe843fe309cefa7c1d94b140" + integrity sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg== + +value-or-promise@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.12.tgz#0e5abfeec70148c78460a849f6b003ea7986f15c" + integrity sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q== + +vary@^1, vary@^1.1.2, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + +which-typed-array@^1.1.16: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +write-file-atomic@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + +ws@^8.18.3: + version "8.18.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" + integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + +xss@^1.0.8: + version "1.0.15" + resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.15.tgz#96a0e13886f0661063028b410ed1b18670f4e59a" + integrity sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg== + dependencies: + commander "^2.20.3" + cssfilter "0.0.10" + +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^15.0.2, yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yocto-queue@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.2.1.tgz#36d7c4739f775b3cbc28e6136e21aa057adec418" + integrity sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==