fix: make example field optional and fix Discord webhook JSON escaping

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 <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-12-03 08:32:14 +10:00
parent da12d1fc5b
commit 484190720a
2 changed files with 73 additions and 64 deletions

View file

@ -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"

View file

@ -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'