diff --git a/src/components/App.tsx b/src/components/App.tsx index 0b5202e..7e418db 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,7 +1,11 @@ // @ts-expect-error semver has no types import semver from "semver"; import { createSignal, For, onMount, Show } from "solid-js"; -import { getBasePackageSize, getSortedDependents } from "#lib/api"; +import { + getBasePackageSize, + getPackageIsDeprecated, + getSortedDependents, +} from "#lib/api"; import { escapeMdTable, formatDownloads, @@ -14,6 +18,7 @@ interface AnalysisResult { version: string; downloads: number; traffic: number; + deprecated: boolean; } type State = @@ -31,6 +36,7 @@ export default function App() { const [error, setError] = createSignal(""); const [state, setState] = createSignal({ status: "initial" }); const [copyBtnLabel, setCopyBtnLabel] = createSignal("Copy as Markdown"); + let activeAnalysisId = 0; const isInitial = () => state().status === "initial"; const showEmpty = () => @@ -101,6 +107,7 @@ export default function App() { const pkg = pkgInput().trim(); if (!pkg) return; + const currentAnalysisId = ++activeAnalysisId; setLoading(true); setError(""); setState({ status: "no-results" }); @@ -128,9 +135,9 @@ export default function App() { isDev: dev, onProgress, }); - onProgress(99, "Calculating traffic and versions..."); + onProgress(90, "Calculating traffic and versions..."); - const items = sortedDeps + const items: AnalysisResult[] = sortedDeps .filter((d) => { if (!requestedRange) return true; if (d.v === requestedRange) return true; @@ -141,14 +148,36 @@ export default function App() { return false; } }) + .slice(0, limit) .map((d) => ({ name: d.n, version: d.v, downloads: d.d, traffic: d.d * pkgSize, - })) - .slice(0, limit); + deprecated: false, + })); + + onProgress(90, "Checking deprecation status..."); + const chunkSize = 50; + for (let i = 0; i < items.length; i += chunkSize) { + if (currentAnalysisId !== activeAnalysisId) return; + const chunk = items.slice(i, i + chunkSize); + await Promise.all( + chunk.map(async (item) => { + item.deprecated = await getPackageIsDeprecated(item.name).catch( + () => false, + ); + }), + ); + if (currentAnalysisId !== activeAnalysisId) return; + onProgress( + 90 + Math.floor(((i + chunk.length) / items.length) * 9), + "Checking deprecation status...", + ); + } + + if (currentAnalysisId !== activeAnalysisId) return; setState( items.length > 0 ? { status: "results", items } @@ -156,24 +185,36 @@ export default function App() { ); onProgress(100, "Analysis complete"); } catch (err) { + if (currentAnalysisId !== activeAnalysisId) return; console.error(err); setError((err as Error).message); } finally { - setLoading(false); + if (currentAnalysisId === activeAnalysisId) { + setLoading(false); + } } } + function getPackageNote(pkg: AnalysisResult): string { + if (pkg.deprecated) return "Deprecated"; + return ""; + } + async function copyAsMarkdown() { const currentState = state(); if (currentState.status !== "results") return; + const hasNotes = currentState.items.some( + (pkg) => getPackageNote(pkg) !== "", + ); + let md = ""; if (!isDev()) { - md += "| # | Downloads/month | Traffic | Version | Package |\n"; - md += "|---|-----------------|---------|---------|---------|\n"; + md += `| # | Downloads/month | Traffic | Version | Package |${hasNotes ? " Notes |" : ""}\n`; + md += `|---|-----------------|---------|---------|---------|${hasNotes ? "-------|" : ""}\n`; } else { - md += "| # | Downloads/month | Package |\n"; - md += "|---|-----------------|---------|\n"; + md += `| # | Downloads/month | Package |${hasNotes ? " Notes |" : ""}\n`; + md += `|---|-----------------|---------|${hasNotes ? "-------|" : ""}\n`; } currentState.items.forEach((pkg, i) => { @@ -182,12 +223,17 @@ export default function App() { const trafficStr = formatTraffic(pkg.traffic); const versionStr = pkg.version || "any"; const pkgLink = `[${pkg.name}](https://npmx.dev/${pkg.name})`; + const noteStr = getPackageNote(pkg); if (!isDev()) { - md += escapeMdTable`| ${indexStr} | ${downloadsStr} | ${trafficStr} | ${versionStr} | ${pkgLink} |\n`; + md += escapeMdTable`| ${indexStr} | ${downloadsStr} | ${trafficStr} | ${versionStr} | ${pkgLink} |`; } else { - md += escapeMdTable`| ${indexStr} | ${downloadsStr} | ${pkgLink} |\n`; + md += escapeMdTable`| ${indexStr} | ${downloadsStr} | ${pkgLink} |`; + } + if (hasNotes) { + md += escapeMdTable` ${noteStr} |`; } + md += "\n"; }); try { @@ -500,14 +546,34 @@ export default function App() { - - {pkg.name} - +
+ + {pkg.name} + + + + Deprecated + + + +
)}