From 484190720a197a4a8f1b342fddb5eb2aeb7949ee Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Wed, 3 Dec 2025 08:32:14 +1000 Subject: [PATCH] fix: make example field optional and fix Discord webhook JSON escaping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Database Changes: - Made `example` field optional in Prisma schema (String?) - Added default empty arrays for `frameworks` and `tags` - Allows tools to be synced without example field Workflow Changes: - Use jq to properly construct Discord webhook JSON - Fixes "invalid JSON" error caused by unescaped special characters - Properly escapes error messages with newlines and quotes This fixes sync errors for packages missing the example field and ensures Discord notifications are sent successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/sync-keyword.yml | 113 ++++++++++++++++------------- packages/db/prisma/schema.prisma | 24 +++--- 2 files changed, 73 insertions(+), 64 deletions(-) diff --git a/.github/workflows/sync-keyword.yml b/.github/workflows/sync-keyword.yml index 9fea0c6..5c93293 100644 --- a/.github/workflows/sync-keyword.yml +++ b/.github/workflows/sync-keyword.yml @@ -72,60 +72,69 @@ jobs: # Format duration duration_sec=$(echo "scale=2; ${{ steps.sync.outputs.durationMs }} / 1000" | bc) - # Build base fields - fields='[ - { - "name": "📦 Packages Found", - "value": "${{ steps.sync.outputs.packagesFound }}", - "inline": true - }, - { - "name": "✨ Processed", - "value": "${{ steps.sync.outputs.processed }}", - "inline": true - }, - { - "name": "⏭️ Skipped", - "value": "${{ steps.sync.outputs.skipped }}", - "inline": true - }, - { - "name": "❌ Errors", - "value": "${{ steps.sync.outputs.errors }}", - "inline": true - }, - { - "name": "⏱️ Duration", - "value": "'"$duration_sec"'s", - "inline": true - }, - { - "name": "🔗 Run", - "value": "[View Logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})", - "inline": true - }' - - # Add error details if errors exist + # Build Discord payload using jq for proper JSON escaping if [ -f /tmp/error_summary.txt ] && [ ${{ steps.sync.outputs.errors }} -gt 0 ]; then - error_text=$(cat /tmp/error_summary.txt | head -c 900) - fields="$fields"', - { - "name": "🔍 Error Details", - "value": "```\n'"$error_text"'\n```", - "inline": false - }' + # Read and truncate error text + error_text=$(cat /tmp/error_summary.txt | head -c 800) + + # Create payload with error details using jq + payload=$(jq -n \ + --arg title "${{ steps.sync.outputs.status_emoji }} NPM Keyword Search Sync" \ + --argjson color ${{ steps.sync.outputs.status_color }} \ + --arg packages "${{ steps.sync.outputs.packagesFound }}" \ + --arg processed "${{ steps.sync.outputs.processed }}" \ + --arg skipped "${{ steps.sync.outputs.skipped }}" \ + --arg errors "${{ steps.sync.outputs.errors }}" \ + --arg duration "${duration_sec}s" \ + --arg url "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ + --arg error_text "$error_text" \ + --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \ + '{ + embeds: [{ + title: $title, + color: $color, + fields: [ + { name: "📦 Packages Found", value: $packages, inline: true }, + { name: "✨ Processed", value: $processed, inline: true }, + { name: "⏭️ Skipped", value: $skipped, inline: true }, + { name: "❌ Errors", value: $errors, inline: true }, + { name: "⏱️ Duration", value: $duration, inline: true }, + { name: "🔗 Run", value: ("[View Logs](" + $url + ")"), inline: true }, + { name: "🔍 Error Details", value: ("```\n" + $error_text + "\n```"), inline: false } + ], + timestamp: $timestamp + }] + }') + else + # Create payload without error details + payload=$(jq -n \ + --arg title "${{ steps.sync.outputs.status_emoji }} NPM Keyword Search Sync" \ + --argjson color ${{ steps.sync.outputs.status_color }} \ + --arg packages "${{ steps.sync.outputs.packagesFound }}" \ + --arg processed "${{ steps.sync.outputs.processed }}" \ + --arg skipped "${{ steps.sync.outputs.skipped }}" \ + --arg errors "${{ steps.sync.outputs.errors }}" \ + --arg duration "${duration_sec}s" \ + --arg url "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ + --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \ + '{ + embeds: [{ + title: $title, + color: $color, + fields: [ + { name: "📦 Packages Found", value: $packages, inline: true }, + { name: "✨ Processed", value: $processed, inline: true }, + { name: "⏭️ Skipped", value: $skipped, inline: true }, + { name: "❌ Errors", value: $errors, inline: true }, + { name: "⏱️ Duration", value: $duration, inline: true }, + { name: "🔗 Run", value: ("[View Logs](" + $url + ")"), inline: true } + ], + timestamp: $timestamp + }] + }') fi - fields="$fields"']' - - # Create Discord embed + # Send to Discord curl -X POST "${{ secrets.DISCORD_WEBHOOK }}" \ -H "Content-Type: application/json" \ - -d '{ - "embeds": [{ - "title": "${{ steps.sync.outputs.status_emoji }} NPM Keyword Search Sync", - "color": ${{ steps.sync.outputs.status_color }}, - "fields": '"$fields"', - "timestamp": "'"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)"'" - }] - }' + -d "$payload" diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 27bd3cb..103dc13 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -28,18 +28,18 @@ model Tool { npmMaintainers Json? @map("npm_maintainers") @db.JsonB // TPMJS Metadata (from package.json tpmjs field) - category String @db.VarChar(50) - description String @db.Text - example String @db.Text - parameters Json? @db.JsonB - returns Json? @db.JsonB - authentication Json? @db.JsonB - pricing Json? @db.JsonB - frameworks String[] @db.Text - links Json? @db.JsonB - tags String[] @db.Text - status String? @db.VarChar(20) - aiAgent Json? @map("ai_agent") @db.JsonB + category String @db.VarChar(50) + description String @db.Text + example String? @db.Text + parameters Json? @db.JsonB + returns Json? @db.JsonB + authentication Json? @db.JsonB + pricing Json? @db.JsonB + frameworks String[] @default([]) @db.Text + links Json? @db.JsonB + tags String[] @default([]) @db.Text + status String? @db.VarChar(20) + aiAgent Json? @map("ai_agent") @db.JsonB // Discovery Metadata discoveryMethod String @map("discovery_method") @db.VarChar(20) // 'keyword' | 'changes-feed'