Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
343 changes: 4 additions & 339 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@ jobs:

- name: Detect version
id: version
env:
RELEASE_TYPE: ${{ inputs.release_type }}
VERSION_OVERRIDE: ${{ inputs.version_override }}
run: |
set -euo pipefail
RELEASE_TYPE="${{ inputs.release_type }}"
VERSION_OVERRIDE="${{ inputs.version_override }}"

if [[ -n "$VERSION_OVERRIDE" ]]; then
# Strip 'v' prefix if present
Expand Down Expand Up @@ -319,340 +320,4 @@ jobs:
# Strategy per release type (one GitHub release per minor version):
# - patch-release: create a new draft release with GitHub auto-generated notes.
# - minor-prerelease: if no draft release exists for this minor version, generate
# notes using the AI script and create a draft prerelease.
# Subsequent prereleases for the same minor are no-ops.
# - minor-release: find the existing draft prerelease and update its tag/title
# to the final version (removing prerelease flag).
# ============================================================
release-notes:
needs: prepare
if: ${{ !inputs.dry_run }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.prepare.outputs.tag }}
fetch-depth: 0

# --- Patch release: create release with GitHub auto-generated notes ---
- name: Create GitHub release (patch)
if: ${{ inputs.release_type == 'patch-release' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ needs.prepare.outputs.tag }}" \
--title "v${{ needs.prepare.outputs.version }}" \
--draft \
--generate-notes

