Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 11 additions & 11 deletions .agents/skills/cnode-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ Keep repository documentation concise, current, safe, and owned by one source.

## Choose The Owner

| Content | Location |
| --- | --- |
| Generated API reference asset | `apps/web/public/openapi.json` |
| Runtime, data, and design-system architecture | `docs/arch/` |
| Business rules, community governance, and legacy knowledge | `docs/biz/` |
| Deployment guide, example configuration, Compose, and operator helpers | `docs/deployment/` |
| Project overview | `README.md` |
| Agent boundaries and skill triggers | `AGENTS.md` |
| Contributor workflow | `CONTRIBUTING.md` |
| Vulnerability reporting | `SECURITY.md` |
| App-specific setup and commands | `apps/*/README.md` |
| Content | Location |
| ---------------------------------------------------------------------- | ------------------------------ |
| Generated API reference asset | `apps/web/public/openapi.json` |
| Runtime, data, and design-system architecture | `docs/arch/` |
| Business rules, community governance, and legacy knowledge | `docs/biz/` |
| Deployment guide, example configuration, Compose, and operator helpers | `docs/deployment/` |
| Project overview | `README.md` |
| Agent boundaries and skill triggers | `AGENTS.md` |
| Contributor workflow | `CONTRIBUTING.md` |
| Vulnerability reporting | `SECURITY.md` |
| App-specific setup and commands | `apps/*/README.md` |

Do not create `README.md` or `index.md` inside `docs/`. Do not recreate top-level `wiki/`, `api/`, or `deployment/` documentation domains.

Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/migrate-radix-to-base/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ transforming, and record gaps in the report.
fetch base variants directly by URL instead
(`https://ui.shadcn.com/r/styles/base-<style>/<component>.json`).
3. PRISTINE wrappers, whole-project mode: `shadcn add <component>
--overwrite` delivers the base variant with the project's exact
--overwrite` delivers the base variant with the project's exact
icon/font/preset resolution. Never bulk `--all --overwrite`; go
component by component, or you drown in unrelated registry version
drift. PROGRESSIVE mode: never use `--overwrite` (it destroys the
Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/migrate-radix-to-base/wrapper-shapes.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ class existed; the open styling was previously data-[state=open]).
- Drop the radix `position` prop entirely; expose `alignItemWithTrigger`
(default true) picked from Positioner.Props, `sideOffset = 4`.
- Item anatomy: `ItemText` FIRST with `cn-select-item-text shrink-0
whitespace-nowrap`, then `ItemIndicator render={<span
className="cn-select-item-indicator" />}`.
whitespace-nowrap`, then `ItemIndicator render={<span
className="cn-select-item-indicator" />}`.
- Scroll arrows get `top-0 w-full` / `bottom-0 w-full`; List has no classes.

