From bf8d27a59c90c6b47f4b696c98265ee92ca3ca5f Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Thu, 4 Dec 2025 16:36:55 +1000 Subject: [PATCH] feat: add GitHub Action for daily health check cron and backfill script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .github/workflows/health-check.yml | 18 ++++ scripts/backfill-health-checks.ts | 133 +++++++++++++++++++++++++++++ vercel.json | 4 - 3 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/health-check.yml create mode 100644 scripts/backfill-health-checks.ts diff --git a/.github/workflows/health-check.yml b/.github/workflows/health-check.yml new file mode 100644 index 0000000..ddf2a15 --- /dev/null +++ b/.github/workflows/health-check.yml @@ -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" diff --git a/scripts/backfill-health-checks.ts b/scripts/backfill-health-checks.ts new file mode 100644 index 0000000..87cfb2c --- /dev/null +++ b/scripts/backfill-health-checks.ts @@ -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(); diff --git a/vercel.json b/vercel.json index 6543ce1..2eb65e2 100644 --- a/vercel.json +++ b/vercel.json @@ -21,10 +21,6 @@ { "path": "/api/sync/metrics", "schedule": "0 * * * *" - }, - { - "path": "/api/sync/health-check", - "schedule": "0 2 * * *" } ] }