# --- Prerelease: generate notes only if no draft exists for this minor ---
- name: Check for existing draft release
if: ${{ inputs.release_type == 'minor-prerelease' }}
id: check_draft
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
MINOR=$(echo "${{ needs.prepare.outputs.version }}" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')
# Look for any existing GitHub release (draft or published) matching this minor version
EXISTING=$(gh release list --json tagName,isDraft --limit 100 \
| jq -r ".[] | select(.tagName | startswith(\"v${MINOR}.\")) | .tagName" | head -1)
if [[ -n "$EXISTING" ]]; then
echo "Draft release already exists for v${MINOR}: $EXISTING — skipping"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "No existing release for v${MINOR} — will generate notes"
echo "skip=false" >> $GITHUB_OUTPUT
fi

- uses: actions/setup-python@v6
if: ${{ inputs.release_type == 'minor-prerelease' && steps.check_draft.outputs.skip != 'true' }}
with:
python-version: "3.12"
- name: Install dependencies
if: ${{ inputs.release_type == 'minor-prerelease' && steps.check_draft.outputs.skip != 'true' }}
run: pip install PyGithub

- name: Install OpenCode
if: ${{ inputs.release_type == 'minor-prerelease' && steps.check_draft.outputs.skip != 'true' }}
run: curl -fsSL https://opencode.ai/install | bash

- name: Generate release notes with OpenCode
if: ${{ inputs.release_type == 'minor-prerelease' && steps.check_draft.outputs.skip != 'true' }}
env:
HF_TOKEN: ${{ secrets.RELEASE_NOTES_HF_TOKEN }}
GITHUB_TOKEN_RELEASE_NOTES: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NOTES_MODEL: "huggingface/MiniMaxAI/MiniMax-M2.5"
run: |
python -m utils.release_notes.generate_release_notes \
--since "${{ needs.prepare.outputs.since_tag }}" \
--${{ needs.prepare.outputs.bump_type }}

- name: Create draft GitHub release (prerelease)
if: ${{ inputs.release_type == 'minor-prerelease' && steps.check_draft.outputs.skip != 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ needs.prepare.outputs.tag }}"
VERSION="${{ needs.prepare.outputs.version }}"

# Find generated notes file (if any)
NOTES_FILE=""
for f in .release-notes/RELEASE_NOTES_*.md; do
if [[ -f "$f" ]]; then
NOTES_FILE="$f"
break
fi
done

ARGS=(
"$TAG"
--title "v${VERSION}"
--draft
--prerelease
)

if [[ -n "$NOTES_FILE" ]]; then
echo "Using generated release notes from $NOTES_FILE"
ARGS+=(--notes-file "$NOTES_FILE")
else
echo "No generated notes found, using GitHub auto-generated notes"
ARGS+=(--generate-notes)
fi

gh release create "${ARGS[@]}"

# --- Minor release: update existing draft to point to the final tag ---
- name: Promote draft release to final version
if: ${{ inputs.release_type == 'minor-release' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ needs.prepare.outputs.tag }}"
VERSION="${{ needs.prepare.outputs.version }}"
MINOR=$(echo "$VERSION" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')

# Find the existing draft release for this minor version
EXISTING_TAG=$(gh release list --json tagName,isDraft --limit 100 \
| jq -r ".[] | select(.isDraft) | select(.tagName | startswith(\"v${MINOR}.\")) | .tagName" | head -1)

if [[ -n "$EXISTING_TAG" ]]; then
echo "Updating existing draft release from $EXISTING_TAG to $TAG"
gh release edit "$EXISTING_TAG" \
--tag "$TAG" \
--title "v${VERSION}" \
--prerelease=false
else
echo "::warning::No draft release found for v${MINOR}. Creating new release with auto-generated notes."
gh release create "$TAG" \
--title "v${VERSION}" \
--draft \
--generate-notes
fi

# ============================================================
# 6. DOWNSTREAM TESTING — test RC in transformers, datasets, etc.
# ============================================================
test-downstream:
needs: [prepare, publish-pypi]
if: ${{ needs.prepare.outputs.is_prerelease == 'true' && !inputs.dry_run }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target-repo: ["transformers", "datasets", "diffusers", "sentence-transformers"]
steps:
- name: Wait for prerelease to be available on PyPI
run: |
VERSION="${{ needs.prepare.outputs.version }}"
echo "Waiting for huggingface-hub==${VERSION} on PyPI..."
for i in $(seq 1 20); do
if pip install "huggingface-hub==${VERSION}" 2>/dev/null; then
echo "Package available on PyPI!"
exit 0
fi
echo "Not available yet, retrying in 15s... ($i/20)"
sleep 15
done
echo "::error::Package not available on PyPI after 5 minutes"
exit 1

- name: Checkout target repo
uses: actions/checkout@v6
with:
repository: huggingface/${{ matrix.target-repo }}
path: ${{ matrix.target-repo }}
token: ${{ secrets.HUGGINGFACE_HUB_AUTOMATIC_RC_TESTING }}

- name: Configure Git
run: |
cd ${{ matrix.target-repo }}
git config user.name "Hugging Face Bot (RC Testing)"
git config user.email "bot@huggingface.co"

- name: Create test branch and update dependencies
run: |
cd ${{ matrix.target-repo }}
VERSION="${{ needs.prepare.outputs.version }}"
BRANCH_NAME="ci-test-huggingface-hub-${VERSION}-release"

git checkout -b "$BRANCH_NAME"

# Update setup.py if exists
if [ -f "setup.py" ]; then
sed -i -E "s/\"huggingface-hub(>=|==)[^\"]*\"/\"huggingface-hub==${VERSION}\"/" setup.py
git add setup.py
fi

# transformers-specific
if [ "${{ matrix.target-repo }}" = "transformers" ]; then
sed -i -E "s/\"huggingface-hub(>=|==)[^\"]*\"/\"huggingface-hub==${VERSION}\"/" src/transformers/dependency_versions_table.py
git add src/transformers/dependency_versions_table.py
fi

# diffusers-specific
if [ "${{ matrix.target-repo }}" = "diffusers" ]; then
sed -i -E "s/\"huggingface-hub\":.*/\"huggingface-hub\": \"huggingface-hub==${VERSION}\",/" src/diffusers/dependency_versions_table.py
git add src/diffusers/dependency_versions_table.py
fi

# sentence-transformers-specific
if [ "${{ matrix.target-repo }}" = "sentence-transformers" ]; then
sed -i -E "s/\"huggingface-hub(>=|==)[^\"]*\"/\"huggingface-hub==${VERSION}\"/" pyproject.toml
git add pyproject.toml
fi

# Enable prerelease flag in workflow files
find .github/workflows/ -type f -exec sed -i 's/uv pip install /uv pip install --prerelease=allow /g' {} +
git add .github/workflows/

git --no-pager diff --staged
git commit -m "Test hfh ${VERSION}"
git push --set-upstream origin "$BRANCH_NAME"

- name: Print URLs
run: |
VERSION="${{ needs.prepare.outputs.version }}"
BRANCH_NAME="ci-test-huggingface-hub-${VERSION}-release"
echo "### ${{ matrix.target-repo }}" >> $GITHUB_STEP_SUMMARY
echo "- [Actions](https://github.com/huggingface/${{ matrix.target-repo }}/actions)" >> $GITHUB_STEP_SUMMARY
echo "- [Compare](https://github.com/huggingface/${{ matrix.target-repo }}/compare/main...${BRANCH_NAME})" >> $GITHUB_STEP_SUMMARY

# ============================================================
# 7. POST-RELEASE — open PR to bump main to next dev version
# ============================================================
post-release:
needs: [prepare, publish-pypi]
if: ${{ inputs.release_type == 'minor-release' && !inputs.dry_run }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: main

- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Create version bump PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ needs.prepare.outputs.version }}"
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
NEXT_MINOR=$((MINOR + 1))
DEV_VERSION="${MAJOR}.${NEXT_MINOR}.0.dev0"

# Guard: skip if main already has an equal or higher version (e.g. version_override for older minor)
CURRENT_VERSION=$(grep -oP '__version__ = "\K[^"]+' src/huggingface_hub/__init__.py)
CURRENT_MINOR=$(echo "$CURRENT_VERSION" | cut -d. -f2)
if [[ "$CURRENT_MINOR" -ge "$NEXT_MINOR" ]]; then
echo "::warning::main is already at ${CURRENT_VERSION} (>= ${DEV_VERSION}). Skipping version bump."
exit 0
fi

PR_BRANCH="post-release-v${VERSION}"
git checkout -b "$PR_BRANCH"

sed -i -E "s/__version__ = \".*\"/__version__ = \"${DEV_VERSION}\"/" src/huggingface_hub/__init__.py
git add src/huggingface_hub/__init__.py
git commit -m "Post-release: bump version to ${DEV_VERSION}"
git push -u origin "$PR_BRANCH"

gh pr create \
--title "Post-release: bump version to ${DEV_VERSION}" \
--body "Automated version bump after v${VERSION} release." \
--base main

# ============================================================
# 8. SYNC HF CLI SKILL — update skill docs in skills repo
# ============================================================
sync-hf-cli-skill:
needs: prepare
if: ${{ needs.prepare.outputs.is_prerelease != 'true' && !inputs.dry_run }}
runs-on: ubuntu-latest
steps:
- name: Checkout huggingface_hub
uses: actions/checkout@v6
with:
ref: ${{ needs.prepare.outputs.tag }}

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"

- name: Install dependencies
run: pip install -e .

- name: Generate skill docs
run: |
mkdir -p /tmp/hf-cli-skill
hf skills preview > /tmp/hf-cli-skill/SKILL.md

- name: Create GitHub App token
id: app_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID_HUB_SKILLS_REPO }}
private-key: ${{ secrets.APP_SECRET_PREM_HUB_SKILLS_REPO }}
repositories: skills