## Accordion animation placement
Expand Down
1 change: 1 addition & 0 deletions apps/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vitest/
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"test": "vp test run",
"worker:moderation": "tsx src/bootstrap.ts moderation-worker",
"typecheck": "tsc --noEmit",
"gen:openapi": "tsx scripts/gen-openapi.ts"
"gen:openapi": "tsx scripts/gen-openapi.ts && vp -C ../.. fmt apps/web/public/openapi.json"
},
"dependencies": {
"@cnode/db": "workspace:*",
Expand Down
4 changes: 1 addition & 3 deletions apps/api/test/account-auth-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,7 @@ describe("inactive account authentication policy", () => {
);

expect(response.status).toBe(302);
expect(response.headers.get("location")).toContain(
"/resend_activation?error=account_inactive",
);
expect(response.headers.get("location")).toContain("/resend_activation?error=account_inactive");
expect(response.headers.get("set-cookie")).toContain("github_oauth_state=");
expect(updateGithubInfo).not.toHaveBeenCalled();
expect(response.headers.get("set-cookie")).not.toContain("node_club=");
Expand Down
50 changes: 24 additions & 26 deletions apps/api/test/telemetry-sanitize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import {
SimpleSpanProcessor,
} from "@opentelemetry/sdk-trace-base";
import { describe, expect, test } from "vite-plus/test";
import {
isBlockedTelemetryAttribute,
SanitizingSpanProcessor,
} from "../src/telemetry/sanitize";
import { isBlockedTelemetryAttribute, SanitizingSpanProcessor } from "../src/telemetry/sanitize";

describe("telemetry span sanitization", () => {
test.each([
Expand Down Expand Up @@ -48,9 +45,7 @@ describe("telemetry span sanitization", () => {
test("removes sensitive span, event, and link values before export", async () => {
const exporter = new InMemorySpanExporter();
const provider = new BasicTracerProvider({
spanProcessors: [
new SanitizingSpanProcessor(new SimpleSpanProcessor(exporter)),
],
spanProcessors: [new SanitizingSpanProcessor(new SimpleSpanProcessor(exporter))],
});
const tracer = provider.getTracer("sanitize-test");
const sensitive = "fixture-private-value";
Expand All @@ -60,26 +55,29 @@ describe("telemetry span sanitization", () => {
traceFlags: 1,
});

tracer.startSpan("safe-operation", {
attributes: {
"http.request.method": "POST",
"http.request.header.authorization": sensitive,
"request.body": sensitive,
"db.statement": `select '${sensitive}'`,
"db.query.parameters": sensitive,
"url.full": `https://example.com/path?token=${sensitive}`,
},
links: [
{
context: trace.getSpanContext(linkedContext)!,
attributes: { "session.token": sensitive },
tracer
.startSpan("safe-operation", {
attributes: {
"http.request.method": "POST",
"http.request.header.authorization": sensitive,
"request.body": sensitive,
"db.statement": `select '${sensitive}'`,
"db.query.parameters": sensitive,
"url.full": `https://example.com/path?token=${sensitive}`,
},
],
}).addEvent("failure", {
"exception.type": "Error",
"exception.message": sensitive,
"exception.stacktrace": sensitive,
}).end();
links: [
{
context: trace.getSpanContext(linkedContext)!,
attributes: { "session.token": sensitive },
},
],
})
.addEvent("failure", {
"exception.type": "Error",
"exception.message": sensitive,
"exception.stacktrace": sensitive,
})
.end();
await provider.forceFlush();

const finishedSpan = exporter.getFinishedSpans()[0]!;
Expand Down
1 change: 1 addition & 0 deletions apps/web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vitest/
74 changes: 36 additions & 38 deletions apps/web/app/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
import { Button } from "./ui/button";
import { Skeleton } from "./ui/skeleton";
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
import {
Item,
ItemContent,
ItemDescription,
ItemGroup,
ItemMedia,
ItemTitle,
} from "./ui/item";
import { Item, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemTitle } from "./ui/item";
import { TimeAgo } from "./TimeAgo";
import { getAvatarFallback, getAvatarUrl, getTabLabel } from "~/lib/brand";
import { ArrowUpRight, Handshake, MessageCircle, Trophy } from "lucide-react";
Expand Down Expand Up @@ -112,34 +105,34 @@ function LatestReplies({ replies }: { replies: SidebarData["latest_replies"] })
</CardHeader>
<CardContent>
<ItemGroup>
{replies.map((reply) => {
const author = reply.author;
return (
<Item
key={reply.id}
render={<Link to={`/topic/${reply.topic_id}#${reply.id}`} />}
variant="muted"
size="xs"
>
<ItemMedia>
<Avatar className="size-8">
<AvatarImage
src={getAvatarUrl(author?.avatar_url, 32)}
alt={author?.loginname || "CNode"}
/>
<AvatarFallback>{getAvatarFallback(author?.loginname)}</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle className="line-clamp-2 w-full text-xs">{reply.topic_title}</ItemTitle>
<ItemDescription>
{author?.loginname || "社区成员"} ·{" "}
{reply.create_at ? <TimeAgo date={reply.create_at} /> : null}
</ItemDescription>
</ItemContent>
</Item>
);
})}
{replies.map((reply) => {
const author = reply.author;
return (
<Item
key={reply.id}
render={<Link to={`/topic/${reply.topic_id}#${reply.id}`} />}
variant="muted"
size="xs"
>
<ItemMedia>
<Avatar className="size-8">
<AvatarImage
src={getAvatarUrl(author?.avatar_url, 32)}
alt={author?.loginname || "CNode"}
/>
<AvatarFallback>{getAvatarFallback(author?.loginname)}</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle className="line-clamp-2 w-full text-xs">{reply.topic_title}</ItemTitle>
<ItemDescription>
{author?.loginname || "社区成员"} ·{" "}
{reply.create_at ? <TimeAgo date={reply.create_at} /> : null}
</ItemDescription>
</ItemContent>
</Item>
);
})}
</ItemGroup>
</CardContent>
</Card>
Expand All @@ -164,8 +157,13 @@ function NoReplyTopics({ topics }: { topics: SidebarData["no_reply_topics"] }) {
<ItemContent>
<ItemTitle className="line-clamp-2 w-full text-xs">{t.title}</ItemTitle>
<ItemDescription>
<span>{getTabLabel(t.tab)}</span>
{t.create_at ? <> · <TimeAgo date={t.create_at} /></> : null}
<span>{getTabLabel(t.tab)}</span>
{t.create_at ? (
<>
{" "}
· <TimeAgo date={t.create_at} />
</>
) : null}
</ItemDescription>
</ItemContent>
</Item>
Expand Down
5 changes: 1 addition & 4 deletions apps/web/app/components/cnpm/DepsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ export function DepsView({ manifest, version }: { manifest: RegistryManifest; ve
{entries.map(([pkg, spec]) => (
<TableRow key={pkg}>
<TableCell>
<Link
to={`/cnpm/pkg/${pkg}`}
className="text-foreground hover:text-primary"
>
<Link to={`/cnpm/pkg/${pkg}`} className="text-foreground hover:text-primary">
{pkg}
</Link>
</TableCell>
Expand Down
8 changes: 1 addition & 7 deletions apps/web/app/components/cnpm/DownloadCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,7 @@ const chartConfig = {
},
} satisfies ChartConfig;

export function DownloadCard({
pkgName,
range = 7,
}: {
pkgName: string;
range?: number;
}) {
export function DownloadCard({ pkgName, range = 7 }: { pkgName: string; range?: number }) {
const { data, error, loading, retry } = useRegistryQuery(
() => getDownloads(pkgName, range),
[pkgName, range],
Expand Down
15 changes: 7 additions & 8 deletions apps/web/app/components/cnpm/FilesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,12 @@ function FileNode({
}

function FileViewer({ pkgName, spec, path }: { pkgName: string; spec: string; path: string }) {
const { data: content, error, loading, retry } = useRegistryQuery(
() => getFileContent(pkgName, spec, path),
[pkgName, spec, path],
);
const {
data: content,
error,
loading,
retry,
} = useRegistryQuery(() => getFileContent(pkgName, spec, path), [pkgName, spec, path]);

if (loading) {
return <Skeleton className="h-96 w-full" />;
Expand Down Expand Up @@ -271,10 +273,7 @@ function highlighted(code: string) {
}

function escapeHtml(value: string) {
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function basename(path: string) {
Expand Down
5 changes: 1 addition & 4 deletions apps/web/app/components/cnpm/NpmSearchForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ export function NpmSearchForm({
<InputGroupButton
type="submit"
variant="default"
className={cn(
"font-medium",
large ? "h-9 gap-1.5 rounded-lg px-4" : "h-6",
)}
className={cn("font-medium", large ? "h-9 gap-1.5 rounded-lg px-4" : "h-6")}
>
{large && <SearchIcon />}
搜索
Expand Down
18 changes: 9 additions & 9 deletions apps/web/app/components/cnpm/PkgHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import {
} from "~/components/ui/select";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { sortVersions, getVersionTags, repoUrl, safeExternalUrl, type PkgTab } from "~/lib/registry/parse";
import {
sortVersions,
getVersionTags,
repoUrl,
safeExternalUrl,
type PkgTab,
} from "~/lib/registry/parse";
import type { RegistryManifest } from "~/lib/registry/types";
import { PkgTabs } from "./PkgTabs";

Expand Down Expand Up @@ -67,9 +73,7 @@ export function PkgHeader({
<p className="max-w-2xl text-sm text-muted-foreground">{manifest.description}</p>
)}
<div className="flex flex-wrap items-center gap-2">
{manifest.license && (
<Badge variant="outline">{manifest.license}</Badge>
)}
{manifest.license && <Badge variant="outline">{manifest.license}</Badge>}
{repo && (
<a
href={repo}
Expand Down Expand Up @@ -112,11 +116,7 @@ export function PkgHeader({
</SelectContent>
</Select>
<Button type="button" variant="outline" size="sm" className="shrink-0" onClick={copy}>
{copied ? (
<Check data-icon="inline-start" />
) : (
<Copy data-icon="inline-start" />
)}
{copied ? <Check data-icon="inline-start" /> : <Copy data-icon="inline-start" />}
{copied ? "已复制" : installCommand}
</Button>
</div>
Expand Down
21 changes: 17 additions & 4 deletions apps/web/app/components/cnpm/PkgSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,21 @@ export function PkgSidebar({ manifest, version }: { manifest: RegistryManifest;
const dist = manifest.versions?.[version]?.dist;
const links: Array<{ label: string; href?: string; icon: React.ReactNode } | null> = [
{ label: "仓库", href: repo, icon: <GitFork className="size-4 shrink-0" /> },
{ label: "主页", href: safeExternalUrl(manifest.homepage), icon: <Globe className="size-4 shrink-0" /> },
{ label: "npmjs.com", href: `https://www.npmjs.com/package/${manifest.name}`, icon: <PackageIcon className="size-4 shrink-0" /> },
{ label: "unpkg", href: `https://unpkg.com/${manifest.name}@${version}`, icon: <ExternalLink className="size-4 shrink-0" /> },
{
label: "主页",
href: safeExternalUrl(manifest.homepage),
icon: <Globe className="size-4 shrink-0" />,
},
{
label: "npmjs.com",
href: `https://www.npmjs.com/package/${manifest.name}`,
icon: <PackageIcon className="size-4 shrink-0" />,
},
{
label: "unpkg",
href: `https://unpkg.com/${manifest.name}@${version}`,
icon: <ExternalLink className="size-4 shrink-0" />,
},
dist?.tarball
? {
label: `tarball${dist.size !== undefined ? ` · ${formatBytes(dist.size)}` : ""}`,
Expand All @@ -22,7 +34,8 @@ export function PkgSidebar({ manifest, version }: { manifest: RegistryManifest;
: null,
];
const visibleLinks = links.filter(
(link): link is { label: string; href: string; icon: React.ReactNode } => Boolean(link && link.href),
(link): link is { label: string; href: string; icon: React.ReactNode } =>
Boolean(link && link.href),
);

return (
Expand Down
5 changes: 1 addition & 4 deletions apps/web/app/components/cnpm/PkgTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ export function PkgTabs({

return (
<Tabs value={active} onValueChange={handleChange}>
<TabsList
className="w-full max-w-full justify-start overflow-x-auto"
aria-label="包信息分类"
>
<TabsList className="w-full max-w-full justify-start overflow-x-auto" aria-label="包信息分类">
{TABS.map((tab) => (
<TabsTrigger key={tab.key} value={tab.key} className="flex-none">
{tab.label}
Expand Down
Loading
Loading