Make GitHub workflows reusable and validate inputs dynamically
- Replace hardcoded repository and token checks with generic default-branch and pyproject.toml validation - Require fine-grained PAT permissions and validate Gist ownership and description - Add concurrency groups to badge update workflows - Bump softprops/action-gh-release to v3 and use DisplayName from pyproject.toml
This commit is contained in:
@@ -6,7 +6,11 @@
|
||||
# COMFY_DOWNLOADS_GIST_ID
|
||||
#
|
||||
# Required repository secret:
|
||||
# SECRET_TOKEN GitHub token with Gist and repository Actions variables write access
|
||||
# SECRET_TOKEN Fine-grained personal access token with access to this repository:
|
||||
# Repository permissions:
|
||||
# Variables: Read and write
|
||||
# Account permissions:
|
||||
# Gists: Read and write
|
||||
|
||||
name: ComfyUI Top Downloads Badge
|
||||
|
||||
@@ -18,11 +22,14 @@ on:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: badge-updates-${{ github.repository }}
|
||||
cancel-in-progress: false
|
||||
|
||||
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:
|
||||
@@ -33,9 +40,11 @@ jobs:
|
||||
|
||||
- name: Read project metadata and validate authentication
|
||||
id: project
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SECRET_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$GH_TOKEN" ]; then
|
||||
echo "::error title=Missing SECRET_TOKEN::Add a GitHub token with Gist and repository Actions variables write access."
|
||||
echo "::error title=Missing SECRET_TOKEN::Add a fine-grained PAT with Variables: Read and write and Gists: Read and write."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -149,9 +158,14 @@ jobs:
|
||||
|
||||
- name: Create or reuse downloads Gist
|
||||
id: gist
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SECRET_TOKEN }}
|
||||
run: |
|
||||
gist_id="$COMFY_DOWNLOADS_GIST_ID"
|
||||
expected_description="${GITHUB_REPOSITORY} ComfyUI download statistics"
|
||||
authenticated_user=$(gh api user --jq '.login')
|
||||
save_gist_id=false
|
||||
valid_gist=false
|
||||
|
||||
if [ -z "$gist_id" ]; then
|
||||
gist_id=$(python - <<'PY'
|
||||
@@ -182,6 +196,36 @@ jobs:
|
||||
fi
|
||||
|
||||
if [ -n "$gist_id" ] && gh gist view "$gist_id" >/dev/null 2>&1; then
|
||||
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 "gists/$gist_id" --jq '.owner.login')
|
||||
gist_description=$(gh api "gists/$gist_id" --jq '.description // ""')
|
||||
gist_node_slug=$(
|
||||
gh api "gists/$gist_id" |
|
||||
jq -r --arg filename "$gist_filename" '
|
||||
.files[$filename].content // ""
|
||||
| fromjson?
|
||||
| ((.id // empty) | ascii_downcase)
|
||||
'
|
||||
)
|
||||
|
||||
if [ "$gist_owner" = "$authenticated_user" ] &&
|
||||
{ [ "$gist_description" = "$expected_description" ] ||
|
||||
[ "$gist_node_slug" = "$COMFY_NODE_SLUG" ]; }; then
|
||||
valid_gist=true
|
||||
else
|
||||
echo "The detected Gist belongs to another repository, token owner, or ComfyUI node."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$valid_gist" = true ]; then
|
||||
echo "Using existing Gist: $gist_id"
|
||||
else
|
||||
if [ -n "$gist_id" ]; then
|
||||
@@ -191,7 +235,7 @@ jobs:
|
||||
fi
|
||||
|
||||
gist_url=$(gh gist create "$DATA_FILE" \
|
||||
--desc "ComfyUI downloads for $COMFY_NODE_DISPLAY_NAME")
|
||||
--desc "$expected_description")
|
||||
gist_id="${gist_url##*/}"
|
||||
save_gist_id=true
|
||||
fi
|
||||
@@ -214,7 +258,7 @@ jobs:
|
||||
(map(select(endswith(".json"))) | first // "comfyui_downloads.json")
|
||||
end
|
||||
')
|
||||
gist_owner=$(gh api user --jq '.login')
|
||||
gist_owner=$(gh api "gists/$gist_id" --jq '.owner.login')
|
||||
echo "id=$gist_id" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=$gist_filename" >> "$GITHUB_OUTPUT"
|
||||
echo "owner=$gist_owner" >> "$GITHUB_OUTPUT"
|
||||
@@ -291,6 +335,7 @@ jobs:
|
||||
|
||||
- name: Update Gist
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SECRET_TOKEN }}
|
||||
GIST_ID: ${{ steps.gist.outputs.id }}
|
||||
GIST_FILENAME: ${{ steps.gist.outputs.filename }}
|
||||
run: |
|
||||
@@ -301,7 +346,7 @@ jobs:
|
||||
|
||||
content=$(jq -c . "$DATA_FILE")
|
||||
jq -n \
|
||||
--arg description "ComfyUI downloads for $COMFY_NODE_DISPLAY_NAME" \
|
||||
--arg description "${GITHUB_REPOSITORY} ComfyUI download statistics" \
|
||||
--arg filename "$GIST_FILENAME" \
|
||||
--arg content "$content" \
|
||||
'{
|
||||
|
||||
+254
-78
@@ -1,110 +1,286 @@
|
||||
name: GitHub Clone Count Update Everyday
|
||||
# Reusable workflow
|
||||
#
|
||||
# No project-specific repository variables need to be configured manually.
|
||||
# The workflow creates and maintains this internal repository variable automatically:
|
||||
# COMFY_CLONES_GIST_ID
|
||||
#
|
||||
# Required repository secret:
|
||||
# SECRET_TOKEN Fine-grained personal access token with access to this repository:
|
||||
# Repository permissions:
|
||||
# Administration: Read
|
||||
# Variables: Read and write
|
||||
# Account permissions:
|
||||
# Gists: Read and write
|
||||
|
||||
name: Update GitHub Clone Count Badge
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */24 * * *"
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: badge-updates-${{ github.repository }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
BADGE_DOC: badges/CLONE.md
|
||||
CLONE_GIST_ID: ${{ vars.COMFY_CLONES_GIST_ID }}
|
||||
DATA_FILE: clone.json
|
||||
PREVIOUS_DATA_FILE: clone_before.json
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: gh login
|
||||
run: echo "${{ secrets.SECRET_TOKEN }}" | gh auth login --with-token
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: parse latest clone count
|
||||
- name: Validate authentication
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SECRET_TOKEN }}
|
||||
run: |
|
||||
curl --user "${{ github.actor }}:${{ secrets.SECRET_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/${{ github.repository }}/traffic/clones \
|
||||
> clone.json
|
||||
if [ -z "$GH_TOKEN" ]; then
|
||||
echo "::error title=Missing SECRET_TOKEN::Add a fine-grained PAT with Administration: Read, Variables: Read and write, and Gists: Read and write."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: create gist and download previous count
|
||||
id: set_id
|
||||
gh auth status
|
||||
|
||||
- name: Fetch latest clone traffic
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SECRET_TOKEN }}
|
||||
run: |
|
||||
BADGE_DOC="badges/CLONE.md"
|
||||
if ! gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"repos/${GITHUB_REPOSITORY}/traffic/clones" > "$DATA_FILE"; then
|
||||
echo "::error title=Cannot read clone traffic::SECRET_TOKEN needs Administration: Read access to this repository."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if gh secret list | grep -q "GIST_ID"
|
||||
then
|
||||
echo "GIST_ID found"
|
||||
echo "GIST=${{ secrets.GIST_ID }}" >> $GITHUB_OUTPUT
|
||||
curl https://gist.githubusercontent.com/${{ github.actor }}/${{ secrets.GIST_ID }}/raw/clone.json > clone_before.json
|
||||
if cat clone_before.json | grep '404: Not Found'; then
|
||||
echo "GIST_ID not valid anymore. Creating another gist..."
|
||||
gist_id=$(gh gist create clone.json | awk -F / '{print $NF}')
|
||||
echo $gist_id | gh secret set GIST_ID
|
||||
echo "GIST=$gist_id" >> $GITHUB_OUTPUT
|
||||
cp clone.json clone_before.json
|
||||
git rm --ignore-unmatch "$BADGE_DOC" CLONE.md
|
||||
fi
|
||||
if ! jq -e '
|
||||
type == "object" and
|
||||
(.clones | type == "array")
|
||||
' "$DATA_FILE" >/dev/null; then
|
||||
echo "::error title=Invalid traffic response::GitHub did not return clone traffic data."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create or reuse clone statistics Gist
|
||||
id: gist
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SECRET_TOKEN }}
|
||||
run: |
|
||||
gist_id="$CLONE_GIST_ID"
|
||||
expected_description="${GITHUB_REPOSITORY} clone statistics"
|
||||
authenticated_user=$(gh api user --jq '.login')
|
||||
save_gist_id=false
|
||||
valid_gist=false
|
||||
|
||||
if [ -z "$gist_id" ]; then
|
||||
gist_id=$(python - <<'PY'
|
||||
from pathlib import Path
|
||||
import os
|
||||
import re
|
||||
|
||||
pattern = re.compile(
|
||||
r"query=count.*?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 clone statistics 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
|
||||
gist_owner=$(gh api "gists/$gist_id" --jq '.owner.login')
|
||||
gist_description=$(gh api "gists/$gist_id" --jq '.description // ""')
|
||||
|
||||
if [ "$gist_owner" = "$authenticated_user" ] &&
|
||||
[ "$gist_description" = "$expected_description" ]; then
|
||||
valid_gist=true
|
||||
else
|
||||
echo "The detected Gist belongs to another repository or token owner."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$valid_gist" = true ]; then
|
||||
echo "Using existing Gist: $gist_id"
|
||||
else
|
||||
echo "GIST_ID not found. Creating a gist..."
|
||||
gist_id=$(gh gist create clone.json | awk -F / '{print $NF}')
|
||||
echo $gist_id | gh secret set GIST_ID
|
||||
echo "GIST=$gist_id" >> $GITHUB_OUTPUT
|
||||
cp clone.json clone_before.json
|
||||
if [ -n "$gist_id" ]; then
|
||||
echo "Configured Gist does not exist or is inaccessible; creating a new one."
|
||||
else
|
||||
echo "No clone statistics Gist is configured; creating one."
|
||||
fi
|
||||
|
||||
gist_url=$(gh gist create "$DATA_FILE" --desc "$expected_description")
|
||||
gist_id="${gist_url##*/}"
|
||||
save_gist_id=true
|
||||
fi
|
||||
|
||||
- name: update clone.json
|
||||
if [ "$save_gist_id" = true ]; then
|
||||
if ! gh variable set COMFY_CLONES_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_CLONES_GIST_ID=$gist_id"
|
||||
fi
|
||||
|
||||
if ! gh api "gists/$gist_id" \
|
||||
--jq '.files["clone.json"].content // empty' > "$PREVIOUS_DATA_FILE"; then
|
||||
cp "$DATA_FILE" "$PREVIOUS_DATA_FILE"
|
||||
fi
|
||||
|
||||
if ! jq -e '
|
||||
type == "object" and
|
||||
(.clones | type == "array")
|
||||
' "$PREVIOUS_DATA_FILE" >/dev/null 2>&1; then
|
||||
echo "The Gist has no valid clone history yet; starting with the current data."
|
||||
cp "$DATA_FILE" "$PREVIOUS_DATA_FILE"
|
||||
fi
|
||||
|
||||
gist_owner=$(gh api "gists/$gist_id" --jq '.owner.login')
|
||||
echo "id=$gist_id" >> "$GITHUB_OUTPUT"
|
||||
echo "owner=$gist_owner" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Merge clone history
|
||||
run: |
|
||||
curl https://raw.githubusercontent.com/MShawon/github-clone-count-badge/master/main.py > main.py
|
||||
python3 main.py
|
||||
python - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
- name: Update gist with latest count
|
||||
current_path = Path(os.environ["DATA_FILE"])
|
||||
previous_path = Path(os.environ["PREVIOUS_DATA_FILE"])
|
||||
|
||||
current = json.loads(current_path.read_text(encoding="utf-8"))
|
||||
previous = json.loads(previous_path.read_text(encoding="utf-8"))
|
||||
|
||||
by_timestamp = {}
|
||||
for entry in previous.get("clones", []) + current.get("clones", []):
|
||||
timestamp = str(entry.get("timestamp", "")).strip()
|
||||
if timestamp:
|
||||
by_timestamp[timestamp] = {
|
||||
"timestamp": timestamp,
|
||||
"count": int(entry.get("count", 0)),
|
||||
"uniques": int(entry.get("uniques", 0)),
|
||||
}
|
||||
|
||||
clones = [by_timestamp[key] for key in sorted(by_timestamp)]
|
||||
|
||||
if len(clones) > 100:
|
||||
older = clones[:-35]
|
||||
recent = clones[-35:]
|
||||
monthly = {}
|
||||
|
||||
for entry in older:
|
||||
month = entry["timestamp"][:7]
|
||||
aggregate = monthly.setdefault(
|
||||
month,
|
||||
{"timestamp": month, "count": 0, "uniques": 0},
|
||||
)
|
||||
aggregate["count"] += entry["count"]
|
||||
aggregate["uniques"] += entry["uniques"]
|
||||
|
||||
clones = [monthly[key] for key in sorted(monthly)] + recent
|
||||
|
||||
merged = dict(current)
|
||||
merged["clones"] = clones
|
||||
merged["count"] = sum(entry["count"] for entry in clones)
|
||||
merged["uniques"] = sum(entry["uniques"] for entry in clones)
|
||||
|
||||
current_path.write_text(
|
||||
json.dumps(merged, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
PY
|
||||
|
||||
jq . "$DATA_FILE"
|
||||
|
||||
- name: Update Gist and badge document
|
||||
id: badge
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SECRET_TOKEN }}
|
||||
GIST_ID: ${{ steps.gist.outputs.id }}
|
||||
GIST_OWNER: ${{ steps.gist.outputs.owner }}
|
||||
run: |
|
||||
BADGE_DOC="badges/CLONE.md"
|
||||
content=$(jq -c . "$DATA_FILE")
|
||||
jq -n \
|
||||
--arg description "${GITHUB_REPOSITORY} clone statistics" \
|
||||
--arg content "$content" \
|
||||
'{
|
||||
description: $description,
|
||||
files: {
|
||||
"clone.json": {
|
||||
content: $content
|
||||
}
|
||||
}
|
||||
}' > gist_patch.json
|
||||
|
||||
content=$(sed -e 's/\\/\\\\/g' -e 's/\t/\\t/g' -e 's/\"/\\"/g' -e 's/\r//g' "clone.json" | sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g')
|
||||
echo '{"description": "${{ github.repository }} clone statistics", "files": {"clone.json": {"content": "'"$content"'"}}}' > post_clone.json
|
||||
curl -s -X PATCH \
|
||||
--user "${{ github.actor }}:${{ secrets.SECRET_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @post_clone.json https://api.github.com/gists/${{ steps.set_id.outputs.GIST }} > /dev/null 2>&1
|
||||
gh api --method PATCH "gists/${GIST_ID}" --input gist_patch.json >/dev/null
|
||||
|
||||
if [ ! -f "$BADGE_DOC" ]; then
|
||||
mkdir -p badges
|
||||
shields="https://img.shields.io/badge/dynamic/json?color=success&label=Clone&query=count&url="
|
||||
shields_for_the_badge="https://img.shields.io/badge/dynamic/json?color=2F80ED&label=Clone&query=count&url="
|
||||
url="https://gist.githubusercontent.com/${{ github.actor }}/${{ steps.set_id.outputs.GIST }}/raw/clone.json"
|
||||
repo="https://github.com/MShawon/github-clone-count-badge"
|
||||
echo ''> "$BADGE_DOC"
|
||||
echo '
|
||||
**Markdown**
|
||||
mkdir -p "$(dirname "$BADGE_DOC")"
|
||||
|
||||
```markdown' >> "$BADGE_DOC"
|
||||
echo "[]($repo)" >> "$BADGE_DOC"
|
||||
echo '
|
||||
```' >> "$BADGE_DOC"
|
||||
echo "[]($repo)" >> "$BADGE_DOC"
|
||||
echo '
|
||||
gist_url="https://gist.githubusercontent.com/${GIST_OWNER}/${GIST_ID}/raw/clone.json"
|
||||
repo_url="https://github.com/${GITHUB_REPOSITORY}"
|
||||
shields="https://img.shields.io/badge/dynamic/json?color=success&label=Clone&query=count&url="
|
||||
shields_for_the_badge="https://img.shields.io/badge/dynamic/json?color=2F80ED&label=Clone&query=count&url="
|
||||
|
||||
**HTML**
|
||||
```html' >> "$BADGE_DOC"
|
||||
echo "<a href='$repo'><img alt='GitHub Clones' src='$shields$url&logo=github'></a>" >> "$BADGE_DOC"
|
||||
echo '
|
||||
```' >> "$BADGE_DOC"
|
||||
echo "<a href='$repo'><img alt='GitHub Clones' src='$shields$url&logo=github'></a>" >> "$BADGE_DOC"
|
||||
echo '
|
||||
{
|
||||
echo "**Markdown**"
|
||||
echo
|
||||
echo '```markdown'
|
||||
echo "[](${repo_url})"
|
||||
echo '```'
|
||||
echo
|
||||
echo "[](${repo_url})"
|
||||
echo
|
||||
echo "**HTML**"
|
||||
echo
|
||||
echo '```html'
|
||||
echo "<a href='${repo_url}'><img alt='GitHub Clones' src='${shields}${gist_url}&logo=github'></a>"
|
||||
echo '```'
|
||||
echo
|
||||
echo "<a href='${repo_url}'><img alt='GitHub Clones' src='${shields}${gist_url}&logo=github'></a>"
|
||||
echo
|
||||
echo "**HTML (for-the-badge)**"
|
||||
echo
|
||||
echo '```html'
|
||||
echo "<a href='${repo_url}'><img alt='GitHub Clones' src='${shields_for_the_badge}${gist_url}&logo=github&style=for-the-badge'></a>"
|
||||
echo '```'
|
||||
echo
|
||||
echo "<a href='${repo_url}'><img alt='GitHub Clones' src='${shields_for_the_badge}${gist_url}&logo=github&style=for-the-badge'></a>"
|
||||
} > "$BADGE_DOC"
|
||||
|
||||
**HTML (for-the-badge)**
|
||||
```html' >> "$BADGE_DOC"
|
||||
echo "<a href='$repo'><img alt='GitHub Clones' src='$shields_for_the_badge$url&logo=github&style=for-the-badge'></a>" >> "$BADGE_DOC"
|
||||
echo '```' >> "$BADGE_DOC"
|
||||
echo "<a href='$repo'><img alt='GitHub Clones' src='$shields_for_the_badge$url&logo=github&style=for-the-badge'></a>" >> "$BADGE_DOC"
|
||||
|
||||
if [ -n "$(git status --porcelain -- "$BADGE_DOC")" ]; then
|
||||
git add "$BADGE_DOC"
|
||||
git config --global user.name "GitHub Action"
|
||||
git config --global user.email "action@github.com"
|
||||
git commit -m "create clone count badge"
|
||||
git config user.name "GitHub Action"
|
||||
git config user.email "action@github.com"
|
||||
git commit -m "Create or update GitHub clone count badge"
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Badge document is already up to date."
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Push
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Push badge document
|
||||
if: steps.badge.outputs.changed == 'true'
|
||||
run: git push
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
# Reusable workflow
|
||||
#
|
||||
# Project metadata is read by the publishing action from pyproject.toml.
|
||||
# No project-specific repository variables need to be configured manually.
|
||||
#
|
||||
# Required repository secret:
|
||||
# REGISTRY_ACCESS_TOKEN Comfy Registry publishing API key
|
||||
|
||||
name: Publish to Comfy Registry
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
@@ -19,12 +25,12 @@ jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'Azornes/Comfyui-Model-Resolver' }}
|
||||
if: github.ref_name == github.event.repository.default_branch
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Check registry token
|
||||
- name: Validate publishing configuration
|
||||
env:
|
||||
REGISTRY_ACCESS_TOKEN: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
run: |
|
||||
@@ -33,7 +39,38 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python - <<'PY'
|
||||
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)
|
||||
|
||||
required_fields = {
|
||||
"[project].name": config.get("project", {}).get("name"),
|
||||
"[project].version": config.get("project", {}).get("version"),
|
||||
"[tool.comfy].PublisherId": (
|
||||
config.get("tool", {}).get("comfy", {}).get("PublisherId")
|
||||
),
|
||||
}
|
||||
missing = [
|
||||
field
|
||||
for field, value in required_fields.items()
|
||||
if not str(value or "").strip()
|
||||
]
|
||||
|
||||
if missing:
|
||||
raise SystemExit(
|
||||
"Missing required pyproject.toml fields: " + ", ".join(missing)
|
||||
)
|
||||
PY
|
||||
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
with:
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
skip_checkout: true
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# Reusable workflow
|
||||
#
|
||||
# The release name and version are read from pyproject.toml.
|
||||
# No project-specific repository variables or additional secrets are required.
|
||||
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
@@ -18,6 +21,7 @@ concurrency:
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref_name == github.event.repository.default_branch
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
@@ -35,14 +39,21 @@ jobs:
|
||||
with open("pyproject.toml", "rb") as handle:
|
||||
data = tomllib.load(handle)
|
||||
|
||||
project = data["project"]
|
||||
version = str(project["version"]).strip()
|
||||
name = str(project["name"]).strip()
|
||||
project = data.get("project", {})
|
||||
version = str(project.get("version", "")).strip()
|
||||
name = str(project.get("name", "")).strip()
|
||||
display_name = str(
|
||||
data.get("tool", {}).get("comfy", {}).get("DisplayName") or name
|
||||
).strip()
|
||||
|
||||
if not name:
|
||||
raise SystemExit("[project].name is missing in pyproject.toml")
|
||||
|
||||
if not re.fullmatch(r"\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?", version):
|
||||
raise SystemExit(f"Invalid semantic version in pyproject.toml: {version}")
|
||||
|
||||
print(f"project_name={name}")
|
||||
print(f"display_name={display_name}")
|
||||
print(f"version={version}")
|
||||
print(f"tag=v{version}")
|
||||
PY
|
||||
@@ -86,9 +97,9 @@ jobs:
|
||||
|
||||
- name: Create GitHub Release
|
||||
if: steps.tag_check.outputs.exists != 'true'
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: ${{ steps.metadata.outputs.tag }}
|
||||
name: ComfyUI Model Resolver ${{ steps.metadata.outputs.tag }}
|
||||
name: ${{ steps.metadata.outputs.display_name }} ${{ steps.metadata.outputs.tag }}
|
||||
body: ${{ steps.notes.outputs.body }}
|
||||
generate_release_notes: true
|
||||
|
||||
Reference in New Issue
Block a user