diff --git a/.github/workflows/ComfyUIdownloads.yml b/.github/workflows/ComfyUIdownloads.yml
index 1f0d27b..7d2699f 100644
--- a/.github/workflows/ComfyUIdownloads.yml
+++ b/.github/workflows/ComfyUIdownloads.yml
@@ -1,4 +1,14 @@
-name: Model Resolver Top Downloads Badge
+# Reusable workflow
+#
+# Project metadata is read from pyproject.toml and the Comfy Registry API.
+# No project-specific repository variables need to be configured manually.
+# The workflow creates and maintains this internal repository variable automatically:
+# COMFY_DOWNLOADS_GIST_ID
+#
+# Required repository secret:
+# SECRET_TOKEN GitHub token with Gist and repository Actions variables write access
+
+name: ComfyUI Top Downloads Badge
on:
schedule:
@@ -8,6 +18,12 @@ on:
permissions:
contents: write
+env:
+ COMFY_DOWNLOADS_GIST_ID: ${{ vars.COMFY_DOWNLOADS_GIST_ID }}
+ BADGE_DOC: badges/DOWNLOADS.md
+ DATA_FILE: comfyui_downloads.json
+ GH_TOKEN: ${{ secrets.SECRET_TOKEN }}
+
jobs:
build:
runs-on: ubuntu-latest
@@ -15,39 +31,67 @@ jobs:
steps:
- uses: actions/checkout@v7
- - name: gh login
- run: echo "${{ secrets.SECRET_TOKEN }}" | gh auth login --with-token
+ - name: Read project metadata and validate authentication
+ id: project
+ run: |
+ if [ -z "$GH_TOKEN" ]; then
+ echo "::error title=Missing SECRET_TOKEN::Add a GitHub token with Gist and repository Actions variables write access."
+ exit 1
+ fi
- - name: Query Model Resolver API and find top download
+ gh auth status
+
+ python - <<'PY' >> "$GITHUB_OUTPUT"
+ from pathlib import Path
+ import tomllib
+
+ path = Path("pyproject.toml")
+ if not path.is_file():
+ raise SystemExit("pyproject.toml was not found")
+
+ with path.open("rb") as file:
+ config = tomllib.load(file)
+
+ node_id = str(config.get("project", {}).get("name", "")).strip()
+ if not node_id:
+ raise SystemExit("[project].name is missing in pyproject.toml")
+
+ print(f"node_id={node_id}")
+ PY
+
+ - name: Query Comfy Registry API and find top download
+ env:
+ COMFY_NODE_ID: ${{ steps.project.outputs.node_id }}
run: |
max_downloads=0
found_valid_data=false
top_node_json="{}"
- node_id="Comfyui-Model-Resolver"
for i in {1..3}; do
echo "Fetching data for attempt $i..."
+ response_file="registry_response_$i.json"
+
if ! curl -fsSL --retry 3 --retry-delay 2 --retry-all-errors \
-H "Accept: application/json" \
- "https://api.comfy.org/nodes/${node_id}" > tmp_$i.json; then
+ "https://api.comfy.org/nodes/${COMFY_NODE_ID}" > "$response_file"; then
echo "Error: request failed for attempt $i"
- rm -f tmp_$i.json
+ rm -f "$response_file"
continue
fi
- if [ ! -s tmp_$i.json ] || ! jq empty tmp_$i.json 2>/dev/null; then
+ if [ ! -s "$response_file" ] || ! jq empty "$response_file" 2>/dev/null; then
echo "Error: invalid JSON for attempt $i"
- rm -f tmp_$i.json
+ rm -f "$response_file"
continue
fi
- if jq -e 'type == "array"' tmp_$i.json >/dev/null; then
- node_count=$(jq 'length' tmp_$i.json)
+ if jq -e 'type == "array"' "$response_file" >/dev/null; then
+ node_count=$(jq 'length' "$response_file")
echo "Found $node_count nodes in attempt $i"
- for j in $(seq 0 $((node_count - 1))); do
- downloads=$(jq -r ".[$j].downloads // 0" tmp_$i.json)
- name=$(jq -r ".[$j].name // \"\"" tmp_$i.json)
+ while IFS= read -r node_json; do
+ downloads=$(jq -r '.downloads // 0' <<< "$node_json")
+ name=$(jq -r '.name // ""' <<< "$node_json")
if ! [[ "$downloads" =~ ^[0-9]+$ ]]; then
downloads=0
@@ -56,13 +100,13 @@ jobs:
if [ "$found_valid_data" = false ] || [ "$downloads" -gt "$max_downloads" ]; then
found_valid_data=true
max_downloads=$downloads
- top_node_json=$(jq ".[$j]" tmp_$i.json)
+ top_node_json=$node_json
echo "New maximum found: $downloads (node: $name)"
fi
- done
+ done < <(jq -c '.[]' "$response_file")
else
- downloads=$(jq -r '.downloads // 0' tmp_$i.json)
- name=$(jq -r '.name // ""' tmp_$i.json)
+ downloads=$(jq -r '.downloads // 0' "$response_file")
+ name=$(jq -r '.name // ""' "$response_file")
if ! [[ "$downloads" =~ ^[0-9]+$ ]]; then
downloads=0
@@ -71,126 +115,206 @@ jobs:
if [ "$found_valid_data" = false ] || [ "$downloads" -gt "$max_downloads" ]; then
found_valid_data=true
max_downloads=$downloads
- top_node_json=$(cat tmp_$i.json)
+ top_node_json=$(jq -c . "$response_file")
echo "New maximum found: $downloads (node: $name)"
fi
fi
- rm -f tmp_$i.json
+ rm -f "$response_file"
done
- if [ "$found_valid_data" = true ]; then
- echo "$top_node_json" > top_modelresolver.json
- echo "Highest download count: $max_downloads"
- echo "Node details:"
- jq . top_modelresolver.json
- else
+ if [ "$found_valid_data" = false ]; then
echo "Error: no valid data found"
exit 1
fi
- - name: create or update gist with top download
- id: set_id
- run: |
- BADGE_DOC="badges/DOWNLOADS.md"
+ jq . <<< "$top_node_json" > "$DATA_FILE"
+ echo "Highest download count: $max_downloads"
+ jq . "$DATA_FILE"
- if gh secret list | grep -q "MODELRESOLVER_GIST_ID"
- then
- echo "GIST_ID found"
- echo "GIST=${{ secrets.MODELRESOLVER_GIST_ID }}" >> $GITHUB_OUTPUT
+ display_name=$(jq -r '.name // .id // empty' "$DATA_FILE")
+ publisher_id=$(jq -r '.publisher.id // empty' "$DATA_FILE")
+ node_slug=$(jq -r '(.id // empty) | ascii_downcase' "$DATA_FILE")
- if gh gist view ${{ secrets.MODELRESOLVER_GIST_ID }} &>/dev/null; then
- echo "Gist exists and will be updated"
- else
- echo "Gist does not exist, creating a new one"
- gist_id=$(gh gist create top_modelresolver.json | awk -F / '{print $NF}')
- echo $gist_id | gh secret set MODELRESOLVER_GIST_ID
- echo "GIST=$gist_id" >> $GITHUB_OUTPUT
- git rm --ignore-unmatch "$BADGE_DOC" MODELRESOLVER.md
- fi
- else
- echo "Creating a new gist"
- gist_id=$(gh gist create top_modelresolver.json | awk -F / '{print $NF}')
- echo $gist_id | gh secret set MODELRESOLVER_GIST_ID
- echo "GIST=$gist_id" >> $GITHUB_OUTPUT
- fi
-
- - name: create badge if needed
- run: |
- COUNT=$(jq '.downloads' top_modelresolver.json)
- NAME=$(jq -r '.name' top_modelresolver.json)
- BADGE_DOC="badges/DOWNLOADS.md"
-
- if [ ! -f "$BADGE_DOC" ] || ! grep -q "Combined HTML (for-the-badge)" "$BADGE_DOC"; then
- mkdir -p badges
- shields="https://img.shields.io/badge/dynamic/json?color=informational&label=TopModelResolver&query=downloads&url="
- shields_for_the_badge="https://img.shields.io/badge/dynamic/json?color=2F80ED&label=TopModelResolver&query=downloads&url="
- comfy_badge="https://img.shields.io/badge/ComfyUI-1a1a1a?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAASFBMVEVHcEwYLtsYLtkXLtkXLdkYLtkWLdcFIdoAD95uerfI1XLR3mq3xIP8/yj0/zvw/0FSYMP5/zKMmKQtPNOuuozj8FOhrZW7x4FMWFFbAAAABnRSTlMAUrPX87KxijklAAAA00lEQVR4AX3SBw6DMAxA0UzbrIzO+9+02GkEpoWP9hPZZs06Hw75aI3k4W/+wkQtnGZNhF1I34BzalQcxkmasY0b9raklNcvLYU1GNiiOeVWauOa/XS526gRyzpV/7HeUOG9Jp6vcsvUrCPeKg/3KBKBQhoTD1dQggPWzPVfFOIgo85/kR4y6oB/8SlIEh7wvmTuKd3wgLVW1sTfRBoR7oWVqy/U2NcrWDYMINE7NUuJuoV+2fhaWmnbjzcOWnRv7XbiLh/Y9dNUqk2y0QcNwTu7wgf+/BhsPUhf4QAAAABJRU5ErkJggg=="
- combined_downloads="https://img.shields.io/badge/dynamic/json?color=%230D2A4A&label=&query=downloads&url="
- url="https://gist.githubusercontent.com/${{ github.actor }}/${{ steps.set_id.outputs.GIST }}/raw/top_modelresolver.json"
- repo="https://registry.comfy.org/publishers/azornes/nodes/comfyui-model-resolver"
- echo ''> "$BADGE_DOC"
- echo '
- **Markdown**
-
- ```markdown' >> "$BADGE_DOC"
- echo "[]($repo)" >> "$BADGE_DOC"
- echo '
- ```' >> "$BADGE_DOC"
- echo "[]($repo)" >> "$BADGE_DOC"
- echo '
-
- **HTML**
- ```html' >> "$BADGE_DOC"
- echo "
" >> "$BADGE_DOC"
- echo '
- ```' >> "$BADGE_DOC"
- echo "
" >> "$BADGE_DOC"
- echo '
-
- **HTML (for-the-badge)**
- ```html' >> "$BADGE_DOC"
- echo "
" >> "$BADGE_DOC"
- echo '```' >> "$BADGE_DOC"
- echo "
" >> "$BADGE_DOC"
- echo '
-
- **Combined Markdown (for-the-badge)**
- ```markdown' >> "$BADGE_DOC"
- echo "[]($repo)" >> "$BADGE_DOC"
- echo '
- ```' >> "$BADGE_DOC"
- echo "[]($repo)" >> "$BADGE_DOC"
- echo '
-
- **Combined HTML (for-the-badge)**
- ```html' >> "$BADGE_DOC"
- echo "
" >> "$BADGE_DOC"
- echo '```' >> "$BADGE_DOC"
- echo "
" >> "$BADGE_DOC"
-
- git add "$BADGE_DOC"
- git config --global user.name "GitHub Action"
- git config --global user.email "action@github.com"
- git commit -m "Create or update Model Resolver downloads badge"
- fi
-
- - name: Update Gist
- run: |
- if jq empty top_modelresolver.json 2>/dev/null; then
- content=$(jq -c . top_modelresolver.json)
- echo "{\"description\": \"Top Model Resolver Node\", \"files\": {\"top_modelresolver.json\": {\"content\": $(jq -Rs . <<< "$content")}}}" > patch.json
-
- curl -s -X PATCH \
- --user "${{ github.actor }}:${{ secrets.SECRET_TOKEN }}" \
- -H "Content-Type: application/json" \
- -d @patch.json https://api.github.com/gists/${{ steps.set_id.outputs.GIST }}
- else
- echo "Error: invalid JSON in top_modelresolver.json"
+ if [ -z "$display_name" ] || [ -z "$publisher_id" ] || [ -z "$node_slug" ]; then
+ echo "::error title=Incomplete Registry response::The node name, publisher ID, or node ID is missing."
exit 1
fi
- - name: Push
- uses: ad-m/github-push-action@master
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
+ {
+ echo "COMFY_NODE_DISPLAY_NAME=$display_name"
+ echo "COMFY_PUBLISHER_ID=$publisher_id"
+ echo "COMFY_NODE_SLUG=$node_slug"
+ } >> "$GITHUB_ENV"
+
+ - name: Create or reuse downloads Gist
+ id: gist
+ run: |
+ gist_id="$COMFY_DOWNLOADS_GIST_ID"
+ save_gist_id=false
+
+ if [ -z "$gist_id" ]; then
+ gist_id=$(python - <<'PY'
+ from pathlib import Path
+ import os
+ import re
+
+ pattern = re.compile(
+ r"query=downloads.*?url=https://gist\.githubusercontent\.com/[^/]+/([0-9a-f]+)",
+ re.IGNORECASE,
+ )
+
+ for filename in (os.environ["BADGE_DOC"], "README.md"):
+ path = Path(filename)
+ if not path.is_file():
+ continue
+ match = pattern.search(path.read_text(encoding="utf-8"))
+ if match:
+ print(match.group(1))
+ break
+ PY
+ )
+
+ if [ -n "$gist_id" ]; then
+ echo "Found an existing downloads Gist in the badge documentation: $gist_id"
+ save_gist_id=true
+ fi
+ fi
+
+ if [ -n "$gist_id" ] && gh gist view "$gist_id" >/dev/null 2>&1; then
+ echo "Using existing Gist: $gist_id"
+ else
+ if [ -n "$gist_id" ]; then
+ echo "Configured Gist does not exist or is inaccessible; creating a new one."
+ else
+ echo "No Gist is configured; creating one."
+ fi
+
+ gist_url=$(gh gist create "$DATA_FILE" \
+ --desc "ComfyUI downloads for $COMFY_NODE_DISPLAY_NAME")
+ gist_id="${gist_url##*/}"
+ save_gist_id=true
+ fi
+
+ if [ "$save_gist_id" = true ]; then
+ if ! gh variable set COMFY_DOWNLOADS_GIST_ID --body "$gist_id"; then
+ echo "::error title=Cannot save Gist ID::Grant SECRET_TOKEN repository Actions variables write access."
+ exit 1
+ fi
+
+ echo "Saved COMFY_DOWNLOADS_GIST_ID=$gist_id"
+ fi
+
+ gist_filename=$(gh api "gists/$gist_id" --jq '
+ .files
+ | keys
+ | if index("comfyui_downloads.json") then
+ "comfyui_downloads.json"
+ else
+ (map(select(endswith(".json"))) | first // "comfyui_downloads.json")
+ end
+ ')
+ gist_owner=$(gh api user --jq '.login')
+ echo "id=$gist_id" >> "$GITHUB_OUTPUT"
+ echo "filename=$gist_filename" >> "$GITHUB_OUTPUT"
+ echo "owner=$gist_owner" >> "$GITHUB_OUTPUT"
+
+ - name: Create or update badge document
+ id: badge
+ env:
+ GIST_ID: ${{ steps.gist.outputs.id }}
+ GIST_FILENAME: ${{ steps.gist.outputs.filename }}
+ GIST_OWNER: ${{ steps.gist.outputs.owner }}
+ run: |
+ mkdir -p "$(dirname "$BADGE_DOC")"
+
+ badge_label=$(jq -rn --arg value "$COMFY_NODE_DISPLAY_NAME" '$value | @uri')
+ registry_url="https://registry.comfy.org/publishers/${COMFY_PUBLISHER_ID}/nodes/${COMFY_NODE_SLUG}"
+ gist_url="https://gist.githubusercontent.com/${GIST_OWNER}/${GIST_ID}/raw/${GIST_FILENAME}"
+ shields="https://img.shields.io/badge/dynamic/json?color=informational&label=${badge_label}&query=downloads&url="
+ shields_for_the_badge="https://img.shields.io/badge/dynamic/json?color=2F80ED&label=${badge_label}&query=downloads&url="
+ comfy_badge="https://img.shields.io/badge/ComfyUI-1a1a1a?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAASFBMVEVHcEwYLtsYLtkXLtkXLdkYLtkWLdcFIdoAD95uerfI1XLR3mq3xIP8/yj0/zvw/0FSYMP5/zKMmKQtPNOuuozj8FOhrZW7x4FMWFFbAAAABnRSTlMAUrPX87KxijklAAAA00lEQVR4AX3SBw6DMAxA0UzbrIzO+9+02GkEpoWP9hPZZs06Hw75aI3k4W/+wkQtnGZNhF1I34BzalQcxkmasY0b9raklNcvLYU1GNiiOeVWauOa/XS526gRyzpV/7HeUOG9Jp6vcsvUrCPeKg/3KBKBQhoTD1dQggPWzPVfFOIgo85/kR4y6oB/8SlIEh7wvmTuKd3wgLVW1sTfRBoR7oWVqy/U2NcrWDYMINE7NUuJuoV+2fhaWmnbjzcOWnRv7XbiLh/Y9dNUqk2y0QcNwTu7wgf+/BhsPUhf4QAAAABJRU5ErkJggg=="
+ combined_downloads="https://img.shields.io/badge/dynamic/json?color=%230D2A4A&label=&query=downloads&url="
+
+ {
+ echo "**Markdown**"
+ echo
+ echo '```markdown'
+ echo "[](${registry_url})"
+ echo '```'
+ echo
+ echo "[](${registry_url})"
+ echo
+ echo "**HTML**"
+ echo
+ echo '```html'
+ echo "
"
+ echo '```'
+ echo
+ echo "
"
+ echo
+ echo "**HTML (for-the-badge)**"
+ echo
+ echo '```html'
+ echo "
"
+ echo '```'
+ echo
+ echo "
"
+ echo
+ echo "**Combined Markdown (for-the-badge)**"
+ echo
+ echo '```markdown'
+ echo "[](${registry_url})"
+ echo '```'
+ echo
+ echo "[](${registry_url})"
+ echo
+ echo "**Combined HTML (for-the-badge)**"
+ echo
+ echo '```html'
+ echo "
"
+ echo '```'
+ echo
+ echo "
"
+ } > "$BADGE_DOC"
+
+ if [ -n "$(git status --porcelain -- "$BADGE_DOC")" ]; then
+ git add "$BADGE_DOC"
+ git config user.name "GitHub Action"
+ git config user.email "action@github.com"
+ git commit -m "Create or update ComfyUI downloads badge"
+ echo "changed=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "Badge document is already up to date."
+ echo "changed=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Update Gist
+ env:
+ GIST_ID: ${{ steps.gist.outputs.id }}
+ GIST_FILENAME: ${{ steps.gist.outputs.filename }}
+ run: |
+ if ! jq empty "$DATA_FILE" 2>/dev/null; then
+ echo "Error: invalid JSON in $DATA_FILE"
+ exit 1
+ fi
+
+ content=$(jq -c . "$DATA_FILE")
+ jq -n \
+ --arg description "ComfyUI downloads for $COMFY_NODE_DISPLAY_NAME" \
+ --arg filename "$GIST_FILENAME" \
+ --arg content "$content" \
+ '{
+ description: $description,
+ files: {
+ ($filename): {
+ content: $content
+ }
+ }
+ }' > gist_patch.json
+
+ gh api --method PATCH "gists/${GIST_ID}" --input gist_patch.json >/dev/null
+
+ - name: Push badge document
+ if: steps.badge.outputs.changed == 'true'
+ run: git push