Skip to main content
Glama
pubAnalyzer.js9.22 kB
import axios from 'axios'; import yaml from 'yaml'; const PUB_API_BASE = 'https://pub.dev/api'; export async function analyzePubPackage(args) { const { packageName, checkDependencies = true, checkScores = true } = args; try { const packageInfo = await fetchPackageInfo(packageName); const analysis = { package: { name: packageInfo.name, version: packageInfo.latest.version, description: packageInfo.latest.pubspec.description, homepage: packageInfo.latest.pubspec.homepage, repository: packageInfo.latest.pubspec.repository, }, metrics: {}, dependencies: {}, compatibility: {}, recommendations: [], }; if (checkScores) { const scores = await fetchPackageScores(packageName); analysis.metrics = { likes: scores.likeCount, pubPoints: scores.grantedPoints, maxPoints: scores.maxPoints, popularity: scores.popularityScore, health: calculateHealthScore(scores), }; analysis.recommendations.push(...generateScoreRecommendations(scores)); } if (checkDependencies) { const dependencies = packageInfo.latest.pubspec.dependencies || {}; const devDependencies = packageInfo.latest.pubspec.dev_dependencies || {}; analysis.dependencies = { runtime: await analyzeDependencies(dependencies), dev: await analyzeDependencies(devDependencies), }; analysis.recommendations.push(...generateDependencyRecommendations(analysis.dependencies)); } const compatibility = await checkFlutterCompatibility(packageInfo); analysis.compatibility = compatibility; if (compatibility.issues.length > 0) { analysis.recommendations.push(...compatibility.issues.map(issue => ({ type: 'compatibility', message: issue, severity: 'warning', }))); } const securityCheck = await performSecurityCheck(packageName, packageInfo); if (securityCheck.issues.length > 0) { analysis.security = securityCheck; analysis.recommendations.push(...securityCheck.issues.map(issue => ({ type: 'security', message: issue.message, severity: issue.severity, }))); } analysis.summary = generatePackageSummary(analysis); return { content: [ { type: 'text', text: JSON.stringify(analysis, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error analyzing package: ${error.message}`, }, ], }; } } async function fetchPackageInfo(packageName) { try { const response = await axios.get(`${PUB_API_BASE}/packages/${packageName}`); return response.data; } catch (error) { if (error.response?.status === 404) { throw new Error(`Package '${packageName}' not found on pub.dev`); } throw error; } } async function fetchPackageScores(packageName) { try { const response = await axios.get(`${PUB_API_BASE}/packages/${packageName}/score`); return response.data; } catch (error) { console.error('Error fetching scores:', error.message); return null; } } function calculateHealthScore(scores) { if (!scores) return 'Unknown'; const percentage = (scores.grantedPoints / scores.maxPoints) * 100; if (percentage >= 90) return 'Excellent'; if (percentage >= 75) return 'Good'; if (percentage >= 50) return 'Fair'; return 'Poor'; } async function analyzeDependencies(deps) { const analysis = { count: Object.keys(deps).length, packages: {}, }; for (const [depName, version] of Object.entries(deps)) { try { const depInfo = await fetchPackageInfo(depName); const latestVersion = depInfo.latest.version; const isOutdated = !isVersionCompatible(version, latestVersion); analysis.packages[depName] = { specified: version, latest: latestVersion, isOutdated, lastUpdated: depInfo.latest.published, }; } catch (error) { analysis.packages[depName] = { specified: version, error: 'Failed to fetch package info', }; } } return analysis; } function isVersionCompatible(specified, latest) { if (specified === 'any' || specified === latest) return true; if (specified.startsWith('^')) { const specParts = specified.substring(1).split('.').map(Number); const latestParts = latest.split('.').map(Number); return specParts[0] === latestParts[0] && (latestParts[1] >= specParts[1] || (latestParts[1] === specParts[1] && latestParts[2] >= specParts[2])); } return false; } async function checkFlutterCompatibility(packageInfo) { const compatibility = { flutter: false, dart: null, platforms: [], issues: [], }; const pubspec = packageInfo.latest.pubspec; if (pubspec.environment) { compatibility.dart = pubspec.environment.sdk; if (pubspec.dependencies?.flutter) { compatibility.flutter = true; } } if (pubspec.flutter) { if (pubspec.flutter.plugin) { compatibility.platforms = Object.keys(pubspec.flutter.plugin.platforms || {}); } } if (!compatibility.flutter && packageInfo.latest.pubspec.description?.toLowerCase().includes('flutter')) { compatibility.issues.push('Package mentions Flutter but doesn\'t declare Flutter dependency'); } if (compatibility.dart && !isValidDartConstraint(compatibility.dart)) { compatibility.issues.push('Dart SDK constraint might be too restrictive'); } return compatibility; } function isValidDartConstraint(constraint) { if (!constraint.includes('>=') || !constraint.includes('<')) { return false; } return true; } async function performSecurityCheck(packageName, packageInfo) { const security = { issues: [], score: 100, }; if (!packageInfo.latest.pubspec.homepage && !packageInfo.latest.pubspec.repository) { security.issues.push({ message: 'Package lacks homepage or repository URL', severity: 'low', }); security.score -= 10; } const age = Date.now() - new Date(packageInfo.latest.published).getTime(); const daysSinceUpdate = age / (1000 * 60 * 60 * 24); if (daysSinceUpdate > 365) { security.issues.push({ message: `Package hasn't been updated in ${Math.floor(daysSinceUpdate)} days`, severity: 'medium', }); security.score -= 20; } if (packageInfo.versions.length < 3) { security.issues.push({ message: 'Package has very few releases, might be unstable', severity: 'low', }); security.score -= 15; } return security; } function generateScoreRecommendations(scores) { const recommendations = []; if (!scores) { return [{ type: 'metrics', message: 'Package scores not available', severity: 'info', }]; } const percentage = (scores.grantedPoints / scores.maxPoints) * 100; if (percentage < 75) { recommendations.push({ type: 'quality', message: `Package health score is ${percentage.toFixed(1)}%. Consider packages with higher scores`, severity: 'warning', }); } if (scores.popularityScore < 0.5) { recommendations.push({ type: 'popularity', message: 'Package has low popularity. Consider more widely adopted alternatives', severity: 'info', }); } return recommendations; } function generateDependencyRecommendations(deps) { const recommendations = []; let outdatedCount = 0; for (const dep of Object.values(deps.runtime.packages)) { if (dep.isOutdated) outdatedCount++; } if (outdatedCount > 0) { recommendations.push({ type: 'dependencies', message: `${outdatedCount} outdated dependencies found`, severity: 'warning', }); } if (deps.runtime.count > 20) { recommendations.push({ type: 'dependencies', message: 'High number of dependencies may increase bundle size and complexity', severity: 'info', }); } return recommendations; } function generatePackageSummary(analysis) { const summary = { overallRating: 'Good', strengths: [], concerns: [], recommendation: '', }; if (analysis.metrics.health === 'Excellent' || analysis.metrics.health === 'Good') { summary.strengths.push('High package quality score'); } if (analysis.metrics.popularity > 0.7) { summary.strengths.push('Popular and well-adopted package'); } if (analysis.compatibility.flutter) { summary.strengths.push('Flutter-compatible'); } if (analysis.recommendations.filter(r => r.severity === 'warning').length > 2) { summary.overallRating = 'Fair'; summary.concerns.push('Multiple warnings detected'); } if (analysis.security && analysis.security.score < 70) { summary.overallRating = 'Poor'; summary.concerns.push('Security concerns identified'); } summary.recommendation = summary.overallRating === 'Good' || summary.overallRating === 'Excellent' ? 'Package is recommended for use' : 'Consider alternatives or proceed with caution'; return summary; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dvillegastech/flutter_mcp_2'

If you have feedback or need assistance with the MCP directory API, please join our Discord server