From 0f4c1395ac1e3287ef1a9c8e7b81ac50105866db Mon Sep 17 00:00:00 2001 From: Felix Schneider <99918022+trueberryless@users.noreply.github.com> Date: Sun, 12 Jul 2026 18:58:22 +0200 Subject: [PATCH 1/4] feat: add deprecation warning --- src/components/App.tsx | 68 +++++++++++++++++++++++++++++++++--------- src/lib/api.ts | 45 ++++++++++++++++++++-------- 2 files changed, 86 insertions(+), 27 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 0b5202e..a34672b 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 = @@ -128,9 +133,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,13 +146,30 @@ 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) { + const chunk = items.slice(i, i + chunkSize); + await Promise.all( + chunk.map(async (item) => { + item.deprecated = await getPackageIsDeprecated(item.name).catch(() => false); + }), + ); + onProgress( + 90 + Math.floor(((i + chunk.length) / items.length) * 9), + "Checking deprecation status...", + ); + } setState( items.length > 0 @@ -181,7 +203,7 @@ export default function App() { const downloadsStr = formatDownloads(pkg.downloads); const trafficStr = formatTraffic(pkg.traffic); const versionStr = pkg.version || "any"; - const pkgLink = `[${pkg.name}](https://npmx.dev/${pkg.name})`; + const pkgLink = `[${pkg.name}](https://npmx.dev/${pkg.name})${pkg.deprecated ? " ⚠️" : ""}`; if (!isDev()) { md += escapeMdTable`| ${indexStr} | ${downloadsStr} | ${trafficStr} | ${versionStr} | ${pkgLink} |\n`; @@ -500,14 +522,32 @@ export default function App() { - - {pkg.name} - +
+ + {pkg.name} + + + + Deprecated + + + +
)} diff --git a/src/lib/api.ts b/src/lib/api.ts index d6b3038..ab8db5e 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -9,12 +9,19 @@ import { import { hash } from "./util"; async function cachedFetch(url: string, options: RequestInit = {}) { - const hashKey = `fetch:${hash(url + (options.body || ""))}`; + const bodyString = options.body ? String(options.body) : ""; + const hashKey = `fetch:${hash(url + bodyString)}`; const cached = localStorage.getItem(hashKey); if (cached) { const { data, expiry }: { data: P; expiry: number } = JSON.parse(cached); - if (Date.now() < expiry) return { data, isCached: true as const }; + if (Date.now() < expiry) { + return { + data, + isCached: true as const, + commit: (_processedData: P, _ttlMinutes?: number) => {}, + }; + } localStorage.removeItem(hashKey); } @@ -146,22 +153,22 @@ async function getPackageIsDeprecated(name: string): Promise { return result.data.deprecated?.deprecated ?? false; } -interface DevDependentsRow { - id: string; - key: string; - value: string & { version: undefined }; +interface DependentValueObject { + version?: string; + name?: string; + deprecated?: { deprecated?: boolean }; } interface DependentsRow { id: string; key: string; - value: { name: string; version: string }; + value: string | DependentValueObject | null; } interface Dependents { total_rows: number; offset: number; - rows: DependentsRow[] | DevDependentsRow[]; + rows: DependentsRow[]; } interface ProcessedDependent { @@ -196,11 +203,23 @@ async function getSortedDependents( const MAX_DEPENDENTS = 3000; const processed: ProcessedDependent[] = data.rows - .map((r) => ({ - n: r.id, - v: r.value?.version?.trim() ?? "", - d: allStats[r.id] ?? 0, - })) + .map((r) => { + let v = ""; + if (typeof r.value === "string") { + v = r.value.trim(); + } else if ( + r.value !== null && + typeof r.value === "object" && + "version" in r.value + ) { + v = String(r.value.version).trim(); + } + return { + n: r.id, + v, + d: allStats[r.id] ?? 0, + }; + }) .sort((a, b) => b.d - a.d) .slice(0, MAX_DEPENDENTS); From 674c16dcacfaa6ab52d64c8c5cd370f8fc0403e8 Mon Sep 17 00:00:00 2001 From: Felix Schneider <99918022+trueberryless@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:32:44 +0200 Subject: [PATCH 2/4] style: format --- src/components/App.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index a34672b..1269530 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -162,7 +162,9 @@ export default function App() { const chunk = items.slice(i, i + chunkSize); await Promise.all( chunk.map(async (item) => { - item.deprecated = await getPackageIsDeprecated(item.name).catch(() => false); + item.deprecated = await getPackageIsDeprecated(item.name).catch( + () => false, + ); }), ); onProgress( From 6490c5ef8c3609bee4c8e2bb7fa4de59ce107017 Mon Sep 17 00:00:00 2001 From: Felix Schneider <99918022+trueberryless@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:56:43 +0200 Subject: [PATCH 3/4] Update src/components/App.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/components/App.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/App.tsx b/src/components/App.tsx index 1269530..cd8af8b 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -539,6 +539,8 @@ export default function App() { fill="none" viewBox="0 0 24 24" stroke="currentColor" + role="img" + aria-label="Deprecated" > Deprecated Date: Sun, 12 Jul 2026 19:59:59 +0200 Subject: [PATCH 4/4] revert: api changes --- src/lib/api.ts | 45 +++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/src/lib/api.ts b/src/lib/api.ts index ab8db5e..d6b3038 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -9,19 +9,12 @@ import { import { hash } from "./util"; async function cachedFetch(url: string, options: RequestInit = {}) { - const bodyString = options.body ? String(options.body) : ""; - const hashKey = `fetch:${hash(url + bodyString)}`; + const hashKey = `fetch:${hash(url + (options.body || ""))}`; const cached = localStorage.getItem(hashKey); if (cached) { const { data, expiry }: { data: P; expiry: number } = JSON.parse(cached); - if (Date.now() < expiry) { - return { - data, - isCached: true as const, - commit: (_processedData: P, _ttlMinutes?: number) => {}, - }; - } + if (Date.now() < expiry) return { data, isCached: true as const }; localStorage.removeItem(hashKey); } @@ -153,22 +146,22 @@ async function getPackageIsDeprecated(name: string): Promise { return result.data.deprecated?.deprecated ?? false; } -interface DependentValueObject { - version?: string; - name?: string; - deprecated?: { deprecated?: boolean }; +interface DevDependentsRow { + id: string; + key: string; + value: string & { version: undefined }; } interface DependentsRow { id: string; key: string; - value: string | DependentValueObject | null; + value: { name: string; version: string }; } interface Dependents { total_rows: number; offset: number; - rows: DependentsRow[]; + rows: DependentsRow[] | DevDependentsRow[]; } interface ProcessedDependent { @@ -203,23 +196,11 @@ async function getSortedDependents( const MAX_DEPENDENTS = 3000; const processed: ProcessedDependent[] = data.rows - .map((r) => { - let v = ""; - if (typeof r.value === "string") { - v = r.value.trim(); - } else if ( - r.value !== null && - typeof r.value === "object" && - "version" in r.value - ) { - v = String(r.value.version).trim(); - } - return { - n: r.id, - v, - d: allStats[r.id] ?? 0, - }; - }) + .map((r) => ({ + n: r.id, + v: r.value?.version?.trim() ?? "", + d: allStats[r.id] ?? 0, + })) .sort((a, b) => b.d - a.d) .slice(0, MAX_DEPENDENTS);