Latest commit

History

1,432 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

PatchNotes

A GitHub release viewer for npm packages. Track release notes across your favorite packages in one place.

Forged in Gas Town

Deployment

EnvironmentURLStatus
Frontendhttps://myreleasenotes.aiLive
APIhttps://api.myreleasenotes.aiLive
Sync Functionfn-patchnotes-sync (Azure Functions)Timer (every 6h)
Email Functionspatchnotes-email (Azure Functions)HTTP + Timer triggers

Project Status

Stage: Production (MVP)

AreaStatus
Architecture✅ .NET API + React SPA + Azure Functions
Code Quality✅ Good
CI/CD✅ GitHub Actions (build, test, deploy API + Function + frontend)
Testing✅ 368 xUnit tests + 118 Vitest tests
Authentication✅ Stytch B2C
Sync✅ Concurrent pipeline (Channel-based producer-consumer)

Features

  • Package Tracking - Add npm packages to monitor their GitHub releases
  • Release Timeline - Mobile-first timeline view grouped by date
  • Feed - Combined feed with server-side grouping, filtering stable/pre-release
  • Package Picker - Filter releases by selected packages
  • Sync Engine - Fetch releases from GitHub with rate limit awareness
  • AI Summaries - Generate concise release note summaries using Ollama Cloud (gemma4:31b)
  • Watchlist - Per-user package watchlists with default packages on signup
  • Subscriptions - Stripe-powered Pro subscriptions
  • Email Notifications - Welcome, release, and weekly digest emails via Resend
  • Design System - Consistent visual language across components

Architecture

  • PatchNotes.Data - EF Core models, SQLite/SQL Server, database seeding, version parsing
  • PatchNotes.Api - ASP.NET Core Web API (port 5031), Stytch authentication
  • PatchNotes.Sync - CLI tool + SyncPipeline for concurrent sync & summary generation, GitHub client, AI client. TODO: Refactor into a class library (PatchNotes.Sync.Core) and a thin CLI entry point — the API currently references this Exe project for the GitHub client, which causes Azure App Service startup issues when multiple runtimeconfig.json files are published.
  • PatchNotes.Functions - Azure Functions timer trigger that runs the SyncPipeline every 6 hours
  • patchnotes-web - React frontend with TanStack Router & Query, Orval-generated API client
  • patchnotes-email - Azure Functions (TypeScript) for email delivery via Resend

Prerequisites

  • .NET 10 SDK
  • Node.js 22+
  • pnpm 10+
  • direnv (recommended for secrets management)

Configuration

The project uses direnv for environment-based configuration. Create a secrets file at ~/.secrets/patchnotes/.env.local:

ConnectionStrings__PatchNotes=Server=localhost;Database=PatchNotes;User Id=sa;Password=...;TrustServerCertificate=TrueGitHub__Token=ghp_...Stytch__ProjectId=...Stytch__Secret=...Stytch__WebhookSecret=...Stripe__SecretKey=sk_test_...Stripe__WebhookSecret=whsec_...AI__ApiKey=...AI__BaseUrl=https://ollama.com/v1/AI__Model=gemma4:31bRESEND_API_KEY=re_...DATABASE_URL=Server=localhost;Database=PatchNotes;User id=sa;Password=...;TrustServerCertificate=true

Also copy the email function local settings:

cp patchnotes-email/local.settings.json.example patchnotes-email/local.settings.json

Azure Deployment Configuration

API -- App Service (api-myreleasenotes-ai)

