feat: add GitHub Action for daily health check cron and backfill script
- Create .github/workflows/health-check.yml to run daily at 2am UTC - Add scripts/backfill-health-checks.ts to populate health data for existing tools - Remove health-check from vercel.json crons (now using GitHub Actions) The GitHub Action workflow follows the same pattern as other sync operations and calls the /api/sync/health-check endpoint with proper authentication. The backfill script: - Fetches all tools from database - Runs batch health checks with concurrency control (5 tools at a time) - Shows detailed progress and summary statistics - Lists broken tools with error details 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
64a05482e4
commit
958697f788
3 changed files with 151 additions and 4 deletions
18
.github/workflows/health-check.yml
vendored
Normal file
18
.github/workflows/health-check.yml
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
name: Daily Health Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Run daily at 2am UTC
|
||||||
|
- cron: '0 2 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
health-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Trigger health check sync
|
||||||
|
run: |
|
||||||
|
curl -X POST "${{ secrets.VERCEL_PRODUCTION_URL }}/api/sync/health-check" \
|
||||||
|
-H "Authorization: Bearer ${{ secrets.CRON_SECRET }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-f -s -S -w "\nHTTP Status: %{http_code}\n"
|
||||||
133
scripts/backfill-health-checks.ts
Normal file
133
scripts/backfill-health-checks.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
#!/usr/bin/env tsx
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backfill Health Checks Script
|
||||||
|
*
|
||||||
|
* Runs health checks on all existing tools in the database to populate
|
||||||
|
* initial health status data. This is a one-time script to be run after
|
||||||
|
* deploying the health check system.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* pnpm tsx scripts/backfill-health-checks.ts
|
||||||
|
*
|
||||||
|
* Environment Variables Required:
|
||||||
|
* DATABASE_URL - PostgreSQL connection string
|
||||||
|
* RAILWAY_EXECUTOR_URL - Railway executor service URL
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { prisma } from '@tpmjs/db';
|
||||||
|
import { performBatchHealthCheck } from '../apps/web/src/lib/health-check/health-check-service';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('🏥 Starting health check backfill...\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch all tool IDs
|
||||||
|
console.log('📊 Fetching all tools from database...');
|
||||||
|
const tools = await prisma.tool.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
exportName: true,
|
||||||
|
package: {
|
||||||
|
select: {
|
||||||
|
npmPackageName: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Found ${tools.length} tools to check\n`);
|
||||||
|
|
||||||
|
if (tools.length === 0) {
|
||||||
|
console.log('No tools found in database. Exiting.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract tool IDs
|
||||||
|
const toolIds = tools.map((t) => t.id);
|
||||||
|
|
||||||
|
// Run batch health check with 5 concurrent checks and 1s delay between batches
|
||||||
|
console.log('🔄 Running batch health checks...');
|
||||||
|
console.log(' Batch size: 5 concurrent checks');
|
||||||
|
console.log(' Delay between batches: 1000ms\n');
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
await performBatchHealthCheck(toolIds, 'backfill', 5);
|
||||||
|
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
const durationSeconds = (duration / 1000).toFixed(2);
|
||||||
|
|
||||||
|
console.log('\n✅ Backfill complete!');
|
||||||
|
console.log(` Total tools checked: ${tools.length}`);
|
||||||
|
console.log(` Duration: ${durationSeconds}s`);
|
||||||
|
console.log(` Average time per tool: ${(duration / tools.length / 1000).toFixed(2)}s\n`);
|
||||||
|
|
||||||
|
// Show summary of health status
|
||||||
|
console.log('📊 Health Status Summary:');
|
||||||
|
const healthSummary = await prisma.tool.groupBy({
|
||||||
|
by: ['importHealth', 'executionHealth'],
|
||||||
|
_count: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\nImport Health:');
|
||||||
|
const importHealthCounts = await prisma.tool.groupBy({
|
||||||
|
by: ['importHealth'],
|
||||||
|
_count: true,
|
||||||
|
});
|
||||||
|
for (const row of importHealthCounts) {
|
||||||
|
console.log(` ${row.importHealth || 'UNKNOWN'}: ${row._count}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\nExecution Health:');
|
||||||
|
const executionHealthCounts = await prisma.tool.groupBy({
|
||||||
|
by: ['executionHealth'],
|
||||||
|
_count: true,
|
||||||
|
});
|
||||||
|
for (const row of executionHealthCounts) {
|
||||||
|
console.log(` ${row.executionHealth || 'UNKNOWN'}: ${row._count}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show broken tools
|
||||||
|
const brokenTools = await prisma.tool.findMany({
|
||||||
|
where: {
|
||||||
|
OR: [{ importHealth: 'BROKEN' }, { executionHealth: 'BROKEN' }],
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
exportName: true,
|
||||||
|
package: {
|
||||||
|
select: {
|
||||||
|
npmPackageName: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importHealth: true,
|
||||||
|
executionHealth: true,
|
||||||
|
healthCheckError: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (brokenTools.length > 0) {
|
||||||
|
console.log(`\n⚠️ Broken Tools (${brokenTools.length}):`);
|
||||||
|
for (const tool of brokenTools) {
|
||||||
|
console.log(
|
||||||
|
` - ${tool.package.npmPackageName}/${tool.exportName} (Import: ${tool.importHealth}, Execution: ${tool.executionHealth})`
|
||||||
|
);
|
||||||
|
if (tool.healthCheckError) {
|
||||||
|
console.log(` Error: ${tool.healthCheckError.slice(0, 100)}...`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('\n✅ All tools are healthy!');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ Backfill failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
@ -21,10 +21,6 @@
|
||||||
{
|
{
|
||||||
"path": "/api/sync/metrics",
|
"path": "/api/sync/metrics",
|
||||||
"schedule": "0 * * * *"
|
"schedule": "0 * * * *"
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/api/sync/health-check",
|
|
||||||
"schedule": "0 2 * * *"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue