Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7d32ae9
feat: add CNPM registry browser
thonatos Aug 5, 2026
9871e69
docs: archive completed changes and sync specs
thonatos Aug 5, 2026
cd11b46
fix: address copilot review on CNPM registry browser
thonatos Aug 5, 2026
1809236
fix: address suppressed copilot suggestions
thonatos Aug 5, 2026
f5d6761
fix: apply second-round copilot review suggestions
thonatos Aug 5, 2026
3c6b335
fix: apply third-round copilot review suggestions
thonatos Aug 5, 2026
08c4dd1
fix: apply fourth-round copilot review suggestions
thonatos Aug 5, 2026
f2100d1
fix: skip search fetch when query is blank, keep dir error in own row
thonatos Aug 5, 2026
a9aada2
fix: full self-review of CNPM registry browser
thonatos Aug 5, 2026
d0a8b55
fix: address suppressed copilot suggestions on download card and sear…
thonatos Aug 5, 2026
35663c6
fix: deterministic version fallback, sanitize chart id, support git+h…
thonatos Aug 5, 2026
2a0af00
fix: strip userinfo from git repos, render explicit error for empty m…
thonatos Aug 5, 2026
1bb8b9e
fix: encode scoped package names as a single path segment
thonatos Aug 5, 2026
dfd84c0
fix: typography and avatar polish from copilot review
thonatos Aug 5, 2026
78f1bf8
fix: clamp download range and use trailing-slash files root
thonatos Aug 5, 2026
7c10bd2
fix: normalize trailing slashes in getDir paths
thonatos Aug 6, 2026
34d0317
fix: address copilot suggestions on stats, autofocus, dates, keys
thonatos Aug 6, 2026
08b1717
fix: generate download range dates in UTC
thonatos Aug 6, 2026
e12067b
fix: normalize invalid version query params to the resolved version
thonatos Aug 6, 2026
1607e50
docs: cap Copilot review re-requests at 5 rounds per PR
thonatos Aug 6, 2026
dce1945
chore: merge main into feat/cnpm-registry-browser, resolve pnpm-lock.…
Copilot Aug 6, 2026
5c0d8fa
fix: validate homepage/tarball URL schemes to prevent javascript: inj…
Copilot Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ pnpm migrate:reconcile # explicit migration reconcile
- Use OpenSpec for scoped product or behavior changes.
- Run `pnpm verify` before release or PR validation when feasible.
- Never print, commit, or document real `.env` values, tokens, private keys, user data, database URLs, or production host secrets.
- Copilot PR review loop: when iterating on Copilot review comments, do not re-request the review more than **5 rounds** per PR. Once 5 rounds have been addressed and pushed, stop re-requesting; a PR with only suppressed/skipped suggestions after the cap may be considered review-complete at the reviewer's or maintainer's discretion.
11 changes: 11 additions & 0 deletions apps/web/app/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
User,
Settings,
LogOut,
Boxes,
} from "lucide-react";
import { ThemeToggle } from "./ThemeToggle";
import { useNavTransition } from "./NavProgress";
Expand Down Expand Up @@ -102,6 +103,10 @@ export function Header() {
{z.name}
</NavLink>
))}
<NavLink to="/cnpm">
<Boxes className="h-4 w-4" />
CNPM
</NavLink>
<NavLink to="/api">
<Code className="h-4 w-4" />
API
Expand Down Expand Up @@ -317,6 +322,12 @@ function MobileNavTrigger({ visibleZones = [] }: { visibleZones?: any[] }) {
>
<Code className="h-5 w-5 text-primary" /> API
</Link>
<Link
to="/cnpm"
className="flex min-h-12 items-center gap-3 rounded-xl bg-muted px-3 text-sm text-foreground transition-colors hover:bg-accent"
>
<Boxes className="h-5 w-5 text-primary" /> CNPM
</Link>
<Link
to="/about"
className="flex min-h-12 items-center gap-3 rounded-xl bg-muted px-3 text-sm text-foreground transition-colors hover:bg-accent"
Expand Down
78 changes: 78 additions & 0 deletions apps/web/app/components/cnpm/DepsView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Link } from "react-router";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import type { RegistryManifest } from "~/lib/registry/types";

type DepGroup = {
key: "dependencies" | "devDependencies" | "optionalDependencies" | "peerDependencies";
label: string;
};

const GROUPS: DepGroup[] = [
{ key: "dependencies", label: "dependencies" },
{ key: "devDependencies", label: "devDependencies" },
{ key: "optionalDependencies", label: "optionalDependencies" },
{ key: "peerDependencies", label: "peerDependencies" },
];