SettingDescription
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT for repository search and release fetching
Stytch__ProjectIdStytch project ID
Stytch__SecretStytch secret key
Stytch__WebhookSecretStytch webhook signing secret
Stripe__SecretKeyStripe secret key
Stripe__WebhookSecretStripe webhook signing secret
SyncFunction__UrlURL of the sync function (e.g. https://fn-patchnotes-sync.azurewebsites.net/api/sync-new-packages)
SyncFunction__KeyFunction-level auth key for the sync function
EmailFunction__UrlURL of the test email function (e.g. https://fn-patchnotes-email.azurewebsites.net/api/sendTestEmail)
EmailFunction__KeyFunction-level auth key for the email function
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Sync Function -- Azure Functions (fn-patchnotes-sync)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEdotnet-isolated
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT
AI__ApiKeyAI provider API key
AI__BaseUrlAI provider endpoint
AI__ModelModel name (e.g. llama-3.3-70b-versatile)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Email Function -- Azure Functions (fn-patchnotes-email)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEnode
RESEND_API_KEYResend API key for email delivery
DATABASE_URLSQL Server connection string (ADO.NET format)
APP_BASE_URLBase URL for links in emails (default: https://app.myreleasenotes.ai)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Frontend -- Static Web Apps

Build-time variables set in CI:

SettingDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai) -- GitHub Actions variable
VITE_STYTCH_PUBLIC_TOKENStytch public token -- GitHub Actions secret

GitHub Actions

Secrets:

SecretDescription
AZURE_CLIENT_IDService principal client ID (OIDC login)
AZURE_TENANT_IDAzure AD tenant ID
AZURE_SUBSCRIPTION_IDAzure subscription ID
DATABASE_CONNECTION_STRINGSQL Server connection string (for migrations)
AZURE_STATIC_WEB_APPS_API_TOKENSWA deployment token
VITE_STYTCH_PUBLIC_TOKENStytch public token (frontend build)

Variables:

VariableDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai)

Quick Start

1. Build the backend

dotnet build

2. Apply database migrations (SQLite for local dev)

dotnet ef database update --context SqliteContext --project PatchNotes.Data --startup-project PatchNotes.Api

3. Seed the database

cd PatchNotes.Sync
dotnet run -- --seed

4. Run the API

cd PatchNotes.Api
dotnet run

API available at: http://localhost:5031

5. Run the frontend

cd patchnotes-web
pnpm install
pnpm dev

Frontend available at: http://localhost:5173

Testing

Test the API endpoints

# List packages
curl http://localhost:5031/api/packages
# Get releases (last 7 days)
curl http://localhost:5031/api/releases
# Get releases for specific packages
curl "http://localhost:5031/api/releases?packages=react,vue&days=30"# Get combined feed
curl http://localhost:5031/api/feed
# Get packages by owner
curl http://localhost:5031/api/packages/facebook
# Get package by owner/repo
curl http://localhost:5031/api/packages/facebook/react
# Add a package
curl -X POST http://localhost:5031/api/packages \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"npmName": "lodash"}'

Sync CLI

# Run full sync pipeline (concurrent sync + summary generation)
dotnet run --project PatchNotes.Sync
# Sync a single repo
dotnet run --project PatchNotes.Sync -- -r https://github.com/prettier/prettier
# Generate summaries for a specific package
dotnet run --project PatchNotes.Sync -- -s prettier/prettier
# Seed package catalog from packages.json and sync all from GitHub (first-time setup)
dotnet run --project PatchNotes.Sync -- --init
# Seed database with sample data (local dev)
dotnet run --project PatchNotes.Sync -- --seed

Exit codes: 0=success, 1=partial failure, 2=fatal error

The default sync uses a producer-consumer pipeline (SyncPipeline) — as soon as a package finishes syncing, its summaries start generating while the next package syncs.

Project Structure

PatchNotes/
├── PatchNotes.Api/ # Web API + Stytch auth
│ ├── Routes/ # Minimal API route handlers
│ ├── Stytch/ # Stytch authentication client
│ └── Webhooks/ # Stytch + Stripe webhook handlers
├── PatchNotes.Data/ # Data layer
│ ├── Migrations/ # EF Core migrations (Sqlite + SqlServer)
│ └── SeedData/ # Package catalog (packages.json)
├── PatchNotes.Sync/ # Sync CLI + SyncPipeline
│ ├── GitHub/ # GitHub API client
│ └── AI/ # AI client (OpenAI-compatible)
├── PatchNotes.Functions/ # Azure Functions (timer-triggered sync)
├── PatchNotes.Tests/ # xUnit tests
├── patchnotes-web/ # React frontend
│ └── src/
│ ├── api/ # Orval-generated + custom hooks
│ ├── components/ # UI components
│ ├── pages/ # Route pages
│ └── routes/ # TanStack Router config
└── patchnotes-email/ # Azure Functions (email via Resend)
└── src/
├── functions/ # sendWelcome, sendDigest
└── lib/ # Resend client

Tech Stack

Backend:

  • .NET 10 / ASP.NET Core
  • Entity Framework Core (SQLite dev / SQL Server prod)
  • Azure Functions (isolated worker, timer trigger)
  • GitHub API integration
  • AI summaries via Ollama Cloud (gemma4:31b, OpenAI-compatible API)
  • Stytch B2C authentication
  • Stripe subscriptions

Frontend:

  • React 19 + TypeScript
  • TanStack Router (type-safe file-based routing)
  • TanStack Query (data fetching)
  • Tailwind CSS 4
  • Vite 7
  • Orval (OpenAPI client generation)

Email:

  • Azure Functions (TypeScript)
  • Resend (email delivery)

Workspace:

  • pnpm 10 workspace monorepo (patchnotes-web, patchnotes-email)

Development

Database Migrations

The project uses separate EF Core migrations for SQLite (development) and SQL Server (production).

Creating a new migration (when you change entity models):

# Set SQL Server connection string (required)export ConnectionStrings__PatchNotes="Server=...;Database=...;User Id=...;Password=..."# Generate migrations for both providers
./scripts/add-migration.sh MigrationName

This creates migrations in:

  • PatchNotes.Data/Migrations/Sqlite/ - Local development
  • PatchNotes.Data/Migrations/SqlServer/ - Production

Important: CI will fail if you change models without creating migrations. The has-pending-model-changes check ensures migrations are always committed with model changes.

For more details, see PatchNotes.Data/README.md.

Running Tests

Backend:

dotnet test PatchNotes.slnx

Frontend:

cd patchnotes-web
pnpm test

Code Quality

The project uses GitHub Actions for CI. All PRs must pass:

  • dotnet build and dotnet test
  • pnpm lint and pnpm format:check
  • pnpm build (includes TypeScript type checking)

Contributing

Code Style

Backend (.NET):

  • Follow standard C# conventions
  • Use async/await for I/O operations
  • Keep controllers thin, business logic in services

Frontend (TypeScript):

  • Use TypeScript strict mode
  • Prefer TanStack Query for data fetching
  • Follow the existing component structure

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes with clear commit messages
  3. Ensure all CI checks pass
  4. Request review

License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

1,432 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

PatchNotes

A GitHub release viewer for npm packages. Track release notes across your favorite packages in one place.

Forged in Gas Town

Deployment

EnvironmentURLStatus
Frontendhttps://myreleasenotes.aiLive
APIhttps://api.myreleasenotes.aiLive
Sync Functionfn-patchnotes-sync (Azure Functions)Timer (every 6h)
Email Functionspatchnotes-email (Azure Functions)HTTP + Timer triggers

Project Status

Stage: Production (MVP)

AreaStatus
Architecture✅ .NET API + React SPA + Azure Functions
Code Quality✅ Good
CI/CD✅ GitHub Actions (build, test, deploy API + Function + frontend)
Testing✅ 368 xUnit tests + 118 Vitest tests
Authentication✅ Stytch B2C
Sync✅ Concurrent pipeline (Channel-based producer-consumer)

Features

  • Package Tracking - Add npm packages to monitor their GitHub releases
  • Release Timeline - Mobile-first timeline view grouped by date
  • Feed - Combined feed with server-side grouping, filtering stable/pre-release
  • Package Picker - Filter releases by selected packages
  • Sync Engine - Fetch releases from GitHub with rate limit awareness
  • AI Summaries - Generate concise release note summaries using Ollama Cloud (gemma4:31b)
  • Watchlist - Per-user package watchlists with default packages on signup
  • Subscriptions - Stripe-powered Pro subscriptions
  • Email Notifications - Welcome, release, and weekly digest emails via Resend
  • Design System - Consistent visual language across components

Architecture

  • PatchNotes.Data - EF Core models, SQLite/SQL Server, database seeding, version parsing
  • PatchNotes.Api - ASP.NET Core Web API (port 5031), Stytch authentication
  • PatchNotes.Sync - CLI tool + SyncPipeline for concurrent sync & summary generation, GitHub client, AI client. TODO: Refactor into a class library (PatchNotes.Sync.Core) and a thin CLI entry point — the API currently references this Exe project for the GitHub client, which causes Azure App Service startup issues when multiple runtimeconfig.json files are published.
  • PatchNotes.Functions - Azure Functions timer trigger that runs the SyncPipeline every 6 hours
  • patchnotes-web - React frontend with TanStack Router & Query, Orval-generated API client
  • patchnotes-email - Azure Functions (TypeScript) for email delivery via Resend

Prerequisites

  • .NET 10 SDK
  • Node.js 22+
  • pnpm 10+
  • direnv (recommended for secrets management)

Configuration

The project uses direnv for environment-based configuration. Create a secrets file at ~/.secrets/patchnotes/.env.local:

ConnectionStrings__PatchNotes=Server=localhost;Database=PatchNotes;User Id=sa;Password=...;TrustServerCertificate=TrueGitHub__Token=ghp_...Stytch__ProjectId=...Stytch__Secret=...Stytch__WebhookSecret=...Stripe__SecretKey=sk_test_...Stripe__WebhookSecret=whsec_...AI__ApiKey=...AI__BaseUrl=https://ollama.com/v1/AI__Model=gemma4:31bRESEND_API_KEY=re_...DATABASE_URL=Server=localhost;Database=PatchNotes;User id=sa;Password=...;TrustServerCertificate=true

Also copy the email function local settings:

cp patchnotes-email/local.settings.json.example patchnotes-email/local.settings.json

Azure Deployment Configuration

API -- App Service (api-myreleasenotes-ai)

SettingDescription
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT for repository search and release fetching
Stytch__ProjectIdStytch project ID
Stytch__SecretStytch secret key
Stytch__WebhookSecretStytch webhook signing secret
Stripe__SecretKeyStripe secret key
Stripe__WebhookSecretStripe webhook signing secret
SyncFunction__UrlURL of the sync function (e.g. https://fn-patchnotes-sync.azurewebsites.net/api/sync-new-packages)
SyncFunction__KeyFunction-level auth key for the sync function
EmailFunction__UrlURL of the test email function (e.g. https://fn-patchnotes-email.azurewebsites.net/api/sendTestEmail)
EmailFunction__KeyFunction-level auth key for the email function
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Sync Function -- Azure Functions (fn-patchnotes-sync)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEdotnet-isolated
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT
AI__ApiKeyAI provider API key
AI__BaseUrlAI provider endpoint
AI__ModelModel name (e.g. llama-3.3-70b-versatile)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Email Function -- Azure Functions (fn-patchnotes-email)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEnode
RESEND_API_KEYResend API key for email delivery
DATABASE_URLSQL Server connection string (ADO.NET format)
APP_BASE_URLBase URL for links in emails (default: https://app.myreleasenotes.ai)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Frontend -- Static Web Apps

Build-time variables set in CI:

SettingDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai) -- GitHub Actions variable
VITE_STYTCH_PUBLIC_TOKENStytch public token -- GitHub Actions secret

GitHub Actions

Secrets:

SecretDescription
AZURE_CLIENT_IDService principal client ID (OIDC login)
AZURE_TENANT_IDAzure AD tenant ID
AZURE_SUBSCRIPTION_IDAzure subscription ID
DATABASE_CONNECTION_STRINGSQL Server connection string (for migrations)
AZURE_STATIC_WEB_APPS_API_TOKENSWA deployment token
VITE_STYTCH_PUBLIC_TOKENStytch public token (frontend build)

Variables:

VariableDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai)

Quick Start

1. Build the backend

dotnet build

2. Apply database migrations (SQLite for local dev)

dotnet ef database update --context SqliteContext --project PatchNotes.Data --startup-project PatchNotes.Api

3. Seed the database

cd PatchNotes.Sync
dotnet run -- --seed

4. Run the API

cd PatchNotes.Api
dotnet run

API available at: http://localhost:5031

5. Run the frontend

cd patchnotes-web
pnpm install
pnpm dev

Frontend available at: http://localhost:5173

Testing

Test the API endpoints

# List packages
curl http://localhost:5031/api/packages
# Get releases (last 7 days)
curl http://localhost:5031/api/releases
# Get releases for specific packages
curl "http://localhost:5031/api/releases?packages=react,vue&days=30"# Get combined feed
curl http://localhost:5031/api/feed
# Get packages by owner
curl http://localhost:5031/api/packages/facebook
# Get package by owner/repo
curl http://localhost:5031/api/packages/facebook/react
# Add a package
curl -X POST http://localhost:5031/api/packages \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"npmName": "lodash"}'

Sync CLI

# Run full sync pipeline (concurrent sync + summary generation)
dotnet run --project PatchNotes.Sync
# Sync a single repo
dotnet run --project PatchNotes.Sync -- -r https://github.com/prettier/prettier
# Generate summaries for a specific package
dotnet run --project PatchNotes.Sync -- -s prettier/prettier
# Seed package catalog from packages.json and sync all from GitHub (first-time setup)
dotnet run --project PatchNotes.Sync -- --init
# Seed database with sample data (local dev)
dotnet run --project PatchNotes.Sync -- --seed

Exit codes: 0=success, 1=partial failure, 2=fatal error

The default sync uses a producer-consumer pipeline (SyncPipeline) — as soon as a package finishes syncing, its summaries start generating while the next package syncs.

Project Structure

PatchNotes/
├── PatchNotes.Api/ # Web API + Stytch auth
│ ├── Routes/ # Minimal API route handlers
│ ├── Stytch/ # Stytch authentication client
│ └── Webhooks/ # Stytch + Stripe webhook handlers
├── PatchNotes.Data/ # Data layer
│ ├── Migrations/ # EF Core migrations (Sqlite + SqlServer)
│ └── SeedData/ # Package catalog (packages.json)
├── PatchNotes.Sync/ # Sync CLI + SyncPipeline
│ ├── GitHub/ # GitHub API client
│ └── AI/ # AI client (OpenAI-compatible)
├── PatchNotes.Functions/ # Azure Functions (timer-triggered sync)
├── PatchNotes.Tests/ # xUnit tests
├── patchnotes-web/ # React frontend
│ └── src/
│ ├── api/ # Orval-generated + custom hooks
│ ├── components/ # UI components
│ ├── pages/ # Route pages
│ └── routes/ # TanStack Router config
└── patchnotes-email/ # Azure Functions (email via Resend)
└── src/
├── functions/ # sendWelcome, sendDigest
└── lib/ # Resend client

Tech Stack

Backend:

  • .NET 10 / ASP.NET Core
  • Entity Framework Core (SQLite dev / SQL Server prod)
  • Azure Functions (isolated worker, timer trigger)
  • GitHub API integration
  • AI summaries via Ollama Cloud (gemma4:31b, OpenAI-compatible API)
  • Stytch B2C authentication
  • Stripe subscriptions

Frontend:

  • React 19 + TypeScript
  • TanStack Router (type-safe file-based routing)
  • TanStack Query (data fetching)
  • Tailwind CSS 4
  • Vite 7
  • Orval (OpenAPI client generation)

Email:

  • Azure Functions (TypeScript)
  • Resend (email delivery)

Workspace:

  • pnpm 10 workspace monorepo (patchnotes-web, patchnotes-email)

Development

Database Migrations

The project uses separate EF Core migrations for SQLite (development) and SQL Server (production).

Creating a new migration (when you change entity models):

# Set SQL Server connection string (required)export ConnectionStrings__PatchNotes="Server=...;Database=...;User Id=...;Password=..."# Generate migrations for both providers
./scripts/add-migration.sh MigrationName

This creates migrations in:

  • PatchNotes.Data/Migrations/Sqlite/ - Local development
  • PatchNotes.Data/Migrations/SqlServer/ - Production

Important: CI will fail if you change models without creating migrations. The has-pending-model-changes check ensures migrations are always committed with model changes.

For more details, see PatchNotes.Data/README.md.

Running Tests

Backend:

dotnet test PatchNotes.slnx

Frontend:

cd patchnotes-web
pnpm test

Code Quality

The project uses GitHub Actions for CI. All PRs must pass:

  • dotnet build and dotnet test
  • pnpm lint and pnpm format:check
  • pnpm build (includes TypeScript type checking)

Contributing

Code Style

Backend (.NET):

  • Follow standard C# conventions
  • Use async/await for I/O operations
  • Keep controllers thin, business logic in services

Frontend (TypeScript):

  • Use TypeScript strict mode
  • Prefer TanStack Query for data fetching
  • Follow the existing component structure

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes with clear commit messages
  3. Ensure all CI checks pass
  4. Request review

License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,432 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

PatchNotes

A GitHub release viewer for npm packages. Track release notes across your favorite packages in one place.

Forged in Gas Town

Deployment

EnvironmentURLStatus
Frontendhttps://myreleasenotes.aiLive
APIhttps://api.myreleasenotes.aiLive
Sync Functionfn-patchnotes-sync (Azure Functions)Timer (every 6h)
Email Functionspatchnotes-email (Azure Functions)HTTP + Timer triggers

Project Status

Stage: Production (MVP)

AreaStatus
Architecture✅ .NET API + React SPA + Azure Functions
Code Quality✅ Good
CI/CD✅ GitHub Actions (build, test, deploy API + Function + frontend)
Testing✅ 368 xUnit tests + 118 Vitest tests
Authentication✅ Stytch B2C
Sync✅ Concurrent pipeline (Channel-based producer-consumer)

Features

  • Package Tracking - Add npm packages to monitor their GitHub releases
  • Release Timeline - Mobile-first timeline view grouped by date
  • Feed - Combined feed with server-side grouping, filtering stable/pre-release
  • Package Picker - Filter releases by selected packages
  • Sync Engine - Fetch releases from GitHub with rate limit awareness
  • AI Summaries - Generate concise release note summaries using Ollama Cloud (gemma4:31b)
  • Watchlist - Per-user package watchlists with default packages on signup
  • Subscriptions - Stripe-powered Pro subscriptions
  • Email Notifications - Welcome, release, and weekly digest emails via Resend
  • Design System - Consistent visual language across components

Architecture

  • PatchNotes.Data - EF Core models, SQLite/SQL Server, database seeding, version parsing
  • PatchNotes.Api - ASP.NET Core Web API (port 5031), Stytch authentication
  • PatchNotes.Sync - CLI tool + SyncPipeline for concurrent sync & summary generation, GitHub client, AI client. TODO: Refactor into a class library (PatchNotes.Sync.Core) and a thin CLI entry point — the API currently references this Exe project for the GitHub client, which causes Azure App Service startup issues when multiple runtimeconfig.json files are published.
  • PatchNotes.Functions - Azure Functions timer trigger that runs the SyncPipeline every 6 hours
  • patchnotes-web - React frontend with TanStack Router & Query, Orval-generated API client
  • patchnotes-email - Azure Functions (TypeScript) for email delivery via Resend

Prerequisites

  • .NET 10 SDK
  • Node.js 22+
  • pnpm 10+
  • direnv (recommended for secrets management)

Configuration

The project uses direnv for environment-based configuration. Create a secrets file at ~/.secrets/patchnotes/.env.local:

ConnectionStrings__PatchNotes=Server=localhost;Database=PatchNotes;User Id=sa;Password=...;TrustServerCertificate=TrueGitHub__Token=ghp_...Stytch__ProjectId=...Stytch__Secret=...Stytch__WebhookSecret=...Stripe__SecretKey=sk_test_...Stripe__WebhookSecret=whsec_...AI__ApiKey=...AI__BaseUrl=https://ollama.com/v1/AI__Model=gemma4:31bRESEND_API_KEY=re_...DATABASE_URL=Server=localhost;Database=PatchNotes;User id=sa;Password=...;TrustServerCertificate=true

Also copy the email function local settings:

cp patchnotes-email/local.settings.json.example patchnotes-email/local.settings.json

Azure Deployment Configuration

API -- App Service (api-myreleasenotes-ai)

SettingDescription
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT for repository search and release fetching
Stytch__ProjectIdStytch project ID
Stytch__SecretStytch secret key
Stytch__WebhookSecretStytch webhook signing secret
Stripe__SecretKeyStripe secret key
Stripe__WebhookSecretStripe webhook signing secret
SyncFunction__UrlURL of the sync function (e.g. https://fn-patchnotes-sync.azurewebsites.net/api/sync-new-packages)
SyncFunction__KeyFunction-level auth key for the sync function
EmailFunction__UrlURL of the test email function (e.g. https://fn-patchnotes-email.azurewebsites.net/api/sendTestEmail)
EmailFunction__KeyFunction-level auth key for the email function
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Sync Function -- Azure Functions (fn-patchnotes-sync)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEdotnet-isolated
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT
AI__ApiKeyAI provider API key
AI__BaseUrlAI provider endpoint
AI__ModelModel name (e.g. llama-3.3-70b-versatile)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Email Function -- Azure Functions (fn-patchnotes-email)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEnode
RESEND_API_KEYResend API key for email delivery
DATABASE_URLSQL Server connection string (ADO.NET format)
APP_BASE_URLBase URL for links in emails (default: https://app.myreleasenotes.ai)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Frontend -- Static Web Apps

Build-time variables set in CI:

SettingDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai) -- GitHub Actions variable
VITE_STYTCH_PUBLIC_TOKENStytch public token -- GitHub Actions secret

GitHub Actions

Secrets:

SecretDescription
AZURE_CLIENT_IDService principal client ID (OIDC login)
AZURE_TENANT_IDAzure AD tenant ID
AZURE_SUBSCRIPTION_IDAzure subscription ID
DATABASE_CONNECTION_STRINGSQL Server connection string (for migrations)
AZURE_STATIC_WEB_APPS_API_TOKENSWA deployment token
VITE_STYTCH_PUBLIC_TOKENStytch public token (frontend build)

Variables:

VariableDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai)

Quick Start

1. Build the backend

dotnet build

2. Apply database migrations (SQLite for local dev)

dotnet ef database update --context SqliteContext --project PatchNotes.Data --startup-project PatchNotes.Api

3. Seed the database

cd PatchNotes.Sync
dotnet run -- --seed

4. Run the API

cd PatchNotes.Api
dotnet run

API available at: http://localhost:5031

5. Run the frontend

cd patchnotes-web
pnpm install
pnpm dev

Frontend available at: http://localhost:5173

Testing

Test the API endpoints

# List packages
curl http://localhost:5031/api/packages
# Get releases (last 7 days)
curl http://localhost:5031/api/releases
# Get releases for specific packages
curl "http://localhost:5031/api/releases?packages=react,vue&days=30"# Get combined feed
curl http://localhost:5031/api/feed
# Get packages by owner
curl http://localhost:5031/api/packages/facebook
# Get package by owner/repo
curl http://localhost:5031/api/packages/facebook/react
# Add a package
curl -X POST http://localhost:5031/api/packages \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"npmName": "lodash"}'

Sync CLI

# Run full sync pipeline (concurrent sync + summary generation)
dotnet run --project PatchNotes.Sync
# Sync a single repo
dotnet run --project PatchNotes.Sync -- -r https://github.com/prettier/prettier
# Generate summaries for a specific package
dotnet run --project PatchNotes.Sync -- -s prettier/prettier
# Seed package catalog from packages.json and sync all from GitHub (first-time setup)
dotnet run --project PatchNotes.Sync -- --init
# Seed database with sample data (local dev)
dotnet run --project PatchNotes.Sync -- --seed

Exit codes: 0=success, 1=partial failure, 2=fatal error

The default sync uses a producer-consumer pipeline (SyncPipeline) — as soon as a package finishes syncing, its summaries start generating while the next package syncs.

Project Structure

PatchNotes/
├── PatchNotes.Api/ # Web API + Stytch auth
│ ├── Routes/ # Minimal API route handlers
│ ├── Stytch/ # Stytch authentication client
│ └── Webhooks/ # Stytch + Stripe webhook handlers
├── PatchNotes.Data/ # Data layer
│ ├── Migrations/ # EF Core migrations (Sqlite + SqlServer)
│ └── SeedData/ # Package catalog (packages.json)
├── PatchNotes.Sync/ # Sync CLI + SyncPipeline
│ ├── GitHub/ # GitHub API client
│ └── AI/ # AI client (OpenAI-compatible)
├── PatchNotes.Functions/ # Azure Functions (timer-triggered sync)
├── PatchNotes.Tests/ # xUnit tests
├── patchnotes-web/ # React frontend
│ └── src/
│ ├── api/ # Orval-generated + custom hooks
│ ├── components/ # UI components
│ ├── pages/ # Route pages
│ └── routes/ # TanStack Router config
└── patchnotes-email/ # Azure Functions (email via Resend)
└── src/
├── functions/ # sendWelcome, sendDigest
└── lib/ # Resend client

Tech Stack

Backend:

  • .NET 10 / ASP.NET Core
  • Entity Framework Core (SQLite dev / SQL Server prod)
  • Azure Functions (isolated worker, timer trigger)
  • GitHub API integration
  • AI summaries via Ollama Cloud (gemma4:31b, OpenAI-compatible API)
  • Stytch B2C authentication
  • Stripe subscriptions

Frontend:

  • React 19 + TypeScript
  • TanStack Router (type-safe file-based routing)
  • TanStack Query (data fetching)
  • Tailwind CSS 4
  • Vite 7
  • Orval (OpenAPI client generation)

Email:

  • Azure Functions (TypeScript)
  • Resend (email delivery)

Workspace:

  • pnpm 10 workspace monorepo (patchnotes-web, patchnotes-email)

Development

Database Migrations

The project uses separate EF Core migrations for SQLite (development) and SQL Server (production).

Creating a new migration (when you change entity models):

# Set SQL Server connection string (required)export ConnectionStrings__PatchNotes="Server=...;Database=...;User Id=...;Password=..."# Generate migrations for both providers
./scripts/add-migration.sh MigrationName

This creates migrations in:

  • PatchNotes.Data/Migrations/Sqlite/ - Local development
  • PatchNotes.Data/Migrations/SqlServer/ - Production

Important: CI will fail if you change models without creating migrations. The has-pending-model-changes check ensures migrations are always committed with model changes.

For more details, see PatchNotes.Data/README.md.

Running Tests

Backend:

dotnet test PatchNotes.slnx

Frontend:

cd patchnotes-web
pnpm test

Code Quality

The project uses GitHub Actions for CI. All PRs must pass:

  • dotnet build and dotnet test
  • pnpm lint and pnpm format:check
  • pnpm build (includes TypeScript type checking)

Contributing

Code Style

Backend (.NET):

  • Follow standard C# conventions
  • Use async/await for I/O operations
  • Keep controllers thin, business logic in services

Frontend (TypeScript):

  • Use TypeScript strict mode
  • Prefer TanStack Query for data fetching
  • Follow the existing component structure

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes with clear commit messages
  3. Ensure all CI checks pass
  4. Request review

License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,432 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

PatchNotes

A GitHub release viewer for npm packages. Track release notes across your favorite packages in one place.

Forged in Gas Town

Deployment

EnvironmentURLStatus
Frontendhttps://myreleasenotes.aiLive
APIhttps://api.myreleasenotes.aiLive
Sync Functionfn-patchnotes-sync (Azure Functions)Timer (every 6h)
Email Functionspatchnotes-email (Azure Functions)HTTP + Timer triggers

Project Status

Stage: Production (MVP)

AreaStatus
Architecture✅ .NET API + React SPA + Azure Functions
Code Quality✅ Good
CI/CD✅ GitHub Actions (build, test, deploy API + Function + frontend)
Testing✅ 368 xUnit tests + 118 Vitest tests
Authentication✅ Stytch B2C
Sync✅ Concurrent pipeline (Channel-based producer-consumer)

Features

  • Package Tracking - Add npm packages to monitor their GitHub releases
  • Release Timeline - Mobile-first timeline view grouped by date
  • Feed - Combined feed with server-side grouping, filtering stable/pre-release
  • Package Picker - Filter releases by selected packages
  • Sync Engine - Fetch releases from GitHub with rate limit awareness
  • AI Summaries - Generate concise release note summaries using Ollama Cloud (gemma4:31b)
  • Watchlist - Per-user package watchlists with default packages on signup
  • Subscriptions - Stripe-powered Pro subscriptions
  • Email Notifications - Welcome, release, and weekly digest emails via Resend
  • Design System - Consistent visual language across components

Architecture

  • PatchNotes.Data - EF Core models, SQLite/SQL Server, database seeding, version parsing
  • PatchNotes.Api - ASP.NET Core Web API (port 5031), Stytch authentication
  • PatchNotes.Sync - CLI tool + SyncPipeline for concurrent sync & summary generation, GitHub client, AI client. TODO: Refactor into a class library (PatchNotes.Sync.Core) and a thin CLI entry point — the API currently references this Exe project for the GitHub client, which causes Azure App Service startup issues when multiple runtimeconfig.json files are published.
  • PatchNotes.Functions - Azure Functions timer trigger that runs the SyncPipeline every 6 hours
  • patchnotes-web - React frontend with TanStack Router & Query, Orval-generated API client
  • patchnotes-email - Azure Functions (TypeScript) for email delivery via Resend

Prerequisites

  • .NET 10 SDK
  • Node.js 22+
  • pnpm 10+
  • direnv (recommended for secrets management)

Configuration

The project uses direnv for environment-based configuration. Create a secrets file at ~/.secrets/patchnotes/.env.local:

ConnectionStrings__PatchNotes=Server=localhost;Database=PatchNotes;User Id=sa;Password=...;TrustServerCertificate=TrueGitHub__Token=ghp_...Stytch__ProjectId=...Stytch__Secret=...Stytch__WebhookSecret=...Stripe__SecretKey=sk_test_...Stripe__WebhookSecret=whsec_...AI__ApiKey=...AI__BaseUrl=https://ollama.com/v1/AI__Model=gemma4:31bRESEND_API_KEY=re_...DATABASE_URL=Server=localhost;Database=PatchNotes;User id=sa;Password=...;TrustServerCertificate=true

Also copy the email function local settings:

cp patchnotes-email/local.settings.json.example patchnotes-email/local.settings.json

Azure Deployment Configuration

API -- App Service (api-myreleasenotes-ai)

SettingDescription
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT for repository search and release fetching
Stytch__ProjectIdStytch project ID
Stytch__SecretStytch secret key
Stytch__WebhookSecretStytch webhook signing secret
Stripe__SecretKeyStripe secret key
Stripe__WebhookSecretStripe webhook signing secret
SyncFunction__UrlURL of the sync function (e.g. https://fn-patchnotes-sync.azurewebsites.net/api/sync-new-packages)
SyncFunction__KeyFunction-level auth key for the sync function
EmailFunction__UrlURL of the test email function (e.g. https://fn-patchnotes-email.azurewebsites.net/api/sendTestEmail)
EmailFunction__KeyFunction-level auth key for the email function
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Sync Function -- Azure Functions (fn-patchnotes-sync)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEdotnet-isolated
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT
AI__ApiKeyAI provider API key
AI__BaseUrlAI provider endpoint
AI__ModelModel name (e.g. llama-3.3-70b-versatile)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Email Function -- Azure Functions (fn-patchnotes-email)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEnode
RESEND_API_KEYResend API key for email delivery
DATABASE_URLSQL Server connection string (ADO.NET format)
APP_BASE_URLBase URL for links in emails (default: https://app.myreleasenotes.ai)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Frontend -- Static Web Apps

Build-time variables set in CI:

SettingDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai) -- GitHub Actions variable
VITE_STYTCH_PUBLIC_TOKENStytch public token -- GitHub Actions secret

GitHub Actions

Secrets:

SecretDescription
AZURE_CLIENT_IDService principal client ID (OIDC login)
AZURE_TENANT_IDAzure AD tenant ID
AZURE_SUBSCRIPTION_IDAzure subscription ID
DATABASE_CONNECTION_STRINGSQL Server connection string (for migrations)
AZURE_STATIC_WEB_APPS_API_TOKENSWA deployment token
VITE_STYTCH_PUBLIC_TOKENStytch public token (frontend build)

Variables:

VariableDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai)

Quick Start

1. Build the backend

dotnet build

2. Apply database migrations (SQLite for local dev)

dotnet ef database update --context SqliteContext --project PatchNotes.Data --startup-project PatchNotes.Api

3. Seed the database

cd PatchNotes.Sync
dotnet run -- --seed

4. Run the API

cd PatchNotes.Api
dotnet run

API available at: http://localhost:5031

5. Run the frontend

cd patchnotes-web
pnpm install
pnpm dev

Frontend available at: http://localhost:5173

Testing

Test the API endpoints

# List packages
curl http://localhost:5031/api/packages
# Get releases (last 7 days)
curl http://localhost:5031/api/releases
# Get releases for specific packages
curl "http://localhost:5031/api/releases?packages=react,vue&days=30"# Get combined feed
curl http://localhost:5031/api/feed
# Get packages by owner
curl http://localhost:5031/api/packages/facebook
# Get package by owner/repo
curl http://localhost:5031/api/packages/facebook/react
# Add a package
curl -X POST http://localhost:5031/api/packages \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"npmName": "lodash"}'

Sync CLI

# Run full sync pipeline (concurrent sync + summary generation)
dotnet run --project PatchNotes.Sync
# Sync a single repo
dotnet run --project PatchNotes.Sync -- -r https://github.com/prettier/prettier
# Generate summaries for a specific package
dotnet run --project PatchNotes.Sync -- -s prettier/prettier
# Seed package catalog from packages.json and sync all from GitHub (first-time setup)
dotnet run --project PatchNotes.Sync -- --init
# Seed database with sample data (local dev)
dotnet run --project PatchNotes.Sync -- --seed

Exit codes: 0=success, 1=partial failure, 2=fatal error

The default sync uses a producer-consumer pipeline (SyncPipeline) — as soon as a package finishes syncing, its summaries start generating while the next package syncs.

Project Structure

PatchNotes/
├── PatchNotes.Api/ # Web API + Stytch auth
│ ├── Routes/ # Minimal API route handlers
│ ├── Stytch/ # Stytch authentication client
│ └── Webhooks/ # Stytch + Stripe webhook handlers
├── PatchNotes.Data/ # Data layer
│ ├── Migrations/ # EF Core migrations (Sqlite + SqlServer)
│ └── SeedData/ # Package catalog (packages.json)
├── PatchNotes.Sync/ # Sync CLI + SyncPipeline
│ ├── GitHub/ # GitHub API client
│ └── AI/ # AI client (OpenAI-compatible)
├── PatchNotes.Functions/ # Azure Functions (timer-triggered sync)
├── PatchNotes.Tests/ # xUnit tests
├── patchnotes-web/ # React frontend
│ └── src/
│ ├── api/ # Orval-generated + custom hooks
│ ├── components/ # UI components
│ ├── pages/ # Route pages
│ └── routes/ # TanStack Router config
└── patchnotes-email/ # Azure Functions (email via Resend)
└── src/
├── functions/ # sendWelcome, sendDigest
└── lib/ # Resend client

Tech Stack

Backend:

  • .NET 10 / ASP.NET Core
  • Entity Framework Core (SQLite dev / SQL Server prod)
  • Azure Functions (isolated worker, timer trigger)
  • GitHub API integration
  • AI summaries via Ollama Cloud (gemma4:31b, OpenAI-compatible API)
  • Stytch B2C authentication
  • Stripe subscriptions

Frontend:

  • React 19 + TypeScript
  • TanStack Router (type-safe file-based routing)
  • TanStack Query (data fetching)
  • Tailwind CSS 4
  • Vite 7
  • Orval (OpenAPI client generation)

Email:

  • Azure Functions (TypeScript)
  • Resend (email delivery)

Workspace:

  • pnpm 10 workspace monorepo (patchnotes-web, patchnotes-email)

Development

Database Migrations

The project uses separate EF Core migrations for SQLite (development) and SQL Server (production).

Creating a new migration (when you change entity models):

# Set SQL Server connection string (required)export ConnectionStrings__PatchNotes="Server=...;Database=...;User Id=...;Password=..."# Generate migrations for both providers
./scripts/add-migration.sh MigrationName

This creates migrations in:

  • PatchNotes.Data/Migrations/Sqlite/ - Local development
  • PatchNotes.Data/Migrations/SqlServer/ - Production

Important: CI will fail if you change models without creating migrations. The has-pending-model-changes check ensures migrations are always committed with model changes.

For more details, see PatchNotes.Data/README.md.

Running Tests

Backend:

dotnet test PatchNotes.slnx

Frontend:

cd patchnotes-web
pnpm test

Code Quality

The project uses GitHub Actions for CI. All PRs must pass:

  • dotnet build and dotnet test
  • pnpm lint and pnpm format:check
  • pnpm build (includes TypeScript type checking)

Contributing

Code Style

Backend (.NET):

  • Follow standard C# conventions
  • Use async/await for I/O operations
  • Keep controllers thin, business logic in services

Frontend (TypeScript):

  • Use TypeScript strict mode
  • Prefer TanStack Query for data fetching
  • Follow the existing component structure

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes with clear commit messages
  3. Ensure all CI checks pass
  4. Request review

License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

1,432 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

PatchNotes

A GitHub release viewer for npm packages. Track release notes across your favorite packages in one place.

Forged in Gas Town

Deployment

EnvironmentURLStatus
Frontendhttps://myreleasenotes.aiLive
APIhttps://api.myreleasenotes.aiLive
Sync Functionfn-patchnotes-sync (Azure Functions)Timer (every 6h)
Email Functionspatchnotes-email (Azure Functions)HTTP + Timer triggers

Project Status

Stage: Production (MVP)

AreaStatus
Architecture✅ .NET API + React SPA + Azure Functions
Code Quality✅ Good
CI/CD✅ GitHub Actions (build, test, deploy API + Function + frontend)
Testing✅ 368 xUnit tests + 118 Vitest tests
Authentication✅ Stytch B2C
Sync✅ Concurrent pipeline (Channel-based producer-consumer)

Features

  • Package Tracking - Add npm packages to monitor their GitHub releases
  • Release Timeline - Mobile-first timeline view grouped by date
  • Feed - Combined feed with server-side grouping, filtering stable/pre-release
  • Package Picker - Filter releases by selected packages
  • Sync Engine - Fetch releases from GitHub with rate limit awareness
  • AI Summaries - Generate concise release note summaries using Ollama Cloud (gemma4:31b)
  • Watchlist - Per-user package watchlists with default packages on signup
  • Subscriptions - Stripe-powered Pro subscriptions
  • Email Notifications - Welcome, release, and weekly digest emails via Resend
  • Design System - Consistent visual language across components

Architecture

  • PatchNotes.Data - EF Core models, SQLite/SQL Server, database seeding, version parsing
  • PatchNotes.Api - ASP.NET Core Web API (port 5031), Stytch authentication
  • PatchNotes.Sync - CLI tool + SyncPipeline for concurrent sync & summary generation, GitHub client, AI client. TODO: Refactor into a class library (PatchNotes.Sync.Core) and a thin CLI entry point — the API currently references this Exe project for the GitHub client, which causes Azure App Service startup issues when multiple runtimeconfig.json files are published.
  • PatchNotes.Functions - Azure Functions timer trigger that runs the SyncPipeline every 6 hours
  • patchnotes-web - React frontend with TanStack Router & Query, Orval-generated API client
  • patchnotes-email - Azure Functions (TypeScript) for email delivery via Resend

Prerequisites

  • .NET 10 SDK
  • Node.js 22+
  • pnpm 10+
  • direnv (recommended for secrets management)

Configuration

The project uses direnv for environment-based configuration. Create a secrets file at ~/.secrets/patchnotes/.env.local:

ConnectionStrings__PatchNotes=Server=localhost;Database=PatchNotes;User Id=sa;Password=...;TrustServerCertificate=TrueGitHub__Token=ghp_...Stytch__ProjectId=...Stytch__Secret=...Stytch__WebhookSecret=...Stripe__SecretKey=sk_test_...Stripe__WebhookSecret=whsec_...AI__ApiKey=...AI__BaseUrl=https://ollama.com/v1/AI__Model=gemma4:31bRESEND_API_KEY=re_...DATABASE_URL=Server=localhost;Database=PatchNotes;User id=sa;Password=...;TrustServerCertificate=true

Also copy the email function local settings:

cp patchnotes-email/local.settings.json.example patchnotes-email/local.settings.json

Azure Deployment Configuration

API -- App Service (api-myreleasenotes-ai)

SettingDescription
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT for repository search and release fetching
Stytch__ProjectIdStytch project ID
Stytch__SecretStytch secret key
Stytch__WebhookSecretStytch webhook signing secret
Stripe__SecretKeyStripe secret key
Stripe__WebhookSecretStripe webhook signing secret
SyncFunction__UrlURL of the sync function (e.g. https://fn-patchnotes-sync.azurewebsites.net/api/sync-new-packages)
SyncFunction__KeyFunction-level auth key for the sync function
EmailFunction__UrlURL of the test email function (e.g. https://fn-patchnotes-email.azurewebsites.net/api/sendTestEmail)
EmailFunction__KeyFunction-level auth key for the email function
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Sync Function -- Azure Functions (fn-patchnotes-sync)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEdotnet-isolated
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT
AI__ApiKeyAI provider API key
AI__BaseUrlAI provider endpoint
AI__ModelModel name (e.g. llama-3.3-70b-versatile)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Email Function -- Azure Functions (fn-patchnotes-email)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEnode
RESEND_API_KEYResend API key for email delivery
DATABASE_URLSQL Server connection string (ADO.NET format)
APP_BASE_URLBase URL for links in emails (default: https://app.myreleasenotes.ai)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Frontend -- Static Web Apps

Build-time variables set in CI:

SettingDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai) -- GitHub Actions variable
VITE_STYTCH_PUBLIC_TOKENStytch public token -- GitHub Actions secret

GitHub Actions

Secrets:

SecretDescription
AZURE_CLIENT_IDService principal client ID (OIDC login)
AZURE_TENANT_IDAzure AD tenant ID
AZURE_SUBSCRIPTION_IDAzure subscription ID
DATABASE_CONNECTION_STRINGSQL Server connection string (for migrations)
AZURE_STATIC_WEB_APPS_API_TOKENSWA deployment token
VITE_STYTCH_PUBLIC_TOKENStytch public token (frontend build)

Variables:

VariableDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai)

Quick Start

1. Build the backend

dotnet build

2. Apply database migrations (SQLite for local dev)

dotnet ef database update --context SqliteContext --project PatchNotes.Data --startup-project PatchNotes.Api

3. Seed the database

cd PatchNotes.Sync
dotnet run -- --seed

4. Run the API

cd PatchNotes.Api
dotnet run

API available at: http://localhost:5031

5. Run the frontend

cd patchnotes-web
pnpm install
pnpm dev

Frontend available at: http://localhost:5173

Testing

Test the API endpoints

# List packages
curl http://localhost:5031/api/packages
# Get releases (last 7 days)
curl http://localhost:5031/api/releases
# Get releases for specific packages
curl "http://localhost:5031/api/releases?packages=react,vue&days=30"# Get combined feed
curl http://localhost:5031/api/feed
# Get packages by owner
curl http://localhost:5031/api/packages/facebook
# Get package by owner/repo
curl http://localhost:5031/api/packages/facebook/react
# Add a package
curl -X POST http://localhost:5031/api/packages \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"npmName": "lodash"}'

Sync CLI

# Run full sync pipeline (concurrent sync + summary generation)
dotnet run --project PatchNotes.Sync
# Sync a single repo
dotnet run --project PatchNotes.Sync -- -r https://github.com/prettier/prettier
# Generate summaries for a specific package
dotnet run --project PatchNotes.Sync -- -s prettier/prettier
# Seed package catalog from packages.json and sync all from GitHub (first-time setup)
dotnet run --project PatchNotes.Sync -- --init
# Seed database with sample data (local dev)
dotnet run --project PatchNotes.Sync -- --seed

Exit codes: 0=success, 1=partial failure, 2=fatal error

The default sync uses a producer-consumer pipeline (SyncPipeline) — as soon as a package finishes syncing, its summaries start generating while the next package syncs.

Project Structure

PatchNotes/
├── PatchNotes.Api/ # Web API + Stytch auth
│ ├── Routes/ # Minimal API route handlers
│ ├── Stytch/ # Stytch authentication client
│ └── Webhooks/ # Stytch + Stripe webhook handlers
├── PatchNotes.Data/ # Data layer
│ ├── Migrations/ # EF Core migrations (Sqlite + SqlServer)
│ └── SeedData/ # Package catalog (packages.json)
├── PatchNotes.Sync/ # Sync CLI + SyncPipeline
│ ├── GitHub/ # GitHub API client
│ └── AI/ # AI client (OpenAI-compatible)
├── PatchNotes.Functions/ # Azure Functions (timer-triggered sync)
├── PatchNotes.Tests/ # xUnit tests
├── patchnotes-web/ # React frontend
│ └── src/
│ ├── api/ # Orval-generated + custom hooks
│ ├── components/ # UI components
│ ├── pages/ # Route pages
│ └── routes/ # TanStack Router config
└── patchnotes-email/ # Azure Functions (email via Resend)
└── src/
├── functions/ # sendWelcome, sendDigest
└── lib/ # Resend client

Tech Stack

Backend:

  • .NET 10 / ASP.NET Core
  • Entity Framework Core (SQLite dev / SQL Server prod)
  • Azure Functions (isolated worker, timer trigger)
  • GitHub API integration
  • AI summaries via Ollama Cloud (gemma4:31b, OpenAI-compatible API)
  • Stytch B2C authentication
  • Stripe subscriptions

Frontend:

  • React 19 + TypeScript
  • TanStack Router (type-safe file-based routing)
  • TanStack Query (data fetching)
  • Tailwind CSS 4
  • Vite 7
  • Orval (OpenAPI client generation)

Email:

  • Azure Functions (TypeScript)
  • Resend (email delivery)

Workspace:

  • pnpm 10 workspace monorepo (patchnotes-web, patchnotes-email)

Development

Database Migrations

The project uses separate EF Core migrations for SQLite (development) and SQL Server (production).

Creating a new migration (when you change entity models):

# Set SQL Server connection string (required)export ConnectionStrings__PatchNotes="Server=...;Database=...;User Id=...;Password=..."# Generate migrations for both providers
./scripts/add-migration.sh MigrationName

This creates migrations in:

  • PatchNotes.Data/Migrations/Sqlite/ - Local development
  • PatchNotes.Data/Migrations/SqlServer/ - Production

Important: CI will fail if you change models without creating migrations. The has-pending-model-changes check ensures migrations are always committed with model changes.

For more details, see PatchNotes.Data/README.md.

Running Tests

Backend:

dotnet test PatchNotes.slnx

Frontend:

cd patchnotes-web
pnpm test

Code Quality

The project uses GitHub Actions for CI. All PRs must pass:

  • dotnet build and dotnet test
  • pnpm lint and pnpm format:check
  • pnpm build (includes TypeScript type checking)

Contributing

Code Style

Backend (.NET):

  • Follow standard C# conventions
  • Use async/await for I/O operations
  • Keep controllers thin, business logic in services

Frontend (TypeScript):

  • Use TypeScript strict mode
  • Prefer TanStack Query for data fetching
  • Follow the existing component structure

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes with clear commit messages
  3. Ensure all CI checks pass
  4. Request review

License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,432 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

PatchNotes

A GitHub release viewer for npm packages. Track release notes across your favorite packages in one place.

Forged in Gas Town

Deployment

EnvironmentURLStatus
Frontendhttps://myreleasenotes.aiLive
APIhttps://api.myreleasenotes.aiLive
Sync Functionfn-patchnotes-sync (Azure Functions)Timer (every 6h)
Email Functionspatchnotes-email (Azure Functions)HTTP + Timer triggers

Project Status

Stage: Production (MVP)

AreaStatus
Architecture✅ .NET API + React SPA + Azure Functions
Code Quality✅ Good
CI/CD✅ GitHub Actions (build, test, deploy API + Function + frontend)
Testing✅ 368 xUnit tests + 118 Vitest tests
Authentication✅ Stytch B2C
Sync✅ Concurrent pipeline (Channel-based producer-consumer)

Features

  • Package Tracking - Add npm packages to monitor their GitHub releases
  • Release Timeline - Mobile-first timeline view grouped by date
  • Feed - Combined feed with server-side grouping, filtering stable/pre-release
  • Package Picker - Filter releases by selected packages
  • Sync Engine - Fetch releases from GitHub with rate limit awareness
  • AI Summaries - Generate concise release note summaries using Ollama Cloud (gemma4:31b)
  • Watchlist - Per-user package watchlists with default packages on signup
  • Subscriptions - Stripe-powered Pro subscriptions
  • Email Notifications - Welcome, release, and weekly digest emails via Resend
  • Design System - Consistent visual language across components

Architecture

  • PatchNotes.Data - EF Core models, SQLite/SQL Server, database seeding, version parsing
  • PatchNotes.Api - ASP.NET Core Web API (port 5031), Stytch authentication
  • PatchNotes.Sync - CLI tool + SyncPipeline for concurrent sync & summary generation, GitHub client, AI client. TODO: Refactor into a class library (PatchNotes.Sync.Core) and a thin CLI entry point — the API currently references this Exe project for the GitHub client, which causes Azure App Service startup issues when multiple runtimeconfig.json files are published.
  • PatchNotes.Functions - Azure Functions timer trigger that runs the SyncPipeline every 6 hours
  • patchnotes-web - React frontend with TanStack Router & Query, Orval-generated API client
  • patchnotes-email - Azure Functions (TypeScript) for email delivery via Resend

Prerequisites

  • .NET 10 SDK
  • Node.js 22+
  • pnpm 10+
  • direnv (recommended for secrets management)

Configuration

The project uses direnv for environment-based configuration. Create a secrets file at ~/.secrets/patchnotes/.env.local:

ConnectionStrings__PatchNotes=Server=localhost;Database=PatchNotes;User Id=sa;Password=...;TrustServerCertificate=TrueGitHub__Token=ghp_...Stytch__ProjectId=...Stytch__Secret=...Stytch__WebhookSecret=...Stripe__SecretKey=sk_test_...Stripe__WebhookSecret=whsec_...AI__ApiKey=...AI__BaseUrl=https://ollama.com/v1/AI__Model=gemma4:31bRESEND_API_KEY=re_...DATABASE_URL=Server=localhost;Database=PatchNotes;User id=sa;Password=...;TrustServerCertificate=true

Also copy the email function local settings:

cp patchnotes-email/local.settings.json.example patchnotes-email/local.settings.json

Azure Deployment Configuration

API -- App Service (api-myreleasenotes-ai)

SettingDescription
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT for repository search and release fetching
Stytch__ProjectIdStytch project ID
Stytch__SecretStytch secret key
Stytch__WebhookSecretStytch webhook signing secret
Stripe__SecretKeyStripe secret key
Stripe__WebhookSecretStripe webhook signing secret
SyncFunction__UrlURL of the sync function (e.g. https://fn-patchnotes-sync.azurewebsites.net/api/sync-new-packages)
SyncFunction__KeyFunction-level auth key for the sync function
EmailFunction__UrlURL of the test email function (e.g. https://fn-patchnotes-email.azurewebsites.net/api/sendTestEmail)
EmailFunction__KeyFunction-level auth key for the email function
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Sync Function -- Azure Functions (fn-patchnotes-sync)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEdotnet-isolated
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT
AI__ApiKeyAI provider API key
AI__BaseUrlAI provider endpoint
AI__ModelModel name (e.g. llama-3.3-70b-versatile)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Email Function -- Azure Functions (fn-patchnotes-email)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEnode
RESEND_API_KEYResend API key for email delivery
DATABASE_URLSQL Server connection string (ADO.NET format)
APP_BASE_URLBase URL for links in emails (default: https://app.myreleasenotes.ai)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Frontend -- Static Web Apps

Build-time variables set in CI:

SettingDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai) -- GitHub Actions variable
VITE_STYTCH_PUBLIC_TOKENStytch public token -- GitHub Actions secret

GitHub Actions

Secrets:

SecretDescription
AZURE_CLIENT_IDService principal client ID (OIDC login)
AZURE_TENANT_IDAzure AD tenant ID
AZURE_SUBSCRIPTION_IDAzure subscription ID
DATABASE_CONNECTION_STRINGSQL Server connection string (for migrations)
AZURE_STATIC_WEB_APPS_API_TOKENSWA deployment token
VITE_STYTCH_PUBLIC_TOKENStytch public token (frontend build)

Variables:

VariableDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai)

Quick Start

1. Build the backend

dotnet build

2. Apply database migrations (SQLite for local dev)

dotnet ef database update --context SqliteContext --project PatchNotes.Data --startup-project PatchNotes.Api

3. Seed the database

cd PatchNotes.Sync
dotnet run -- --seed

4. Run the API

cd PatchNotes.Api
dotnet run

API available at: http://localhost:5031

5. Run the frontend

cd patchnotes-web
pnpm install
pnpm dev

Frontend available at: http://localhost:5173

Testing

Test the API endpoints

# List packages
curl http://localhost:5031/api/packages
# Get releases (last 7 days)
curl http://localhost:5031/api/releases
# Get releases for specific packages
curl "http://localhost:5031/api/releases?packages=react,vue&days=30"# Get combined feed
curl http://localhost:5031/api/feed
# Get packages by owner
curl http://localhost:5031/api/packages/facebook
# Get package by owner/repo
curl http://localhost:5031/api/packages/facebook/react
# Add a package
curl -X POST http://localhost:5031/api/packages \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"npmName": "lodash"}'

Sync CLI

# Run full sync pipeline (concurrent sync + summary generation)
dotnet run --project PatchNotes.Sync
# Sync a single repo
dotnet run --project PatchNotes.Sync -- -r https://github.com/prettier/prettier
# Generate summaries for a specific package
dotnet run --project PatchNotes.Sync -- -s prettier/prettier
# Seed package catalog from packages.json and sync all from GitHub (first-time setup)
dotnet run --project PatchNotes.Sync -- --init
# Seed database with sample data (local dev)
dotnet run --project PatchNotes.Sync -- --seed

Exit codes: 0=success, 1=partial failure, 2=fatal error

The default sync uses a producer-consumer pipeline (SyncPipeline) — as soon as a package finishes syncing, its summaries start generating while the next package syncs.

Project Structure

PatchNotes/
├── PatchNotes.Api/ # Web API + Stytch auth
│ ├── Routes/ # Minimal API route handlers
│ ├── Stytch/ # Stytch authentication client
│ └── Webhooks/ # Stytch + Stripe webhook handlers
├── PatchNotes.Data/ # Data layer
│ ├── Migrations/ # EF Core migrations (Sqlite + SqlServer)
│ └── SeedData/ # Package catalog (packages.json)
├── PatchNotes.Sync/ # Sync CLI + SyncPipeline
│ ├── GitHub/ # GitHub API client
│ └── AI/ # AI client (OpenAI-compatible)
├── PatchNotes.Functions/ # Azure Functions (timer-triggered sync)
├── PatchNotes.Tests/ # xUnit tests
├── patchnotes-web/ # React frontend
│ └── src/
│ ├── api/ # Orval-generated + custom hooks
│ ├── components/ # UI components
│ ├── pages/ # Route pages
│ └── routes/ # TanStack Router config
└── patchnotes-email/ # Azure Functions (email via Resend)
└── src/
├── functions/ # sendWelcome, sendDigest
└── lib/ # Resend client

Tech Stack

Backend:

  • .NET 10 / ASP.NET Core
  • Entity Framework Core (SQLite dev / SQL Server prod)
  • Azure Functions (isolated worker, timer trigger)
  • GitHub API integration
  • AI summaries via Ollama Cloud (gemma4:31b, OpenAI-compatible API)
  • Stytch B2C authentication
  • Stripe subscriptions

Frontend:

  • React 19 + TypeScript
  • TanStack Router (type-safe file-based routing)
  • TanStack Query (data fetching)
  • Tailwind CSS 4
  • Vite 7
  • Orval (OpenAPI client generation)

Email:

  • Azure Functions (TypeScript)
  • Resend (email delivery)

Workspace:

  • pnpm 10 workspace monorepo (patchnotes-web, patchnotes-email)

Development

Database Migrations

The project uses separate EF Core migrations for SQLite (development) and SQL Server (production).

Creating a new migration (when you change entity models):

# Set SQL Server connection string (required)export ConnectionStrings__PatchNotes="Server=...;Database=...;User Id=...;Password=..."# Generate migrations for both providers
./scripts/add-migration.sh MigrationName

This creates migrations in:

  • PatchNotes.Data/Migrations/Sqlite/ - Local development
  • PatchNotes.Data/Migrations/SqlServer/ - Production

Important: CI will fail if you change models without creating migrations. The has-pending-model-changes check ensures migrations are always committed with model changes.

For more details, see PatchNotes.Data/README.md.

Running Tests

Backend:

dotnet test PatchNotes.slnx

Frontend:

cd patchnotes-web
pnpm test

Code Quality

The project uses GitHub Actions for CI. All PRs must pass:

  • dotnet build and dotnet test
  • pnpm lint and pnpm format:check
  • pnpm build (includes TypeScript type checking)

Contributing

Code Style

Backend (.NET):

  • Follow standard C# conventions
  • Use async/await for I/O operations
  • Keep controllers thin, business logic in services

Frontend (TypeScript):

  • Use TypeScript strict mode
  • Prefer TanStack Query for data fetching
  • Follow the existing component structure

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes with clear commit messages
  3. Ensure all CI checks pass
  4. Request review

License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,432 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

PatchNotes

A GitHub release viewer for npm packages. Track release notes across your favorite packages in one place.

Forged in Gas Town

Deployment

EnvironmentURLStatus
Frontendhttps://myreleasenotes.aiLive
APIhttps://api.myreleasenotes.aiLive
Sync Functionfn-patchnotes-sync (Azure Functions)Timer (every 6h)
Email Functionspatchnotes-email (Azure Functions)HTTP + Timer triggers

Project Status

Stage: Production (MVP)

AreaStatus
Architecture✅ .NET API + React SPA + Azure Functions
Code Quality✅ Good
CI/CD✅ GitHub Actions (build, test, deploy API + Function + frontend)
Testing✅ 368 xUnit tests + 118 Vitest tests
Authentication✅ Stytch B2C
Sync✅ Concurrent pipeline (Channel-based producer-consumer)

Features

  • Package Tracking - Add npm packages to monitor their GitHub releases
  • Release Timeline - Mobile-first timeline view grouped by date
  • Feed - Combined feed with server-side grouping, filtering stable/pre-release
  • Package Picker - Filter releases by selected packages
  • Sync Engine - Fetch releases from GitHub with rate limit awareness
  • AI Summaries - Generate concise release note summaries using Ollama Cloud (gemma4:31b)
  • Watchlist - Per-user package watchlists with default packages on signup
  • Subscriptions - Stripe-powered Pro subscriptions
  • Email Notifications - Welcome, release, and weekly digest emails via Resend
  • Design System - Consistent visual language across components

Architecture

  • PatchNotes.Data - EF Core models, SQLite/SQL Server, database seeding, version parsing
  • PatchNotes.Api - ASP.NET Core Web API (port 5031), Stytch authentication
  • PatchNotes.Sync - CLI tool + SyncPipeline for concurrent sync & summary generation, GitHub client, AI client. TODO: Refactor into a class library (PatchNotes.Sync.Core) and a thin CLI entry point — the API currently references this Exe project for the GitHub client, which causes Azure App Service startup issues when multiple runtimeconfig.json files are published.
  • PatchNotes.Functions - Azure Functions timer trigger that runs the SyncPipeline every 6 hours
  • patchnotes-web - React frontend with TanStack Router & Query, Orval-generated API client
  • patchnotes-email - Azure Functions (TypeScript) for email delivery via Resend

Prerequisites

  • .NET 10 SDK
  • Node.js 22+
  • pnpm 10+
  • direnv (recommended for secrets management)

Configuration

The project uses direnv for environment-based configuration. Create a secrets file at ~/.secrets/patchnotes/.env.local:

ConnectionStrings__PatchNotes=Server=localhost;Database=PatchNotes;User Id=sa;Password=...;TrustServerCertificate=TrueGitHub__Token=ghp_...Stytch__ProjectId=...Stytch__Secret=...Stytch__WebhookSecret=...Stripe__SecretKey=sk_test_...Stripe__WebhookSecret=whsec_...AI__ApiKey=...AI__BaseUrl=https://ollama.com/v1/AI__Model=gemma4:31bRESEND_API_KEY=re_...DATABASE_URL=Server=localhost;Database=PatchNotes;User id=sa;Password=...;TrustServerCertificate=true

Also copy the email function local settings:

cp patchnotes-email/local.settings.json.example patchnotes-email/local.settings.json

Azure Deployment Configuration

API -- App Service (api-myreleasenotes-ai)

SettingDescription
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT for repository search and release fetching
Stytch__ProjectIdStytch project ID
Stytch__SecretStytch secret key
Stytch__WebhookSecretStytch webhook signing secret
Stripe__SecretKeyStripe secret key
Stripe__WebhookSecretStripe webhook signing secret
SyncFunction__UrlURL of the sync function (e.g. https://fn-patchnotes-sync.azurewebsites.net/api/sync-new-packages)
SyncFunction__KeyFunction-level auth key for the sync function
EmailFunction__UrlURL of the test email function (e.g. https://fn-patchnotes-email.azurewebsites.net/api/sendTestEmail)
EmailFunction__KeyFunction-level auth key for the email function
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Sync Function -- Azure Functions (fn-patchnotes-sync)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEdotnet-isolated
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT
AI__ApiKeyAI provider API key
AI__BaseUrlAI provider endpoint
AI__ModelModel name (e.g. llama-3.3-70b-versatile)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Email Function -- Azure Functions (fn-patchnotes-email)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEnode
RESEND_API_KEYResend API key for email delivery
DATABASE_URLSQL Server connection string (ADO.NET format)
APP_BASE_URLBase URL for links in emails (default: https://app.myreleasenotes.ai)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Frontend -- Static Web Apps

Build-time variables set in CI:

SettingDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai) -- GitHub Actions variable
VITE_STYTCH_PUBLIC_TOKENStytch public token -- GitHub Actions secret

GitHub Actions

Secrets:

SecretDescription
AZURE_CLIENT_IDService principal client ID (OIDC login)
AZURE_TENANT_IDAzure AD tenant ID
AZURE_SUBSCRIPTION_IDAzure subscription ID
DATABASE_CONNECTION_STRINGSQL Server connection string (for migrations)
AZURE_STATIC_WEB_APPS_API_TOKENSWA deployment token
VITE_STYTCH_PUBLIC_TOKENStytch public token (frontend build)

Variables:

VariableDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai)

Quick Start

1. Build the backend

dotnet build

2. Apply database migrations (SQLite for local dev)

dotnet ef database update --context SqliteContext --project PatchNotes.Data --startup-project PatchNotes.Api

3. Seed the database

cd PatchNotes.Sync
dotnet run -- --seed

4. Run the API

cd PatchNotes.Api
dotnet run

API available at: http://localhost:5031

5. Run the frontend

cd patchnotes-web
pnpm install
pnpm dev

Frontend available at: http://localhost:5173

Testing

Test the API endpoints

# List packages
curl http://localhost:5031/api/packages
# Get releases (last 7 days)
curl http://localhost:5031/api/releases
# Get releases for specific packages
curl "http://localhost:5031/api/releases?packages=react,vue&days=30"# Get combined feed
curl http://localhost:5031/api/feed
# Get packages by owner
curl http://localhost:5031/api/packages/facebook
# Get package by owner/repo
curl http://localhost:5031/api/packages/facebook/react
# Add a package
curl -X POST http://localhost:5031/api/packages \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"npmName": "lodash"}'

Sync CLI

# Run full sync pipeline (concurrent sync + summary generation)
dotnet run --project PatchNotes.Sync
# Sync a single repo
dotnet run --project PatchNotes.Sync -- -r https://github.com/prettier/prettier
# Generate summaries for a specific package
dotnet run --project PatchNotes.Sync -- -s prettier/prettier
# Seed package catalog from packages.json and sync all from GitHub (first-time setup)
dotnet run --project PatchNotes.Sync -- --init
# Seed database with sample data (local dev)
dotnet run --project PatchNotes.Sync -- --seed

Exit codes: 0=success, 1=partial failure, 2=fatal error

The default sync uses a producer-consumer pipeline (SyncPipeline) — as soon as a package finishes syncing, its summaries start generating while the next package syncs.

Project Structure

PatchNotes/
├── PatchNotes.Api/ # Web API + Stytch auth
│ ├── Routes/ # Minimal API route handlers
│ ├── Stytch/ # Stytch authentication client
│ └── Webhooks/ # Stytch + Stripe webhook handlers
├── PatchNotes.Data/ # Data layer
│ ├── Migrations/ # EF Core migrations (Sqlite + SqlServer)
│ └── SeedData/ # Package catalog (packages.json)
├── PatchNotes.Sync/ # Sync CLI + SyncPipeline
│ ├── GitHub/ # GitHub API client
│ └── AI/ # AI client (OpenAI-compatible)
├── PatchNotes.Functions/ # Azure Functions (timer-triggered sync)
├── PatchNotes.Tests/ # xUnit tests
├── patchnotes-web/ # React frontend
│ └── src/
│ ├── api/ # Orval-generated + custom hooks
│ ├── components/ # UI components
│ ├── pages/ # Route pages
│ └── routes/ # TanStack Router config
└── patchnotes-email/ # Azure Functions (email via Resend)
└── src/
├── functions/ # sendWelcome, sendDigest
└── lib/ # Resend client

Tech Stack

Backend:

  • .NET 10 / ASP.NET Core
  • Entity Framework Core (SQLite dev / SQL Server prod)
  • Azure Functions (isolated worker, timer trigger)
  • GitHub API integration
  • AI summaries via Ollama Cloud (gemma4:31b, OpenAI-compatible API)
  • Stytch B2C authentication
  • Stripe subscriptions

Frontend:

  • React 19 + TypeScript
  • TanStack Router (type-safe file-based routing)
  • TanStack Query (data fetching)
  • Tailwind CSS 4
  • Vite 7
  • Orval (OpenAPI client generation)

Email:

  • Azure Functions (TypeScript)
  • Resend (email delivery)

Workspace:

  • pnpm 10 workspace monorepo (patchnotes-web, patchnotes-email)

Development

Database Migrations

The project uses separate EF Core migrations for SQLite (development) and SQL Server (production).

Creating a new migration (when you change entity models):

# Set SQL Server connection string (required)export ConnectionStrings__PatchNotes="Server=...;Database=...;User Id=...;Password=..."# Generate migrations for both providers
./scripts/add-migration.sh MigrationName

This creates migrations in:

  • PatchNotes.Data/Migrations/Sqlite/ - Local development
  • PatchNotes.Data/Migrations/SqlServer/ - Production

Important: CI will fail if you change models without creating migrations. The has-pending-model-changes check ensures migrations are always committed with model changes.

For more details, see PatchNotes.Data/README.md.

Running Tests

Backend:

dotnet test PatchNotes.slnx

Frontend:

cd patchnotes-web
pnpm test

Code Quality

The project uses GitHub Actions for CI. All PRs must pass:

  • dotnet build and dotnet test
  • pnpm lint and pnpm format:check
  • pnpm build (includes TypeScript type checking)

Contributing

Code Style

Backend (.NET):

  • Follow standard C# conventions
  • Use async/await for I/O operations
  • Keep controllers thin, business logic in services

Frontend (TypeScript):

  • Use TypeScript strict mode
  • Prefer TanStack Query for data fetching
  • Follow the existing component structure

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes with clear commit messages
  3. Ensure all CI checks pass
  4. Request review

License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

1,432 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

PatchNotes

A GitHub release viewer for npm packages. Track release notes across your favorite packages in one place.

Forged in Gas Town

Deployment

EnvironmentURLStatus
Frontendhttps://myreleasenotes.aiLive
APIhttps://api.myreleasenotes.aiLive
Sync Functionfn-patchnotes-sync (Azure Functions)Timer (every 6h)
Email Functionspatchnotes-email (Azure Functions)HTTP + Timer triggers

Project Status

Stage: Production (MVP)

AreaStatus
Architecture✅ .NET API + React SPA + Azure Functions
Code Quality✅ Good
CI/CD✅ GitHub Actions (build, test, deploy API + Function + frontend)
Testing✅ 368 xUnit tests + 118 Vitest tests
Authentication✅ Stytch B2C
Sync✅ Concurrent pipeline (Channel-based producer-consumer)

Features

  • Package Tracking - Add npm packages to monitor their GitHub releases
  • Release Timeline - Mobile-first timeline view grouped by date
  • Feed - Combined feed with server-side grouping, filtering stable/pre-release
  • Package Picker - Filter releases by selected packages
  • Sync Engine - Fetch releases from GitHub with rate limit awareness
  • AI Summaries - Generate concise release note summaries using Ollama Cloud (gemma4:31b)
  • Watchlist - Per-user package watchlists with default packages on signup
  • Subscriptions - Stripe-powered Pro subscriptions
  • Email Notifications - Welcome, release, and weekly digest emails via Resend
  • Design System - Consistent visual language across components

Architecture

  • PatchNotes.Data - EF Core models, SQLite/SQL Server, database seeding, version parsing
  • PatchNotes.Api - ASP.NET Core Web API (port 5031), Stytch authentication
  • PatchNotes.Sync - CLI tool + SyncPipeline for concurrent sync & summary generation, GitHub client, AI client. TODO: Refactor into a class library (PatchNotes.Sync.Core) and a thin CLI entry point — the API currently references this Exe project for the GitHub client, which causes Azure App Service startup issues when multiple runtimeconfig.json files are published.
  • PatchNotes.Functions - Azure Functions timer trigger that runs the SyncPipeline every 6 hours
  • patchnotes-web - React frontend with TanStack Router & Query, Orval-generated API client
  • patchnotes-email - Azure Functions (TypeScript) for email delivery via Resend

Prerequisites

  • .NET 10 SDK
  • Node.js 22+
  • pnpm 10+
  • direnv (recommended for secrets management)

Configuration

The project uses direnv for environment-based configuration. Create a secrets file at ~/.secrets/patchnotes/.env.local:

ConnectionStrings__PatchNotes=Server=localhost;Database=PatchNotes;User Id=sa;Password=...;TrustServerCertificate=TrueGitHub__Token=ghp_...Stytch__ProjectId=...Stytch__Secret=...Stytch__WebhookSecret=...Stripe__SecretKey=sk_test_...Stripe__WebhookSecret=whsec_...AI__ApiKey=...AI__BaseUrl=https://ollama.com/v1/AI__Model=gemma4:31bRESEND_API_KEY=re_...DATABASE_URL=Server=localhost;Database=PatchNotes;User id=sa;Password=...;TrustServerCertificate=true

Also copy the email function local settings:

cp patchnotes-email/local.settings.json.example patchnotes-email/local.settings.json

Azure Deployment Configuration

API -- App Service (api-myreleasenotes-ai)

SettingDescription
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT for repository search and release fetching
Stytch__ProjectIdStytch project ID
Stytch__SecretStytch secret key
Stytch__WebhookSecretStytch webhook signing secret
Stripe__SecretKeyStripe secret key
Stripe__WebhookSecretStripe webhook signing secret
SyncFunction__UrlURL of the sync function (e.g. https://fn-patchnotes-sync.azurewebsites.net/api/sync-new-packages)
SyncFunction__KeyFunction-level auth key for the sync function
EmailFunction__UrlURL of the test email function (e.g. https://fn-patchnotes-email.azurewebsites.net/api/sendTestEmail)
EmailFunction__KeyFunction-level auth key for the email function
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Sync Function -- Azure Functions (fn-patchnotes-sync)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEdotnet-isolated
ConnectionStrings__PatchNotesSQL Server connection string
GitHub__TokenGitHub PAT
AI__ApiKeyAI provider API key
AI__BaseUrlAI provider endpoint
AI__ModelModel name (e.g. llama-3.3-70b-versatile)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Email Function -- Azure Functions (fn-patchnotes-email)

SettingDescription
AzureWebJobsStorageAzure Storage connection string
FUNCTIONS_WORKER_RUNTIMEnode
RESEND_API_KEYResend API key for email delivery
DATABASE_URLSQL Server connection string (ADO.NET format)
APP_BASE_URLBase URL for links in emails (default: https://app.myreleasenotes.ai)
APPLICATIONINSIGHTS_CONNECTION_STRINGApplication Insights connection string

Frontend -- Static Web Apps

Build-time variables set in CI:

SettingDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai) -- GitHub Actions variable
VITE_STYTCH_PUBLIC_TOKENStytch public token -- GitHub Actions secret

GitHub Actions

Secrets:

SecretDescription
AZURE_CLIENT_IDService principal client ID (OIDC login)
AZURE_TENANT_IDAzure AD tenant ID
AZURE_SUBSCRIPTION_IDAzure subscription ID
DATABASE_CONNECTION_STRINGSQL Server connection string (for migrations)
AZURE_STATIC_WEB_APPS_API_TOKENSWA deployment token
VITE_STYTCH_PUBLIC_TOKENStytch public token (frontend build)

Variables:

VariableDescription
VITE_API_URLAPI base URL (e.g. https://api.myreleasenotes.ai)

Quick Start

1. Build the backend

dotnet build

2. Apply database migrations (SQLite for local dev)

dotnet ef database update --context SqliteContext --project PatchNotes.Data --startup-project PatchNotes.Api

3. Seed the database

cd PatchNotes.Sync
dotnet run -- --seed

4. Run the API

cd PatchNotes.Api
dotnet run

API available at: http://localhost:5031

5. Run the frontend

cd patchnotes-web
pnpm install
pnpm dev

Frontend available at: http://localhost:5173

Testing

Test the API endpoints

# List packages
curl http://localhost:5031/api/packages
# Get releases (last 7 days)
curl http://localhost:5031/api/releases
# Get releases for specific packages
curl "http://localhost:5031/api/releases?packages=react,vue&days=30"# Get combined feed
curl http://localhost:5031/api/feed
# Get packages by owner
curl http://localhost:5031/api/packages/facebook
# Get package by owner/repo
curl http://localhost:5031/api/packages/facebook/react
# Add a package
curl -X POST http://localhost:5031/api/packages \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"npmName": "lodash"}'

Sync CLI

# Run full sync pipeline (concurrent sync + summary generation)
dotnet run --project PatchNotes.Sync
# Sync a single repo
dotnet run --project PatchNotes.Sync -- -r https://github.com/prettier/prettier
# Generate summaries for a specific package
dotnet run --project PatchNotes.Sync -- -s prettier/prettier
# Seed package catalog from packages.json and sync all from GitHub (first-time setup)
dotnet run --project PatchNotes.Sync -- --init
# Seed database with sample data (local dev)
dotnet run --project PatchNotes.Sync -- --seed

Exit codes: 0=success, 1=partial failure, 2=fatal error

The default sync uses a producer-consumer pipeline (SyncPipeline) — as soon as a package finishes syncing, its summaries start generating while the next package syncs.

Project Structure

PatchNotes/
├── PatchNotes.Api/ # Web API + Stytch auth
│ ├── Routes/ # Minimal API route handlers
│ ├── Stytch/ # Stytch authentication client
│ └── Webhooks/ # Stytch + Stripe webhook handlers
├── PatchNotes.Data/ # Data layer
│ ├── Migrations/ # EF Core migrations (Sqlite + SqlServer)
│ └── SeedData/ # Package catalog (packages.json)
├── PatchNotes.Sync/ # Sync CLI + SyncPipeline
│ ├── GitHub/ # GitHub API client
│ └── AI/ # AI client (OpenAI-compatible)
├── PatchNotes.Functions/ # Azure Functions (timer-triggered sync)
├── PatchNotes.Tests/ # xUnit tests
├── patchnotes-web/ # React frontend
│ └── src/
│ ├── api/ # Orval-generated + custom hooks
│ ├── components/ # UI components
│ ├── pages/ # Route pages
│ └── routes/ # TanStack Router config
└── patchnotes-email/ # Azure Functions (email via Resend)
└── src/
├── functions/ # sendWelcome, sendDigest
└── lib/ # Resend client

Tech Stack

Backend:

  • .NET 10 / ASP.NET Core
  • Entity Framework Core (SQLite dev / SQL Server prod)
  • Azure Functions (isolated worker, timer trigger)
  • GitHub API integration
  • AI summaries via Ollama Cloud (gemma4:31b, OpenAI-compatible API)
  • Stytch B2C authentication
  • Stripe subscriptions

Frontend:

  • React 19 + TypeScript
  • TanStack Router (type-safe file-based routing)
  • TanStack Query (data fetching)
  • Tailwind CSS 4
  • Vite 7
  • Orval (OpenAPI client generation)

Email:

  • Azure Functions (TypeScript)
  • Resend (email delivery)

Workspace:

  • pnpm 10 workspace monorepo (patchnotes-web, patchnotes-email)

Development

Database Migrations

The project uses separate EF Core migrations for SQLite (development) and SQL Server (production).

Creating a new migration (when you change entity models):

# Set SQL Server connection string (required)export ConnectionStrings__PatchNotes="Server=...;Database=...;User Id=...;Password=..."# Generate migrations for both providers
./scripts/add-migration.sh MigrationName

This creates migrations in:

  • PatchNotes.Data/Migrations/Sqlite/ - Local development
  • PatchNotes.Data/Migrations/SqlServer/ - Production

Important: CI will fail if you change models without creating migrations. The has-pending-model-changes check ensures migrations are always committed with model changes.

For more details, see PatchNotes.Data/README.md.

Running Tests

Backend:

dotnet test PatchNotes.slnx

Frontend:

cd patchnotes-web
pnpm test

Code Quality

The project uses GitHub Actions for CI. All PRs must pass:

  • dotnet build and dotnet test
  • pnpm lint and pnpm format:check
  • pnpm build (includes TypeScript type checking)

Contributing

Code Style

Backend (.NET):

  • Follow standard C# conventions
  • Use async/await for I/O operations
  • Keep controllers thin, business logic in services

Frontend (TypeScript):

  • Use TypeScript strict mode
  • Prefer TanStack Query for data fetching
  • Follow the existing component structure

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes with clear commit messages
  3. Ensure all CI checks pass
  4. Request review

License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages