Optimized package analysis

This commit is contained in:
2026-04-14 23:23:23 +05:30
parent be8d796bb3
commit 746593c60d

View File

@@ -1,7 +1,7 @@
import { Button } from "./components/ui/button"; import { Button } from "./components/ui/button";
import { Label } from "./components/ui/label"; import { Label } from "./components/ui/label";
import { Input } from "./components/ui/input"; import { Input } from "./components/ui/input";
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef, useMemo, useCallback, memo } from "react";
import { Edit2, ChevronDown, ChevronUp, AlertCircle, Search, Package, Upload, CheckCircle2, XCircle, AlertTriangle, FileJson, TrendingUp, TrendingDown, Copy, Check, Terminal, X, Save, FolderOpen } from "lucide-react"; import { Edit2, ChevronDown, ChevronUp, AlertCircle, Search, Package, Upload, CheckCircle2, XCircle, AlertTriangle, FileJson, TrendingUp, TrendingDown, Copy, Check, Terminal, X, Save, FolderOpen } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { PackageSelector } from "./PackageSelector"; import { PackageSelector } from "./PackageSelector";
@@ -98,7 +98,7 @@ function satisfiesVersion(installedVersion: string, requiredRange: string): bool
} }
} }
function PackageResultCard({ result, onVersionChange, nodeVersion }: { const PackageResultCard = memo(function PackageResultCard({ result, onVersionChange, nodeVersion }: {
result: AnalysisResult; result: AnalysisResult;
onVersionChange: (packageName: string, version: string) => void; onVersionChange: (packageName: string, version: string) => void;
nodeVersion: string; nodeVersion: string;
@@ -111,11 +111,24 @@ function PackageResultCard({ result, onVersionChange, nodeVersion }: {
const hasVersionChange = result.selectedVersion && result.selectedVersion !== result.currentVersion; const hasVersionChange = result.selectedVersion && result.selectedVersion !== result.currentVersion;
const hasCompatibilityIssue = !result.nodeCompatible || (result.peerConflicts && result.peerConflicts.length > 0); const hasCompatibilityIssue = !result.nodeCompatible || (result.peerConflicts && result.peerConflicts.length > 0);
const sortedVersions = result.availableVersions ? sortVersions([...result.availableVersions]) : []; const sortedVersions = useMemo(() =>
const filteredAvailableVersions = sortedVersions.filter(v => result.availableVersions ? sortVersions([...result.availableVersions]) : [],
v.toLowerCase().includes(versionSearchQuery.toLowerCase()) [result.availableVersions]
); );
const filteredAvailableVersions = useMemo(() =>
sortedVersions.filter(v =>
v.toLowerCase().includes(versionSearchQuery.toLowerCase())
),
[sortedVersions, versionSearchQuery]
);
const handleVersionChange = useCallback((version: string) => {
onVersionChange(result.name, version);
setVersionDropdownOpen(false);
setVersionSearchQuery("");
}, [result.name, onVersionChange]);
useEffect(() => { useEffect(() => {
if (!versionDropdownOpen) return; if (!versionDropdownOpen) return;
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
@@ -233,11 +246,7 @@ function PackageResultCard({ result, onVersionChange, nodeVersion }: {
filteredAvailableVersions.map((version) => ( filteredAvailableVersions.map((version) => (
<button <button
key={version} key={version}
onClick={() => { onClick={() => handleVersionChange(version)}
onVersionChange(result.name, version);
setVersionDropdownOpen(false);
setVersionSearchQuery('');
}}
className={`w-full text-left px-3 py-2 rounded-md transition-colors ${ className={`w-full text-left px-3 py-2 rounded-md transition-colors ${
version === activeVersion version === activeVersion
? 'bg-accent font-medium border border-border' ? 'bg-accent font-medium border border-border'
@@ -419,7 +428,7 @@ function PackageResultCard({ result, onVersionChange, nodeVersion }: {
</div> </div>
</div> </div>
); );
} });
export function PackageDetails() { export function PackageDetails() {
const [packages, setPackages] = useState<PackageData[]>([]); const [packages, setPackages] = useState<PackageData[]>([]);
@@ -480,7 +489,7 @@ export function PackageDetails() {
}); });
}; };
const handleVersionChange = (packageName: string, version: string) => { const handleVersionChange = useCallback((packageName: string, version: string) => {
const newSelections = new Map(versionSelections); const newSelections = new Map(versionSelections);
newSelections.set(packageName, version); newSelections.set(packageName, version);
setVersionSelections(newSelections); setVersionSelections(newSelections);
@@ -505,7 +514,7 @@ export function PackageDetails() {
} else { } else {
toast.info(`${packageName}${version}`); toast.info(`${packageName}${version}`);
} }
}; }, [versionSelections, analysisResults, nodeVersion]);
useEffect(() => { useEffect(() => {
if (analysisResults.length > 0) { if (analysisResults.length > 0) {
@@ -995,44 +1004,69 @@ export function PackageDetails() {
} }
} }
const selectedPkgs = packages.filter(p => selectedPackages.has(p.name)); const selectedPkgs = useMemo(() =>
const filteredVersions = nodeVersions.filter(v => { packages.filter(p => selectedPackages.has(p.name)),
const searchLower = nodeVersionSearch.toLowerCase(); [packages, selectedPackages]
const matchesVersion = v.version.toLowerCase().includes(searchLower); );
const matchesLtsName = typeof v.lts === 'string' && v.lts.toLowerCase().includes(searchLower);
const matchesLtsKeyword = v.lts && searchLower.includes('lts');
return matchesVersion || matchesLtsName || matchesLtsKeyword;
});
const selectedVersionInfo = nodeVersions.find(v => v.version.replace('v', '') === nodeVersion); const filteredVersions = useMemo(() =>
nodeVersions.filter(v => {
const searchLower = nodeVersionSearch.toLowerCase();
const matchesVersion = v.version.toLowerCase().includes(searchLower);
const matchesLtsName = typeof v.lts === 'string' && v.lts.toLowerCase().includes(searchLower);
const matchesLtsKeyword = v.lts && searchLower.includes('lts');
return matchesVersion || matchesLtsName || matchesLtsKeyword;
}),
[nodeVersions, nodeVersionSearch]
);
const selectedVersionInfo = useMemo(() =>
nodeVersions.find(v => v.version.replace('v', '') === nodeVersion),
[nodeVersions, nodeVersion]
);
// Count issues // Count issues
const compatibilityIssues = analysisResults.filter(r => !r.nodeCompatible || (r.peerConflicts && r.peerConflicts.length > 0)).length; const compatibilityIssues = useMemo(() =>
const modifiedPackages = analysisResults.filter(r => r.selectedVersion && r.selectedVersion !== r.currentVersion).length; analysisResults.filter(r => !r.nodeCompatible || (r.peerConflicts && r.peerConflicts.length > 0)).length,
[analysisResults]
);
const modifiedPackages = useMemo(() =>
analysisResults.filter(r => r.selectedVersion && r.selectedVersion !== r.currentVersion).length,
[analysisResults]
);
// Get packages with compatibility issues among modified packages // Get packages with compatibility issues among modified packages
const modifiedWithIssues = analysisResults.filter(r => const modifiedWithIssues = useMemo(() =>
r.selectedVersion && analysisResults.filter(r =>
r.selectedVersion !== r.currentVersion && r.selectedVersion &&
(!r.nodeCompatible || (r.peerConflicts && r.peerConflicts.length > 0)) r.selectedVersion !== r.currentVersion &&
(!r.nodeCompatible || (r.peerConflicts && r.peerConflicts.length > 0))
),
[analysisResults]
); );
// Get unmodified packages with issues // Get unmodified packages with issues
const unmodifiedWithIssues = analysisResults.filter(r => const unmodifiedWithIssues = useMemo(() =>
(!r.selectedVersion || r.selectedVersion === r.currentVersion) && analysisResults.filter(r =>
(!r.nodeCompatible || (r.peerConflicts && r.peerConflicts.length > 0)) (!r.selectedVersion || r.selectedVersion === r.currentVersion) &&
(!r.nodeCompatible || (r.peerConflicts && r.peerConflicts.length > 0))
),
[analysisResults]
); );
// Generate npm install command // Generate npm install command
const generateUpgradeCommand = () => { const upgradeCommand = useMemo(() => {
const modifiedPkgs = analysisResults.filter(r => r.selectedVersion && r.selectedVersion !== r.currentVersion); const modifiedPkgs = analysisResults.filter(r => r.selectedVersion && r.selectedVersion !== r.currentVersion);
if (modifiedPkgs.length === 0) return ''; if (modifiedPkgs.length === 0) return '';
const packages = modifiedPkgs.map(pkg => `${pkg.name}@${pkg.selectedVersion}`).join(' '); const packages = modifiedPkgs.map(pkg => `${pkg.name}@${pkg.selectedVersion}`).join(' ');
return `npm install ${packages}`; return `npm install ${packages}`;
}; }, [analysisResults]);
const copyToClipboard = async (text: string) => { const generateUpgradeCommand = () => upgradeCommand;
const copyToClipboard = useCallback(async (text: string) => {
try { try {
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(text);
setCopiedCommand(true); setCopiedCommand(true);
@@ -1041,7 +1075,7 @@ export function PackageDetails() {
} catch (err) { } catch (err) {
toast.error('Failed to copy to clipboard'); toast.error('Failed to copy to clipboard');
} }
}; }, []);
return ( return (
<div className="bg-background"> <div className="bg-background">