export function DepsView({ manifest, version }: { manifest: RegistryManifest; version: string }) {
const versionData = manifest.versions?.[version];
if (!versionData) {
return <p className="text-sm text-muted-foreground">该版本没有依赖信息</p>;
}

const groups = GROUPS.filter((group) => {
const deps = versionData[group.key];
return deps && Object.keys(deps).length > 0;
});

if (groups.length === 0) {
return <p className="text-sm text-muted-foreground">该版本没有任何依赖</p>;
}

return (
<div className="flex flex-col gap-6">
{groups.map((group) => {
const deps = versionData[group.key]!;
const entries = Object.entries(deps).sort(([a], [b]) => a.localeCompare(b));
return (
<div key={group.key} className="flex flex-col gap-2">
<h3 className="font-mono text-sm font-medium text-muted-foreground">
{group.label}
<span className="ml-2 text-muted-foreground/70">{entries.length}</span>
</h3>
<Table>
<TableHeader>
<TableRow>
<TableHead>名称</TableHead>
<TableHead>版本范围</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{entries.map(([pkg, spec]) => (
<TableRow key={pkg}>
<TableCell>
<Link
to={`/cnpm/pkg/${pkg}`}
className="text-foreground hover:text-primary"
>
{pkg}
</Link>
Comment on lines +61 to +66
</TableCell>
<TableCell className="font-mono text-muted-foreground">{spec}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
})}
</div>
);
}
132 changes: 132 additions & 0 deletions apps/web/app/components/cnpm/DownloadCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { getDownloads, useRegistryQuery } from "~/lib/registry/client";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "~/components/ui/chart";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
import { Skeleton } from "~/components/ui/skeleton";
import { Empty, EmptyDescription, EmptyTitle } from "~/components/ui/empty";
import { Alert, AlertDescription } from "~/components/ui/alert";
import { Button } from "~/components/ui/button";

const chartConfig = {
downloads: {
label: "下载量",
color: "var(--primary)",
},
} satisfies ChartConfig;

export function DownloadCard({
pkgName,
range = 7,
}: {
pkgName: string;
range?: number;
}) {
const { data, error, loading, retry } = useRegistryQuery(
() => getDownloads(pkgName, range),
[pkgName, range],
);

if (loading) {
return (
<Card>
<CardHeader>
<CardTitle>下载量</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-3">
<Skeleton className="h-6 w-24" />
<Skeleton className="h-24 w-full" />
</CardContent>
</Card>
);
}

if (error) {
return (
<Card>
<CardHeader>
<CardTitle>下载量</CardTitle>
</CardHeader>
<CardContent>
<Alert variant="destructive">
<AlertDescription className="flex items-center justify-between gap-3">
下载数据加载失败
<Button type="button" variant="outline" size="sm" onClick={retry}>
重试
</Button>
</AlertDescription>
</Alert>
</CardContent>
</Card>
);
}

const points = data?.downloads || [];
const total = points.reduce((sum, point) => sum + point.downloads, 0);

if (points.length === 0) {
return (
<Card>
<CardHeader>
<CardTitle>下载量</CardTitle>
</CardHeader>
<CardContent>
<Empty>
<EmptyTitle>暂无数据</EmptyTitle>
<EmptyDescription>该包暂无下载数据</EmptyDescription>
</Empty>
</CardContent>
</Card>
);
}

return (
<Card>
<CardHeader>
<CardTitle className="flex items-baseline gap-2">
近 {range} 天下载
<span className="font-mono text-xl font-semibold tabular-nums text-foreground">
{total.toLocaleString("en-US")}
</span>
</CardTitle>
</CardHeader>
<CardContent>
<ChartContainer
config={chartConfig}
className="h-32 aspect-auto [&_.recharts-text]:fill-muted-foreground"
>
<AreaChart data={points} margin={{ left: 0, right: 0, top: 4, bottom: 0 }}>
<defs>
<linearGradient id="cnpm-download-fill" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="var(--color-downloads)" stopOpacity={0.35} />
<stop offset="95%" stopColor="var(--color-downloads)" stopOpacity={0.02} />
</linearGradient>
</defs>
<CartesianGrid vertical={false} strokeDasharray="4 4" />
<XAxis
dataKey="day"
tickLine={false}
axisLine={false}
tickMargin={8}
minTickGap={24}
tickFormatter={(value: string) => value.slice(5)}
/>
<ChartTooltip content={<ChartTooltipContent />} />
<Area
dataKey="downloads"
type="monotone"
fill="url(#cnpm-download-fill)"
stroke="var(--color-downloads)"
strokeWidth={2}
dot={false}
/>
</AreaChart>
</ChartContainer>
</CardContent>
</Card>
);
}
Loading