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}
-
+
|
)}
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);