- name: Checkout skills repo
uses: actions/checkout@v6
with:
repository: huggingface/skills
token: ${{ steps.app_token.outputs.token }}
path: skills-repo

- name: Copy generated files
run: cp /tmp/hf-cli-skill/SKILL.md skills-repo/skills/hf-cli/

- name: Check for changes
id: check_changes
working-directory: skills-repo
run: |
git diff --quiet && echo "changed=false" >> $GITHUB_OUTPUT || echo "changed=true" >> $GITHUB_OUTPUT

- name: Create Pull Request
if: steps.check_changes.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ steps.app_token.outputs.token }}
path: skills-repo
branch: sync/hf-cli-${{ github.run_id }}
delete-branch: true
title: "Sync HF CLI Skill (${{ needs.prepare.outputs.tag }})"
reviewers: hanouticelina
body: |
Auto-generated from [huggingface_hub@${{ github.sha }}](https://github.com/huggingface/huggingface_hub/commit/${{ github.sha }})

Triggered by changes to `src/huggingface_hub/cli/`

---
This PR was created automatically by the [release](https://github.com/huggingface/huggingface_hub/blob/main/.github/workflows/release.yml) workflow.
commit-message: "Sync HF CLI skill from huggingface_hub@${{ github.sha }}"
labels: |
automated
cli-sync
# notes using the AI script and create a
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Truncated comment left behind after job deletion

Low Severity

The file ends with a dangling, truncated comment block (lines 317–323). The release-notes job and subsequent jobs were removed, but the comment header describing the "RELEASE NOTES" strategy was only partially deleted — the last line cuts off mid-sentence at "create a". This orphaned comment references removed functionality and is incomplete, appearing to be an editing artifact from the deletion.

Fix in Cursor Fix in Web

Loading