UNPKG

context-forge

Version:

AI orchestration platform with autonomous teams, enhancement planning, migration tools, 25+ slash commands, checkpoints & hooks. Multi-IDE: Claude, Cursor, Windsurf, Cline, Copilot

445 lines (441 loc) 18.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.DependencyAnalyzer = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); class DependencyAnalyzer { constructor() { this.mappings = this.initializeMappings(); } initializeMappings() { return [ // React ecosystem migrations { from: { name: 'react-scripts', framework: 'react' }, to: [ { name: '@vitejs/plugin-react', framework: 'react' }, { name: 'vite', framework: 'agnostic' }, ], migrationNotes: 'Migrate from Create React App to Vite for better performance', compatible: false, }, { from: { name: 'enzyme', framework: 'react' }, to: [ { name: '@testing-library/react', framework: 'react' }, { name: '@testing-library/jest-dom', framework: 'react' }, ], migrationNotes: 'Enzyme is deprecated for React 18+, use React Testing Library', breakingChanges: ['Different API for component testing', 'No shallow rendering'], compatible: false, }, { from: { name: 'react-router', versionRange: '<6', framework: 'react' }, to: [{ name: 'react-router-dom', versionRange: '^6', framework: 'react' }], migrationNotes: 'React Router v6 has significant API changes', breakingChanges: ['Switch replaced with Routes', 'Route component API changed'], compatible: false, }, // Vue ecosystem migrations { from: { name: 'vue-cli-service', framework: 'vue' }, to: [ { name: '@vitejs/plugin-vue', framework: 'vue' }, { name: 'vite', framework: 'agnostic' }, ], migrationNotes: 'Vue CLI is in maintenance mode, migrate to Vite', compatible: false, }, { from: { name: 'vuex', versionRange: '<4', framework: 'vue' }, to: [{ name: 'pinia', framework: 'vue' }], migrationNotes: 'Pinia is the recommended state management for Vue 3', compatible: false, }, { from: { name: 'vue-router', versionRange: '<4', framework: 'vue' }, to: [{ name: 'vue-router', versionRange: '^4', framework: 'vue' }], migrationNotes: 'Vue Router 4 required for Vue 3', breakingChanges: ['Different route configuration', 'Composition API support'], compatible: false, }, // Angular to React migrations { from: { name: '@angular/common', framework: 'angular' }, to: [{ name: 'react', framework: 'react' }], migrationNotes: 'Core framework change from Angular to React', compatible: false, }, { from: { name: '@angular/router', framework: 'angular' }, to: [{ name: 'react-router-dom', framework: 'react' }], migrationNotes: 'Replace Angular Router with React Router', compatible: false, }, { from: { name: '@angular/forms', framework: 'angular' }, to: [ { name: 'react-hook-form', framework: 'react' }, { name: 'formik', framework: 'react' }, ], migrationNotes: 'Form handling libraries for React', compatible: false, }, { from: { name: '@angular/http', framework: 'angular' }, to: [ { name: 'axios', framework: 'agnostic' }, { name: 'swr', framework: 'react' }, { name: '@tanstack/react-query', framework: 'react' }, ], migrationNotes: 'HTTP client alternatives for React', compatible: false, }, // Express to modern alternatives { from: { name: 'express', framework: 'express' }, to: [ { name: 'fastify', framework: 'fastify' }, { name: '@nestjs/core', framework: 'nestjs' }, { name: 'koa', framework: 'koa' }, ], migrationNotes: 'Modern alternatives to Express with better performance', compatible: true, // Can run side by side }, { from: { name: 'body-parser', framework: 'express' }, to: [], migrationNotes: 'Built into Express 4.16+ and not needed in modern frameworks', compatible: true, }, // Build tool migrations { from: { name: 'webpack', versionRange: '<5' }, to: [ { name: 'webpack', versionRange: '^5' }, { name: 'vite', framework: 'agnostic' }, { name: 'esbuild', framework: 'agnostic' }, ], migrationNotes: 'Modern build tools with better performance', compatible: false, }, { from: { name: 'gulp' }, to: [ { name: 'vite', framework: 'agnostic' }, { name: 'webpack', versionRange: '^5' }, ], migrationNotes: 'Gulp is less commonly used, modern bundlers handle asset pipeline', compatible: true, }, // Testing library migrations { from: { name: 'mocha' }, to: [ { name: 'vitest', framework: 'agnostic' }, { name: 'jest', framework: 'agnostic' }, ], migrationNotes: 'Modern testing frameworks with better DX', compatible: true, }, { from: { name: 'karma' }, to: [ { name: 'vitest', framework: 'agnostic' }, { name: '@playwright/test', framework: 'agnostic' }, ], migrationNotes: 'Karma is deprecated, use modern test runners', compatible: false, }, // CSS/Styling migrations { from: { name: 'node-sass' }, to: [{ name: 'sass' }], migrationNotes: 'node-sass is deprecated, use Dart Sass', compatible: false, }, { from: { name: 'styled-components', versionRange: '<5', framework: 'react' }, to: [ { name: 'styled-components', versionRange: '^5', framework: 'react' }, { name: '@emotion/styled', framework: 'react' }, ], migrationNotes: 'Update to v5 or consider Emotion for better performance', compatible: false, }, // State management { from: { name: 'redux', framework: 'react' }, to: [ { name: '@reduxjs/toolkit', framework: 'react' }, { name: 'zustand', framework: 'react' }, { name: 'jotai', framework: 'react' }, ], migrationNotes: 'Modern state management alternatives', compatible: true, }, // Utility libraries { from: { name: 'moment' }, to: [{ name: 'date-fns' }, { name: 'dayjs' }, { name: 'luxon' }], migrationNotes: 'Moment.js is in maintenance mode, use modern alternatives', compatible: true, }, { from: { name: 'lodash' }, to: [{ name: 'lodash-es' }, { name: 'ramda' }], migrationNotes: 'Consider ES modules version or functional alternatives', compatible: true, }, ]; } async analyzeDependencies(sourcePath, sourceFramework, targetFramework) { const packageJson = await this.readPackageJson(sourcePath); if (!packageJson) { return this.emptyAnalysis(); } const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, }; const dependencies = []; const incompatible = []; const replacements = []; for (const [name, version] of Object.entries(allDeps)) { const depInfo = this.analyzeDependency(name, version, sourceFramework, targetFramework); dependencies.push(depInfo); if (!depInfo.isCompatible) { incompatible.push(this.getIncompatibilityInfo(name, sourceFramework, targetFramework)); } if (depInfo.replacements && depInfo.replacements.length > 0) { replacements.push(...this.getReplacementSuggestions(name, depInfo.replacements)); } } const hasReplacements = dependencies.filter((d) => d.hasReplacement).length; const migrationComplexity = this.calculateMigrationComplexity(dependencies.length, incompatible.length, hasReplacements); return { dependencies, incompatible, replacements, migrationComplexity, totalDependencies: dependencies.length, incompatibleCount: incompatible.length, hasReplacements, }; } analyzeDependency(name, version, sourceFramework, targetFramework) { const mapping = this.findMapping(name, sourceFramework); const isFrameworkSpecific = this.isFrameworkSpecific(name, sourceFramework); const isCompatible = !isFrameworkSpecific || sourceFramework === targetFramework; return { name, version, framework: this.detectPackageFramework(name), hasReplacement: !!mapping, isCompatible, replacements: mapping?.to, }; } findMapping(packageName, framework) { return this.mappings.find((m) => m.from.name === packageName && (!m.from.framework || m.from.framework === framework)); } isFrameworkSpecific(packageName, framework) { const frameworkPrefixes = { react: ['react', '@testing-library/react', 'react-dom', 'react-router'], vue: ['vue', '@vue/', 'vuex', 'vue-router', 'pinia'], angular: ['@angular/', '@ngrx/'], svelte: ['svelte', '@sveltejs/'], express: ['express-', 'body-parser', 'multer'], nestjs: ['@nestjs/'], nextjs: ['next'], }; const prefixes = frameworkPrefixes[framework] || []; return prefixes.some((prefix) => packageName.startsWith(prefix)); } detectPackageFramework(packageName) { if (packageName.startsWith('react') || packageName.includes('react')) return 'react'; if (packageName.startsWith('vue') || packageName.startsWith('@vue/')) return 'vue'; if (packageName.startsWith('@angular/')) return 'angular'; if (packageName.startsWith('svelte') || packageName.startsWith('@sveltejs/')) return 'svelte'; if (packageName.startsWith('@nestjs/')) return 'nestjs'; if (packageName === 'next' || packageName.startsWith('next-')) return 'nextjs'; if (packageName === 'express' || packageName.startsWith('express-')) return 'express'; return 'agnostic'; } getIncompatibilityInfo(packageName, sourceFramework, targetFramework) { const mapping = this.findMapping(packageName, sourceFramework); if (mapping && !mapping.compatible) { return { package: packageName, reason: mapping.migrationNotes || `Incompatible with ${targetFramework}`, severity: 'high', resolution: mapping.to.length > 0 ? `Replace with: ${mapping.to.map((p) => p.name).join(', ')}` : 'Remove package', }; } const framework = this.detectPackageFramework(packageName); if (framework !== 'agnostic' && framework !== targetFramework) { return { package: packageName, reason: `${framework} package incompatible with ${targetFramework}`, severity: 'critical', resolution: `Find ${targetFramework} equivalent or remove`, }; } return { package: packageName, reason: 'May not be compatible with target framework', severity: 'low', resolution: 'Verify compatibility', }; } getReplacementSuggestions(packageName, replacements) { return replacements.map((replacement) => { const effort = this.estimateMigrationEffort(packageName, replacement.name); return { from: packageName, to: replacement.name, confidence: this.getReplacementConfidence(packageName, replacement.name), migrationEffort: effort, notes: this.getReplacementNotes(packageName, replacement.name), }; }); } estimateMigrationEffort(from, to) { // Direct version upgrades if (from === to.split('@')[0]) return 'trivial'; // Similar packages if (from.includes(to) || to.includes(from)) return 'small'; // Framework changes if (this.detectPackageFramework(from) !== this.detectPackageFramework(to)) return 'large'; return 'medium'; } getReplacementConfidence(from, to) { const mapping = this.mappings.find((m) => m.from.name === from); if (mapping && mapping.to.some((t) => t.name === to)) return 'high'; if (from.includes(to) || to.includes(from)) return 'medium'; return 'low'; } getReplacementNotes(from, to) { const specificNotes = { 'enzyme:@testing-library/react': 'Different testing philosophy - no shallow rendering', 'moment:date-fns': 'Tree-shakeable, functional API', 'moment:dayjs': 'Similar API to Moment.js, smaller bundle', 'node-sass:sass': 'Direct replacement, may need to update import syntax', 'react-scripts:vite': 'Complete build tool migration, significant config changes', }; return specificNotes[`${from}:${to}`] || ''; } calculateMigrationComplexity(total, incompatible, replacements) { const incompatibleRatio = incompatible / total; const replacementRatio = replacements / total; if (incompatibleRatio > 0.5 || replacementRatio > 0.7) return 'high'; if (incompatibleRatio > 0.2 || replacementRatio > 0.4) return 'medium'; return 'low'; } async readPackageJson(projectPath) { try { const content = await fs.readFile(path.join(projectPath, 'package.json'), 'utf-8'); return JSON.parse(content); } catch { return null; } } emptyAnalysis() { return { dependencies: [], incompatible: [], replacements: [], migrationComplexity: 'low', totalDependencies: 0, incompatibleCount: 0, hasReplacements: 0, }; } generateDependencyReport(analysis) { return `# Dependency Analysis Report ## Summary - **Total Dependencies**: ${analysis.totalDependencies} - **Incompatible**: ${analysis.incompatibleCount} - **With Replacements**: ${analysis.hasReplacements} - **Migration Complexity**: ${analysis.migrationComplexity.toUpperCase()} ## Incompatible Dependencies ${analysis.incompatible.length === 0 ? 'No incompatible dependencies found.\n' : analysis.incompatible .map((inc) => ` ### ${inc.package} - **Reason**: ${inc.reason} - **Severity**: ${inc.severity} - **Resolution**: ${inc.resolution} `) .join('\n')} ## Replacement Suggestions ${analysis.replacements.length === 0 ? 'No replacement suggestions.\n' : analysis.replacements .map((rep) => ` ### ${rep.from}${rep.to} - **Confidence**: ${rep.confidence} - **Effort**: ${rep.migrationEffort} ${rep.notes ? `- **Notes**: ${rep.notes}` : ''} `) .join('\n')} ## All Dependencies ${analysis.dependencies .map((dep) => `- ${dep.name}@${dep.version} (${dep.framework}) ${dep.isCompatible ? '✓' : '✗'}`) .join('\n')} `; } } exports.DependencyAnalyzer = DependencyAnalyzer; //# sourceMappingURL=dependencyAnalyzer.js.map