feat: add README rendering and enhanced package metadata display

**Database Schema:**
- Add npmReadme, npmKeywords, npmAuthor, npmMaintainers fields to Tool model

**NPM Client:**
- Add fetchLatestPackageWithMetadata() function to fetch README and top-level metadata
- Export new PackageVersionWithReadme type

**Sync Workers:**
- Update keyword and changes sync to fetch and store README content
- Store author, maintainers, and keywords from package.json

**UI Components:**
- Create Markdown component using react-markdown with GitHub Flavored Markdown
- Add rehype-sanitize for security and remark-gfm for tables/strikethrough support

**Tool Detail Page:**
- Convert from createElement to JSX for better maintainability
- Display README in a dedicated card with proper markdown rendering
- Show NPM keywords, author, and maintainers in sidebar
- Add ThemeToggle to header
- Improve layout with better spacing and organization

This brings the tool detail pages much closer to NPM's package pages,
providing users with comprehensive information about each tool including
the full README documentation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-29 22:41:50 +10:00
parent 506251bfa5
commit 32499bbc68
9 changed files with 1583 additions and 560 deletions

View file

@ -22,6 +22,10 @@ model Tool {
npmRepository Json? @map("npm_repository") @db.JsonB
npmHomepage String? @map("npm_homepage") @db.Text
npmLicense String? @map("npm_license") @db.VarChar(50)
npmKeywords String[] @default([]) @map("npm_keywords") @db.Text
npmReadme String? @map("npm_readme") @db.Text
npmAuthor Json? @map("npm_author") @db.JsonB
npmMaintainers Json? @map("npm_maintainers") @db.JsonB
// TPMJS Metadata (from package.json tpmjs field)
category String @db.VarChar(50)

View file

@ -26,9 +26,11 @@ export {
export {
fetchPackageMetadata,
fetchLatestPackageVersion,
fetchLatestPackageWithMetadata,
fetchPackageTpmjsField,
type PackageVersion,
type PackageMetadata,
type PackageVersionWithReadme,
} from './package';
// Download statistics

View file

@ -160,6 +160,48 @@ export async function fetchLatestPackageVersion(
};
}
export type PackageVersionWithReadme = PackageVersion & {
readme?: string;
topLevelKeywords?: string[];
topLevelDescription?: string;
};
/**
* Gets the latest version of a package with additional top-level metadata
* @param packageName - The name of the package
* @returns The latest package version with README and top-level metadata, or null if not found
*/
export async function fetchLatestPackageWithMetadata(
packageName: string
): Promise<PackageVersionWithReadme | null> {
const metadata = await fetchPackageMetadata(packageName);
if (!metadata) {
return null;
}
const latestTag = metadata['dist-tags'].latest;
if (!latestTag) {
return null;
}
const version = metadata.versions[latestTag];
if (!version) {
return null;
}
// Add publishedAt from metadata.time
const publishedAt = metadata.time?.[latestTag];
return {
...version,
publishedAt,
readme: metadata.readme,
topLevelKeywords: metadata.keywords,
topLevelDescription: metadata.description,
};
}
/**
* Checks if a package has a tpmjs field in its latest version
* @param packageName - The name of the package