Skip to content

Commit 7d31ffd

Browse files
Enes Yağızclaude
andcommitted
feat(seo): add an about page and keep generated posts out of search
Generated posts are unreviewed, so they are now noindex. Also corrects llms.txt and the homepage registry count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 421be52 commit 7d31ffd

12 files changed

Lines changed: 147 additions & 103 deletions

File tree

‎src/frontend/public/llms.txt‎

Lines changed: 25 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,39 @@
11
# pkgdiff
22

3-
> Compare and analyze package versions across NuGet, npm, PyPI and Pub registries. Track breaking changes, view detailed release notes, and understand version differences with our free online diff tool.
3+
> Shows every release note between two versions of a package across NuGet, npm, PyPI, Maven, pub.dev and Packagist in one categorized timeline, with a security picture of the upgrade.
44

5-
## Overview
5+
pkgdiff helps developers decide whether a dependency upgrade is safe. It combines release notes from the registries and from GitHub, and adds vulnerability and project health data. It is free to use and needs no account.
66

7-
pkgdiff is a web-based tool that helps developers compare package versions across different registries (NuGet, npm, PyPI and Pub). It aggregates release notes from multiple sources including registry pages and GitHub Releases, providing a consolidated view of changes between any two versions. Perfect for developers upgrading dependencies and managing package updates.
7+
## Main pages
88

9-
## Core Features
9+
- [Home](https://pkgdiff.dev/): Search for a package in any supported registry
10+
- [Browse packages](https://pkgdiff.dev/packages): All packages pkgdiff has analyzed
11+
- [Dependency audit](https://pkgdiff.dev/audit): Check a manifest's dependencies for known problems
12+
- [About](https://pkgdiff.dev/about): Who runs pkgdiff, its data sources and their limits
1013

11-
- **Multi-Registry Support**: Search packages across NuGet, npm, and other registries
12-
- **Release Notes Aggregation**: Combines release notes from registry pages and GitHub Releases
13-
- **Version Diff**: Compare any two versions to see consolidated changelogs
14-
- **Real-Time Updates**: Background jobs continuously fetch and update package data
15-
- **Categorized Changes**: Release notes are automatically categorized by type (features, fixes, breaking changes, etc.)
16-
- **Export Functionality**: Export release notes in multiple formats (Markdown, JSON, Text)
14+
## Packages by registry
1715

18-
## Main Pages
16+
- [NuGet packages](https://pkgdiff.dev/packages?registry=nuget)
17+
- [npm packages](https://pkgdiff.dev/packages?registry=npm)
18+
- [PyPI packages](https://pkgdiff.dev/packages?registry=pypi)
19+
- [Maven packages](https://pkgdiff.dev/packages?registry=maven)
20+
- [pub.dev packages](https://pkgdiff.dev/packages?registry=pub.dev)
21+
- [Packagist packages](https://pkgdiff.dev/packages?registry=packagist)
1922

20-
### Home (/)
21-
The landing page features a search interface where users can:
22-
- Select a package registry from the dropdown (NuGet, npm, etc.)
23-
- Search for any package by name
24-
- View live updates of recently analyzed packages
25-
- Access example searches with popular packages
23+
## Package pages
2624

27-
### Package Page (/package/:registry/:packageId)
28-
Displays detailed information about a specific package:
29-
- Package metadata (description, authors, license, repository)
30-
- Complete version history with release dates
31-
- Tabbed interface for viewing releases and comparing versions
32-
- Direct links to package registry and source repository
33-
- Categorized release notes for each version
25+
Package pages follow the pattern `https://pkgdiff.dev/package/{registry}/{packageId}`, for example [react on npm](https://pkgdiff.dev/package/npm/react). Each one lists the package's versions with categorized release notes, and can compare two versions.
3426

35-
### Browse Packages (/packages)
36-
List view of all packages in the system with:
37-
- Filtering by registry
38-
- Search functionality
39-
- Sorting options
40-
- Pagination
27+
## Data sources
4128

42-
## API Endpoints
29+
- Registries: NuGet, npm, PyPI, Maven Central, pub.dev and Packagist
30+
- Release notes: registry pages, GitHub releases and changelogs
31+
- Security: OSV.dev, deps.dev, OpenSSF Scorecard and Socket
4332

44-
The service exposes a REST API:
33+
Changes are categorized by rules that match release note headings and keywords, so a category can be wrong. The release digests under `/blog` are AI-generated and not reviewed by a person; do not treat them as a primary source.
4534

46-
- `GET /api/package/{registry}/{packageId}` - Get package metadata and versions
47-
- `POST /api/package/analyze` - Trigger package analysis
48-
- `GET /api/package/registries` - List supported registries
49-
- `GET /api/package/recent` - Get recent package searches
50-
- `GET /health` - Service health check
35+
## Optional
5136

52-
## Data Sources
53-
54-
pkgdiff retrieves data from:
55-
1. **NuGet.org**: For .NET packages
56-
2. **npmjs.com**: For Node.js packages
57-
3. **GitHub Releases**: Fallback for release notes when registry data is incomplete
58-
4. **Package repositories**: Direct links to source code and issue trackers
59-
60-
## Technical Stack
61-
62-
- **Frontend**: Angular with TypeScript, Tailwind CSS
63-
- **Backend**: ASP.NET Core Web API
64-
- **Database**: PostgreSQL with Entity Framework Core
65-
- **Real-time Communication**: SignalR for live analysis updates
66-
- **Background Jobs**: Hangfire for scheduled scraping tasks
67-
- **Caching**: 30-day cache for package data
68-
69-
## Search Capabilities
70-
71-
Users can search for:
72-
- Package names (e.g., "Newtonsoft.Json", "react")
73-
- View all versions with release dates
74-
- Compare any two versions
75-
- Filter by registry type
76-
- Access recent and popular searches
77-
78-
## Content Organization
79-
80-
- Release notes are scraped from registry pages and GitHub
81-
- Changes are categorized automatically (New Features, Bug Fixes, Breaking Changes, etc.)
82-
- Version comparisons show consolidated changelogs
83-
- All changes are timestamped and linked to sources
84-
85-
## Privacy & Terms
86-
87-
- Minimal data collection (search history, IP for rate limiting)
88-
- No third-party tracking or advertising
89-
- Open-source project
90-
- Free to use
91-
- See /privacy and /terms for detailed policies
92-
93-
## Use Cases
94-
95-
pkgdiff is designed for developers who need to:
96-
- Understand what changed between package versions
97-
- Review breaking changes before upgrading dependencies
98-
- Analyze release notes from multiple sources in one place
99-
- Export changelogs for documentation
100-
- Track package evolution over time
101-
102-
## Links
103-
104-
- Home: https://pkgdiff.com
105-
- GitHub: https://github.com/pkgdiff-dev
106-
- Privacy Policy: https://pkgdiff.com/privacy
107-
- Terms of Service: https://pkgdiff.com/terms
37+
- [Source code](https://github.com/pkgdiff-dev/pkgdiff)
38+
- [Privacy policy](https://pkgdiff.dev/privacy)
39+
- [Terms of service](https://pkgdiff.dev/terms)

‎src/frontend/src/app/app.routes.server.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const serverRoutes: ServerRoute[] = [
77
{ path: 'package/:registry/:packageId', renderMode: RenderMode.Server },
88
{ path: 'package/:registry/:packageId/:version', renderMode: RenderMode.Server },
99
{ path: 'package/:registry/:scope/:name/:version', renderMode: RenderMode.Server },
10+
{ path: 'about', renderMode: RenderMode.Server },
1011
{ path: 'terms', renderMode: RenderMode.Server },
1112
{ path: 'privacy', renderMode: RenderMode.Server },
1213
{ path: 'cookies', renderMode: RenderMode.Server },

‎src/frontend/src/app/app.routes.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export const routes: Routes = [
3030
path: 'package/:registry/:scope/:name/:version',
3131
loadComponent: () => import('./features/package/package').then(m => m.PackageComponent)
3232
},
33+
{
34+
path: 'about',
35+
loadComponent: () => import('./features/about/about').then(m => m.AboutComponent)
36+
},
3337
{
3438
path: 'terms',
3539
loadComponent: () => import('./features/terms/terms').then(m => m.TermsComponent)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<div class="flex flex-col items-center min-h-full min-h-[calc(100vh-64px)]">
2+
<div class="w-full max-w-[720px] px-6 py-12">
3+
<div class="mb-10">
4+
<h1 class="text-3xl font-bold tracking-tight text-[#111418] dark:text-white mb-2">About {{ appName }}</h1>
5+
<p class="text-sm text-gray-500 dark:text-gray-500">
6+
Every release note between two versions of a package, with a security picture of the upgrade.
7+
</p>
8+
</div>
9+
10+
<div class="space-y-8 text-sm leading-relaxed text-gray-700 dark:text-gray-300">
11+
12+
<section>
13+
<h2 class="text-lg font-semibold text-[#111418] dark:text-white mb-3">Who runs it</h2>
14+
<p>
15+
{{ appName }} is built and operated by {{ operatorName }}, an individual developer. It is free to use and
16+
needs no account. The code is on
17+
<a href="https://github.com/pkgdiff-dev/pkgdiff" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">GitHub</a>,
18+
where you can report a problem. You can also write to
19+
<a href="mailto:{{ contactEmail }}" class="text-primary hover:underline">{{ contactEmail }}</a> or find
20+
{{ operatorName }} on
21+
<a href="https://github.com/fleizean" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">GitHub</a>.
22+
</p>
23+
</section>
24+
25+
<section>
26+
<h2 class="text-lg font-semibold text-[#111418] dark:text-white mb-3">Where the data comes from</h2>
27+
<p class="mb-3">
28+
Versions and package details come from the registries: NuGet, npm, PyPI, Maven Central, pub.dev and Packagist.
29+
Release notes come from those registries and from each project's GitHub releases and changelogs. Every note
30+
links back to its source, and it still belongs to its authors.
31+
</p>
32+
<p>The security information comes from these services:</p>
33+
<ul class="list-disc pl-5 mt-2 space-y-1">
34+
<li><a href="https://osv.dev/" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">OSV.dev</a> for known vulnerabilities</li>
35+
<li><a href="https://deps.dev/" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">deps.dev</a> for package and project information</li>
36+
<li><a href="https://scorecard.dev/" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">OpenSSF Scorecard</a> for project security practices</li>
37+
<li><a href="https://socket.dev/" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">Socket</a> for suspicious package behaviour</li>
38+
</ul>
39+
</section>
40+
41+
<section>
42+
<h2 class="text-lg font-semibold text-[#111418] dark:text-white mb-3">How changes are categorised</h2>
43+
<p>
44+
Changes are sorted into groups such as breaking changes, new features and bug fixes by matching the section
45+
headings and keywords in the release notes. These are rules, not a reading of the code, so a change can land
46+
in the wrong group, and a breaking change its authors did not mention will not appear.
47+
</p>
48+
</section>
49+
50+
<section>
51+
<h2 class="text-lg font-semibold text-[#111418] dark:text-white mb-3">How fresh the data is</h2>
52+
<p>
53+
Package data is stored and refreshed by background jobs, so a very recent release or advisory can take time
54+
to appear. Security checks run in the background too, and a page shows what was last retrieved.
55+
</p>
56+
</section>
57+
58+
<section>
59+
<h2 class="text-lg font-semibold text-[#111418] dark:text-white mb-3">What it cannot tell you</h2>
60+
<p>
61+
No listed vulnerability does not prove a package is safe. It means the sources above report nothing for it
62+
right now. Use {{ appName }} to prepare an upgrade, and still test the upgrade yourself. The
63+
<a routerLink="/blog" class="text-primary hover:underline">release digests</a> are written by AI and not
64+
reviewed by a person, so they are labelled as such and kept out of search results.
65+
</p>
66+
</section>
67+
68+
</div>
69+
</div>
70+
</div>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
2+
import { RouterLink } from '@angular/router';
3+
import { APP_CONFIG } from '../../core/config/app.config';
4+
import { SeoService } from '../../core/services/seo.service';
5+
6+
@Component({
7+
changeDetection: ChangeDetectionStrategy.OnPush,
8+
selector: 'app-about',
9+
standalone: true,
10+
imports: [RouterLink],
11+
templateUrl: './about.html',
12+
})
13+
export class AboutComponent {
14+
appName = APP_CONFIG.name;
15+
contactEmail = APP_CONFIG.contact.email;
16+
operatorName = 'Enes Yağız';
17+
18+
constructor() {
19+
inject(SeoService).updateMeta({
20+
title: 'About pkgdiff: Who Runs It and Where the Data Comes From',
21+
description: 'Who operates pkgdiff, which sources its release notes and security data come from, how changes are categorised, and what the data cannot tell you.',
22+
url: '/about',
23+
});
24+
}
25+
}

‎src/frontend/src/app/features/blog/blog-detail/blog-detail.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div class="flex-1 w-full max-w-[900px] mx-auto px-6 py-12 relative">
22

33
<nav aria-label="Breadcrumb" class="flex items-center gap-2 text-xs font-medium text-slate-400 mb-8">
4-
<a routerLink="/blog" class="hover:text-primary transition-colors">Blog</a>
4+
<a routerLink="/blog" class="hover:text-primary transition-colors">Release digests</a>
55
<lucide-icon [img]="ChevronRightIcon" [size]="14" aria-hidden="true"></lucide-icon>
66
<span class="text-slate-600 dark:text-slate-300" aria-current="page">Article</span>
77
</nav>
@@ -61,7 +61,7 @@ <h1 class="text-2xl font-bold text-slate-900 dark:text-white mb-2">Article Not F
6161
published yet.</p>
6262
<a routerLink="/blog"
6363
class="inline-flex items-center justify-center px-6 py-3 border border-slate-200 dark:border-slate-800 text-sm font-bold rounded text-slate-700 dark:text-slate-300 bg-white dark:bg-background-dark hover:bg-slate-50 dark:hover:bg-slate-900 transition-colors uppercase tracking-widest font-mono">
64-
Return to Blog List
64+
Return to Release Digests
6565
</a>
6666
</div>
6767
}

‎src/frontend/src/app/features/blog/blog-detail/blog-detail.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ export class BlogDetailComponent {
7070
const failure = loadFailureOf(loaded.failure);
7171
if (failure === 'not-found') {
7272
this.httpStatus.notFound();
73-
this.seo.setNotFound('Article Not Found — pkgdiff Blog', 'This blog post does not exist or has not been published yet.');
73+
this.seo.setNotFound('Article Not Found — pkgdiff', 'This release digest does not exist or has not been published yet.');
74+
} else {
75+
this.httpStatus.serviceUnavailable();
76+
this.seo.setUnavailable();
7477
}
7578
this.state.set(failure);
7679
return;
@@ -82,12 +85,14 @@ export class BlogDetailComponent {
8285

8386
const excerpt = excerptOf(post.content);
8487
this.seo.updateMeta({
85-
title: `${post.title} — pkgdiff Blog`,
88+
title: `${post.title} — pkgdiff`,
8689
description: excerpt,
8790
url: `/blog/${post.slug}`,
8891
type: 'article',
8992
image: '/og-image/blog.png'
9093
});
94+
// Posts are generated and unreviewed, so they stay out of search results
95+
this.seo.setNoIndex();
9196

9297
this.seo.setStructuredData({
9398
'@context': 'https://schema.org',
@@ -104,7 +109,7 @@ export class BlogDetailComponent {
104109

105110
this.seo.setBreadcrumb([
106111
{ name: 'Home', url: '/' },
107-
{ name: 'Blog', url: '/blog' },
112+
{ name: 'Release digests', url: '/blog' },
108113
{ name: post.title, url: `/blog/${post.slug}` },
109114
]);
110115
}

‎src/frontend/src/app/features/home/home.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ <h3 class="text-sm font-semibold text-[#111418] dark:text-white">Track Breaking
155155
</div>
156156
<div class="flex flex-col items-center gap-2">
157157
<lucide-icon [img]="SparklesIcon" [size]="22" class="text-primary mb-1"></lucide-icon>
158-
<h3 class="text-sm font-semibold text-[#111418] dark:text-white">4 Registries Supported</h3>
158+
<h3 class="text-sm font-semibold text-[#111418] dark:text-white">6 Registries Supported</h3>
159159
<p class="text-xs text-[#617589] dark:text-gray-400 leading-relaxed">
160160
Works with NuGet (.NET), npm (JavaScript), PyPI (Python), Maven (Java), Pub (Dart / Flutter), and Packagist (PHP).
161161
</p>

‎src/frontend/src/app/shared/components/footer/footer.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ export class FooterComponent {
2020
readonly exploreLinks = [
2121
{ path: '/packages', label: 'Packages' },
2222
{ path: '/audit', label: 'Dependency audit' },
23-
{ path: '/blog', label: 'Blog' },
23+
{ path: '/blog', label: 'Release digests' },
2424
{ path: '/contributors', label: 'Contributors' },
25+
{ path: '/about', label: 'About' },
2526
];
2627

2728
appName = APP_CONFIG.name;

‎src/frontend/src/app/shared/components/header/header.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ export class HeaderComponent {
3838
];
3939

4040
readonly moreLinks = [
41-
{ path: '/blog', label: 'Blog' },
41+
{ path: '/blog', label: 'Release digests' },
4242
{ path: '/contributors', label: 'Contributors' },
43+
{ path: '/about', label: 'About' },
4344
];
4445

4546
menuOpen = signal(false);

0 commit comments

Comments
 (0)