fix: add health fields to /api/tools/search response

The search endpoint was missing importHealth, executionHealth,
healthCheckError, and lastHealthCheck fields in the response. This caused
the playground (which uses search-registry tool) to not receive health
data for displaying broken tool badges.

Added all four health fields to the tool mapping in the search response.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-12-04 19:41:50 +10:00
parent 9f1c3fb383
commit 4ba39e07f8
6 changed files with 84 additions and 117 deletions

View file

@ -153,6 +153,10 @@ export async function GET(request: Request) {
exportName: tool.exportName,
description: tool.description,
qualityScore: tool.qualityScore,
importHealth: tool.importHealth,
executionHealth: tool.executionHealth,
healthCheckError: tool.healthCheckError,
lastHealthCheck: tool.lastHealthCheck,
package: {
npmPackageName: tool.package.npmPackageName,
npmVersion: tool.package.npmVersion,

View file

@ -1,5 +1,6 @@
import type { Metadata } from 'next';
import { Space_Grotesk, Space_Mono } from 'next/font/google';
import { AppFooter } from '../components/AppFooter';
import { ThemeProvider } from '../components/providers/ThemeProvider';
import './globals.css';
@ -40,7 +41,10 @@ export default function RootLayout({
enableSystem={true}
disableTransitionOnChange={false}
>
{children}
<div className="flex flex-col min-h-screen">
<div className="flex-1">{children}</div>
<AppFooter />
</div>
</ThemeProvider>
</body>
</html>

View file

@ -74,10 +74,10 @@ async function getHomePageData() {
export default async function HomePage(): Promise<React.ReactElement> {
const data = await getHomePageData();
return (
<div className="min-h-screen flex flex-col">
<>
<AppHeader />
<main className="flex-1">
<main>
{/* Hero Section - Dithered Design */}
<HeroSection stats={data.stats} />
@ -205,41 +205,6 @@ export default async function HomePage(): Promise<React.ReactElement> {
</Container>
</section>
</main>
{/* Footer */}
<footer className="py-8 border-t border-border bg-surface">
<Container size="xl" padding="lg">
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
<p className="text-sm text-foreground-secondary">© 2025 TPMJS. All rights reserved.</p>
<div className="flex items-center gap-4 text-sm">
<a
href="mailto:thomasalwyndavis@gmail.com"
className="text-foreground-secondary hover:text-foreground transition-colors"
>
Contact
</a>
<span className="text-border">·</span>
<a
href="https://github.com/tpmjs/tpmjs"
target="_blank"
rel="noopener noreferrer"
className="text-foreground-secondary hover:text-foreground transition-colors"
>
GitHub
</a>
<span className="text-border">·</span>
<a
href="https://twitter.com/tpmjs"
target="_blank"
rel="noopener noreferrer"
className="text-foreground-secondary hover:text-foreground transition-colors"
>
Twitter
</a>
</div>
</div>
</Container>
</footer>
</div>
</>
);
}

View file

@ -245,18 +245,34 @@ export default function ToolSearchPage(): React.ReactElement {
{tool.package.npmPackageName}
</div>
</div>
{tool.package.npmRepository && (
<a
href={tool.package.npmRepository.url
.replace('git+', '')
.replace('.git', '')}
target="_blank"
rel="noopener noreferrer"
className="text-foreground-secondary hover:text-foreground transition-colors"
>
<Icon icon="externalLink" size="sm" />
</a>
)}
{tool.package.npmRepository &&
(() => {
// Clean up repository URL
let repoUrl = tool.package.npmRepository.url;
// Remove git+ prefix
repoUrl = repoUrl.replace(/^git\+/, '');
// Remove .git suffix
repoUrl = repoUrl.replace(/\.git$/, '');
// Convert git:// to https://
repoUrl = repoUrl.replace(/^git:\/\//, 'https://');
// Convert ssh URLs to https
repoUrl = repoUrl.replace(/^git@github\.com:/, 'https://github.com/');
return (
<a
href={repoUrl}
target="_blank"
rel="noopener noreferrer"
className="text-foreground-secondary hover:text-foreground transition-colors"
>
<Icon icon="externalLink" size="sm" />
</a>
);
})()}
</div>
<CardDescription>{tool.description}</CardDescription>
</CardHeader>

View file

@ -0,0 +1,41 @@
'use client';
import { Container } from '@tpmjs/ui/Container/Container';
export function AppFooter(): React.ReactElement {
return (
<footer className="py-8 border-t border-border bg-surface">
<Container size="xl" padding="lg">
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
<p className="text-sm text-foreground-secondary">© 2025 TPMJS. All rights reserved.</p>
<div className="flex items-center gap-4 text-sm">
<a
href="mailto:thomasalwyndavis@gmail.com"
className="text-foreground-secondary hover:text-foreground transition-colors"
>
Contact
</a>
<span className="text-border">·</span>
<a
href="https://github.com/tpmjs/tpmjs"
target="_blank"
rel="noopener noreferrer"
className="text-foreground-secondary hover:text-foreground transition-colors"
>
GitHub
</a>
<span className="text-border">·</span>
<a
href="https://twitter.com/tpmjs_registry"
target="_blank"
rel="noopener noreferrer"
className="text-foreground-secondary hover:text-foreground transition-colors"
>
Twitter
</a>
</div>
</div>
</Container>
</footer>
);
}

View file

@ -1,7 +1,7 @@
import { cn } from '@tpmjs/utils/cn';
import { forwardRef, useState } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { prism, vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { Icon } from '../Icon/Icon';
import type { CodeBlockProps } from './types';
import {
@ -10,70 +10,6 @@ import {
codeBlockCopyButtonVariants,
} from './variants';
// Custom light theme with better contrast
const customLightTheme = {
'code[class*="language-"]': {
color: '#24292e',
fontFamily: 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace',
fontSize: '1em',
textAlign: 'left',
whiteSpace: 'pre',
wordSpacing: 'normal',
wordBreak: 'normal',
wordWrap: 'normal',
lineHeight: '1.5',
tabSize: '4',
hyphens: 'none',
},
'pre[class*="language-"]': {
color: '#24292e',
fontFamily: 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace',
fontSize: '1em',
textAlign: 'left',
whiteSpace: 'pre',
wordSpacing: 'normal',
wordBreak: 'normal',
wordWrap: 'normal',
lineHeight: '1.5',
tabSize: '4',
hyphens: 'none',
padding: '1em',
margin: '0.5em 0',
overflow: 'auto',
},
comment: { color: '#6a737d', fontStyle: 'italic' },
prolog: { color: '#6a737d' },
doctype: { color: '#6a737d' },
cdata: { color: '#6a737d' },
punctuation: { color: '#24292e' },
property: { color: '#005cc5' },
tag: { color: '#22863a' },
boolean: { color: '#005cc5' },
number: { color: '#005cc5' },
constant: { color: '#005cc5' },
symbol: { color: '#005cc5' },
deleted: { color: '#b31d28' },
selector: { color: '#22863a' },
'attr-name': { color: '#6f42c1' },
string: { color: '#032f62' },
char: { color: '#032f62' },
builtin: { color: '#005cc5' },
inserted: { color: '#22863a' },
operator: { color: '#d73a49' },
entity: { color: '#6f42c1' },
url: { color: '#032f62' },
variable: { color: '#e36209' },
atrule: { color: '#d73a49' },
'attr-value': { color: '#032f62' },
function: { color: '#6f42c1' },
'class-name': { color: '#6f42c1' },
keyword: { color: '#d73a49' },
regex: { color: '#032f62' },
important: { color: '#d73a49', fontWeight: 'bold' },
bold: { fontWeight: 'bold' },
italic: { fontStyle: 'italic' },
};
/**
* CodeBlock component
*
@ -134,7 +70,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
<div className={codeBlockCodeVariants({ size })} data-language={normalizedLanguage}>
<SyntaxHighlighter
language={normalizedLanguage}
style={theme === 'dark' ? oneDark : oneLight}
style={theme === 'dark' ? vscDarkPlus : prism}
customStyle={{
margin: 0,
padding: 0,
@ -145,6 +81,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
codeTagProps={{
style: {
fontFamily: 'inherit',
color: '#24292e', // Darker text color for better contrast
},
}}
>