Check for Tool Updates #148
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Check for Tool Updates | |
| on: | |
| schedule: | |
| - cron: "0 0 * * *" # Daily at midnight UTC | |
| workflow_dispatch: # Manual trigger | |
| jobs: | |
| check-updates: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| actions: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| - name: Check for updates | |
| id: check | |
| env: | |
| SUPABASE_URL: ${{ secrets.SUPABASE_URL }} | |
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} | |
| run: | | |
| UPDATES_FOUND=false | |
| rm -f updates.txt | |
| # Fetch tools from Supabase | |
| HTTP_CODE=$(curl -s -w "%{http_code}" -o /tmp/tools_response.json "$SUPABASE_URL/rest/v1/tools?select=packagename,version" \ | |
| -H "apikey: $SUPABASE_SERVICE_ROLE_KEY" \ | |
| -H "Authorization: Bearer $SUPABASE_SERVICE_ROLE_KEY") | |
| TOOLS_RESPONSE=$(cat /tmp/tools_response.json) | |
| # Check HTTP status code | |
| if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then | |
| echo "❌ Failed to fetch tools from Supabase (HTTP $HTTP_CODE)" | |
| echo "Response: $TOOLS_RESPONSE" | |
| exit 1 | |
| fi | |
| # Check if response is valid JSON array | |
| if ! echo "$TOOLS_RESPONSE" | jq -e 'type == "array"' > /dev/null 2>&1; then | |
| echo "❌ Invalid response from Supabase - expected JSON array" | |
| echo "Response: $TOOLS_RESPONSE" | |
| exit 1 | |
| fi | |
| # For each tool in Supabase | |
| for row in $(echo "$TOOLS_RESPONSE" | jq -r '.[] | @base64'); do | |
| _jq() { | |
| echo ${row} | base64 --decode | jq -r "${1}" | |
| } | |
| NPM_PACKAGE=$(_jq '.packagename') | |
| CURRENT_VERSION=$(_jq '.version') | |
| # Skip if packagename is null or empty | |
| if [ -z "$NPM_PACKAGE" ] || [ "$NPM_PACKAGE" = "null" ]; then | |
| continue | |
| fi | |
| # Get latest npm version | |
| LATEST_VERSION=$(npm view $NPM_PACKAGE version 2>/dev/null || echo "") | |
| if [ -z "$LATEST_VERSION" ]; then | |
| echo "⚠️ Could not fetch version for $NPM_PACKAGE" | |
| continue | |
| fi | |
| # Compare versions | |
| if [ "$LATEST_VERSION" != "$CURRENT_VERSION" ]; then | |
| NPM_INFO=$(npm view "$NPM_PACKAGE@$LATEST_VERSION" --json 2>/dev/null || echo '{}') | |
| # Retrieve update-time metadata from the new package version. | |
| CSP_JSON=$(echo "$NPM_INFO" | jq -c '.csp_exceptions // .cspexception // .configurations.csp_exceptions // null') | |
| FEATURES_JSON=$(echo "$NPM_INFO" | jq -c '.features // .configurations.features // null') | |
| # Preserve structured JSON safely in line-oriented temp files. | |
| CSP_B64=$(printf '%s' "$CSP_JSON" | base64 | tr -d '\n') | |
| FEATURES_B64=$(printf '%s' "$FEATURES_JSON" | base64 | tr -d '\n') | |
| echo "🔄 Update available for $NPM_PACKAGE: $CURRENT_VERSION → $LATEST_VERSION" | |
| echo "$NPM_PACKAGE,$CURRENT_VERSION,$LATEST_VERSION,$CSP_B64,$FEATURES_B64" >> updates.txt | |
| UPDATES_FOUND=true | |
| fi | |
| done | |
| echo "found=$UPDATES_FOUND" >> $GITHUB_OUTPUT | |
| - name: Create tool_updates record in Supabase | |
| if: steps.check.outputs.found == 'true' | |
| env: | |
| SUPABASE_URL: ${{ secrets.SUPABASE_URL }} | |
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} | |
| run: | | |
| # Remove any stale mapping file so this run controls what gets processed | |
| rm -f tool_update_ids.txt | |
| while IFS=',' read -r PACKAGE CURRENT_VERSION LATEST_VERSION CSP_B64 FEATURES_B64; do | |
| if [ -z "$PACKAGE" ]; then | |
| continue | |
| fi | |
| if [ -z "$LATEST_VERSION" ]; then | |
| continue | |
| fi | |
| PACKAGE_FILTER=$(printf '%s' "$PACKAGE" | jq -sRr @uri) | |
| VERSION_FILTER=$(printf '%s' "$LATEST_VERSION" | jq -sRr @uri) | |
| EXISTING_RESPONSE=$(curl -sS "$SUPABASE_URL/rest/v1/tool_updates?select=id&package_name=eq.$PACKAGE_FILTER&version=eq.$VERSION_FILTER&limit=1" \ | |
| -H "apikey: $SUPABASE_SERVICE_ROLE_KEY" \ | |
| -H "Authorization: Bearer $SUPABASE_SERVICE_ROLE_KEY") | |
| if echo "$EXISTING_RESPONSE" | jq -e 'type == "array" and length > 0' > /dev/null 2>&1; then | |
| EXISTING_ID=$(echo "$EXISTING_RESPONSE" | jq -r '.[0].id // empty') | |
| if [ -n "$EXISTING_ID" ]; then | |
| echo "tool_updates already exists for $PACKAGE@$LATEST_VERSION (id=$EXISTING_ID); enqueueing for processing." | |
| echo "$PACKAGE,$EXISTING_ID,$CSP_B64,$FEATURES_B64" >> tool_update_ids.txt | |
| else | |
| echo "Warning: tool_updates exists for $PACKAGE@$LATEST_VERSION but no id was returned; skipping enqueue." | |
| fi | |
| continue | |
| fi | |
| RESPONSE=$(curl -sS -X POST "${{ secrets.SUPABASE_URL }}/rest/v1/tool_updates" \ | |
| -H "apikey: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}" \ | |
| -H "Authorization: Bearer ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}" \ | |
| -H "Content-Type: application/json" \ | |
| -H "Prefer: resolution=merge-duplicates,return=representation" \ | |
| -d "{\"package_name\":\"$PACKAGE\",\"version\":\"$LATEST_VERSION\"}") | |
| echo "Supabase tools response for $PACKAGE: $RESPONSE" | |
| # Check if response is an error object | |
| if echo "$RESPONSE" | jq -e '.code' > /dev/null 2>&1; then | |
| echo "❌ Error creating tool_updates record for $PACKAGE: $RESPONSE" | |
| continue | |
| fi | |
| # Extract the inserted id (Supabase returns an array when using return=representation) | |
| TOOL_UPDATES_ID=$(echo "$RESPONSE" | jq -r 'if type == "array" then .[0].id else .id end // empty') | |
| # Persist mapping for the next step | |
| if [ -n "$TOOL_UPDATES_ID" ]; then | |
| echo "$PACKAGE,$TOOL_UPDATES_ID,$CSP_B64,$FEATURES_B64" >> tool_update_ids.txt | |
| else | |
| echo "Warning: No tool_updates id returned for $PACKAGE" | |
| fi | |
| done < updates.txt | |
| - name: Invoke NextJS Update Tool API | |
| if: steps.check.outputs.found == 'true' | |
| env: | |
| UPDATE_TOOL_API_URL: ${{ secrets.UPDATE_TOOL_API_URL }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "$UPDATE_TOOL_API_URL" ]; then | |
| echo "❌ Secret UPDATE_TOOL_API_URL is not set." | |
| exit 1 | |
| fi | |
| # Iterate over created tool_update ids and notify API per package | |
| if [ ! -f tool_update_ids.txt ]; then | |
| echo "No tool_update_ids.txt found; skipping API notifications." | |
| exit 0 | |
| fi | |
| while IFS=',' read -r PACKAGE TOOL_UPDATES_ID CSP_B64 FEATURES_B64; do | |
| if [ -z "$PACKAGE" ] || [ -z "$TOOL_UPDATES_ID" ]; then | |
| continue | |
| fi | |
| CSP_JSON=$(printf '%s' "$CSP_B64" | base64 --decode) | |
| FEATURES_JSON=$(printf '%s' "$FEATURES_B64" | base64 --decode) | |
| PAYLOAD=$(jq -c -n \ | |
| --arg packageName "$PACKAGE" \ | |
| --arg toolUpdateId "$TOOL_UPDATES_ID" \ | |
| --argjson csp_exceptions "$CSP_JSON" \ | |
| --argjson features "$FEATURES_JSON" \ | |
| '{ | |
| packageName: $packageName, | |
| toolUpdateId: $toolUpdateId, | |
| cspExceptions: $csp_exceptions, | |
| features: $features | |
| }') | |
| echo "Queuing update API notification for $PACKAGE (tool_update_id=$TOOL_UPDATES_ID)" | |
| # Fire-and-forget: do not wait for the HTTP response. | |
| # Notes: | |
| # - We still set small timeouts to avoid hangs if DNS/TLS stalls. | |
| # - Output is discarded to keep logs clean. | |
| nohup curl -sS -L --post301 --post302 --post303 \ | |
| --connect-timeout 5 \ | |
| --max-time 15 \ | |
| -X POST "$UPDATE_TOOL_API_URL" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$PAYLOAD" \ | |
| >/dev/null 2>&1 & | |
| done < tool_update_ids.txt | |
| # Give background curls a moment to start before the step exits. | |
| sleep 5 |