# Release workflow for ComfyUI frontend: version bump → PyPI publish → ComfyUI PR. # Runs on a bi-weekly schedule for minor releases, or manually for patch/hotfix releases. name: 'Release: ComfyUI' on: # Bi-weekly schedule: Monday at 20:00 UTC schedule: - cron: '0 20 * * 1' # Manual trigger for both on-demand minor and patch/hotfix releases workflow_dispatch: inputs: release_type: description: 'minor = next minor version (bi-weekly cadence), patch = hotfix for current production version' required: true default: 'minor' type: choice options: - minor - patch comfyui_fork: description: 'ComfyUI fork to use for PR (e.g., Comfy-Org/ComfyUI)' required: false default: 'Comfy-Org/ComfyUI' type: string jobs: check-release-week: runs-on: ubuntu-latest outputs: is_release_week: ${{ steps.check.outputs.is_release_week }} steps: - name: Check if release week id: check run: | # Anchor date: first bi-weekly release Monday ANCHOR="2025-12-22" ANCHOR_EPOCH=$(date -d "$ANCHOR" +%s) NOW_EPOCH=$(date +%s) WEEKS_SINCE=$(( (NOW_EPOCH - ANCHOR_EPOCH) / 604800 )) if [ $((WEEKS_SINCE % 2)) -eq 0 ]; then echo "Release week (week $WEEKS_SINCE since anchor $ANCHOR)" echo "is_release_week=true" >> $GITHUB_OUTPUT else echo "Not a release week (week $WEEKS_SINCE since anchor $ANCHOR)" echo "is_release_week=false" >> $GITHUB_OUTPUT fi - name: Summary run: | echo "## Release Check" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- Is release week: ${{ steps.check.outputs.is_release_week }}" >> $GITHUB_STEP_SUMMARY echo "- Manual trigger: ${{ github.event_name == 'workflow_dispatch' }}" >> $GITHUB_STEP_SUMMARY echo "- Release type: ${{ inputs.release_type || 'minor (scheduled)' }}" >> $GITHUB_STEP_SUMMARY resolve-version: needs: check-release-week if: needs.check-release-week.outputs.is_release_week == 'true' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest outputs: current_version: ${{ steps.resolve.outputs.current_version }} target_version: ${{ steps.resolve.outputs.target_version }} target_minor: ${{ steps.resolve.outputs.target_minor }} target_branch: ${{ steps.resolve.outputs.target_branch }} needs_release: ${{ steps.resolve.outputs.needs_release }} diff_url: ${{ steps.resolve.outputs.diff_url }} latest_patch_tag: ${{ steps.resolve.outputs.latest_patch_tag }} steps: - name: Checkout ComfyUI_frontend uses: actions/checkout@v6 with: fetch-depth: 0 path: frontend - name: Checkout ComfyUI (sparse) uses: actions/checkout@v6 with: repository: Comfy-Org/ComfyUI sparse-checkout: | requirements.txt path: comfyui - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 with: package_json_file: frontend/package.json - name: Setup Node.js uses: actions/setup-node@v6 with: node-version-file: 'frontend/.nvmrc' - name: Install dependencies working-directory: frontend run: pnpm install --frozen-lockfile - name: Resolve release information id: resolve working-directory: frontend env: RELEASE_TYPE: ${{ inputs.release_type || 'minor' }} run: | set -euo pipefail # Run the resolver script if ! RESULT=$(pnpm exec tsx scripts/cicd/resolve-comfyui-release.ts ../comfyui .); then echo "Failed to resolve release information" exit 1 fi echo "Resolver output:" echo "$RESULT" # Validate JSON output if ! echo "$RESULT" | jq empty 2>/dev/null; then echo "Invalid JSON output from resolver" exit 1 fi # Parse JSON output and set outputs echo "current_version=$(echo "$RESULT" | jq -r '.current_version')" >> $GITHUB_OUTPUT echo "target_version=$(echo "$RESULT" | jq -r '.target_version')" >> $GITHUB_OUTPUT echo "target_minor=$(echo "$RESULT" | jq -r '.target_minor')" >> $GITHUB_OUTPUT echo "target_branch=$(echo "$RESULT" | jq -r '.target_branch')" >> $GITHUB_OUTPUT echo "needs_release=$(echo "$RESULT" | jq -r '.needs_release')" >> $GITHUB_OUTPUT echo "diff_url=$(echo "$RESULT" | jq -r '.diff_url')" >> $GITHUB_OUTPUT echo "latest_patch_tag=$(echo "$RESULT" | jq -r '.latest_patch_tag')" >> $GITHUB_OUTPUT - name: Summary run: | echo "## Release Information" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- Current version: ${{ steps.resolve.outputs.current_version }}" >> $GITHUB_STEP_SUMMARY echo "- Target version: ${{ steps.resolve.outputs.target_version }}" >> $GITHUB_STEP_SUMMARY echo "- Target branch: ${{ steps.resolve.outputs.target_branch }}" >> $GITHUB_STEP_SUMMARY echo "- Needs release: ${{ steps.resolve.outputs.needs_release }}" >> $GITHUB_STEP_SUMMARY echo "- Diff: [${{ steps.resolve.outputs.current_version }}...${{ steps.resolve.outputs.target_version }}](${{ steps.resolve.outputs.diff_url }})" >> $GITHUB_STEP_SUMMARY trigger-release-if-needed: needs: resolve-version if: needs.resolve-version.outputs.needs_release == 'true' runs-on: ubuntu-latest steps: - name: Trigger release workflow env: GH_TOKEN: ${{ secrets.PR_GH_TOKEN }} run: | set -euo pipefail echo "Triggering release workflow for branch ${{ needs.resolve-version.outputs.target_branch }}" # Trigger the release-version-bump workflow if ! gh workflow run release-version-bump.yaml \ --repo Comfy-Org/ComfyUI_frontend \ --ref main \ --field version_type=patch \ --field branch=${{ needs.resolve-version.outputs.target_branch }}; then echo "Failed to trigger release workflow" exit 1 fi echo "Release workflow triggered successfully for ${{ needs.resolve-version.outputs.target_branch }}" - name: Summary run: | echo "## Release Workflow Triggered" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- Branch: ${{ needs.resolve-version.outputs.target_branch }}" >> $GITHUB_STEP_SUMMARY echo "- Target version: ${{ needs.resolve-version.outputs.target_version }}" >> $GITHUB_STEP_SUMMARY echo "- [View workflow runs](https://github.com/Comfy-Org/ComfyUI_frontend/actions/workflows/release-version-bump.yaml)" >> $GITHUB_STEP_SUMMARY publish-pypi: needs: [resolve-version, trigger-release-if-needed] if: > always() && needs.resolve-version.result == 'success' && (needs.trigger-release-if-needed.result == 'success' || needs.trigger-release-if-needed.result == 'skipped') runs-on: ubuntu-latest steps: - name: Wait for release PR to be created and merged if: needs.trigger-release-if-needed.result == 'success' env: GH_TOKEN: ${{ secrets.PR_GH_TOKEN }} run: | set -euo pipefail TARGET_VERSION="${{ needs.resolve-version.outputs.target_version }}" TARGET_BRANCH="${{ needs.resolve-version.outputs.target_branch }}" echo "Waiting for version bump PR for v${TARGET_VERSION} on ${TARGET_BRANCH} to be merged..." # Poll for up to 30 minutes (a human or automation needs to merge the version bump PR) for i in $(seq 1 60); do # Check if the tag exists (release-draft-create creates a tag on merge) if gh api "repos/Comfy-Org/ComfyUI_frontend/git/ref/tags/v${TARGET_VERSION}" --silent 2>/dev/null; then echo "✅ Tag v${TARGET_VERSION} found — release PR has been merged" exit 0 fi echo "Attempt $i/60: Tag v${TARGET_VERSION} not found yet, waiting 30s..." sleep 30 done echo "❌ Timed out waiting for tag v${TARGET_VERSION}" exit 1 - name: Checkout code at target version uses: actions/checkout@v6 with: ref: v${{ needs.resolve-version.outputs.target_version }} - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' cache: 'pnpm' - name: Build project env: SENTRY_DSN: ${{ secrets.SENTRY_DSN }} ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} ENABLE_MINIFY: 'true' USE_PROD_CONFIG: 'true' run: | pnpm install --frozen-lockfile pnpm build - name: Set up Python uses: actions/setup-python@v6 with: python-version: '3.x' - name: Install build dependencies run: python -m pip install build - name: Build and publish PyPI package run: | set -euo pipefail mkdir -p comfyui_frontend_package/comfyui_frontend_package/static/ cp -r dist/* comfyui_frontend_package/comfyui_frontend_package/static/ - name: Build pypi package run: python -m build working-directory: comfyui_frontend_package env: COMFYUI_FRONTEND_VERSION: ${{ needs.resolve-version.outputs.target_version }} - name: Publish pypi package uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: skip-existing: true password: ${{ secrets.PYPI_TOKEN }} packages-dir: comfyui_frontend_package/dist - name: Wait for PyPI propagation run: | set -euo pipefail TARGET_VERSION="${{ needs.resolve-version.outputs.target_version }}" PACKAGE="comfyui-frontend-package" echo "Waiting for ${PACKAGE}==${TARGET_VERSION} to be available on PyPI..." # Wait up to 15 minutes (polling every 30 seconds) for i in $(seq 1 30); do HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/${PACKAGE}/${TARGET_VERSION}/json") if [ "$HTTP_CODE" = "200" ]; then echo "✅ ${PACKAGE}==${TARGET_VERSION} is available on PyPI" exit 0 fi echo "Attempt $i/30: PyPI returned HTTP ${HTTP_CODE}, waiting 30s..." sleep 30 done echo "❌ Timed out waiting for ${PACKAGE}==${TARGET_VERSION} on PyPI" exit 1 - name: Summary run: | echo "## PyPI Publishing" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "- Package: comfyui-frontend-package" >> $GITHUB_STEP_SUMMARY echo "- Version: ${{ needs.resolve-version.outputs.target_version }}" >> $GITHUB_STEP_SUMMARY echo "- Status: ✅ Published and confirmed available" >> $GITHUB_STEP_SUMMARY create-comfyui-pr: needs: [ check-release-week, resolve-version, trigger-release-if-needed, publish-pypi ] if: always() && needs.resolve-version.result == 'success' && needs.publish-pypi.result == 'success' && (needs.check-release-week.outputs.is_release_week == 'true' || github.event_name == 'workflow_dispatch') runs-on: ubuntu-latest steps: - name: Checkout ComfyUI fork uses: actions/checkout@v6 with: repository: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }} token: ${{ secrets.PR_GH_TOKEN }} path: comfyui - name: Sync with upstream working-directory: comfyui run: | set -euo pipefail # Fetch latest upstream to base our branch on fresh code # Note: This only affects the local checkout, NOT the fork's master branch # We only push the automation branch, leaving the fork's master untouched echo "Fetching upstream master..." if ! git fetch https://github.com/Comfy-Org/ComfyUI.git master; then echo "Failed to fetch upstream master" exit 1 fi echo "Checking out upstream master..." if ! git checkout FETCH_HEAD; then echo "Failed to checkout FETCH_HEAD" exit 1 fi echo "Successfully synced with upstream master" - name: Update requirements.txt working-directory: comfyui run: | set -euo pipefail TARGET_VERSION="${{ needs.resolve-version.outputs.target_version }}" echo "Updating comfyui-frontend-package to ${TARGET_VERSION}" # Update the comfyui-frontend-package version (POSIX-compatible) sed -i.bak "s/comfyui-frontend-package==[0-9.][0-9.]*/comfyui-frontend-package==${TARGET_VERSION}/" requirements.txt rm requirements.txt.bak # Verify the change was made if ! grep -q "comfyui-frontend-package==${TARGET_VERSION}" requirements.txt; then echo "Failed to update requirements.txt" exit 1 fi echo "Updated requirements.txt:" grep comfyui-frontend-package requirements.txt - name: Build PR description id: pr-body run: | BODY=$(cat <<'EOF' Bumps frontend to ${{ needs.resolve-version.outputs.target_version }} Test quickly: ```bash python main.py --front-end-version Comfy-Org/ComfyUI_frontend@${{ needs.resolve-version.outputs.target_version }} ``` - Diff: [v${{ needs.resolve-version.outputs.current_version }}...v${{ needs.resolve-version.outputs.target_version }}](${{ needs.resolve-version.outputs.diff_url }}) - PyPI: https://pypi.org/project/comfyui-frontend-package/${{ needs.resolve-version.outputs.target_version }}/ - npm: https://www.npmjs.com/package/@comfyorg/comfyui-frontend-types/v/${{ needs.resolve-version.outputs.target_version }} EOF ) PYPI_NOTE="✅ **PyPI package confirmed available** — \`comfyui-frontend-package==${{ needs.resolve-version.outputs.target_version }}\` has been published and verified." BODY=$''"${PYPI_NOTE}"$'\n\n'"${BODY}" # Save to file for later use printf '%s\n' "$BODY" > pr-body.txt cat pr-body.txt - name: Create PR to ComfyUI working-directory: comfyui env: GH_TOKEN: ${{ secrets.PR_GH_TOKEN }} COMFYUI_FORK: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }} run: | set -euo pipefail # Extract fork owner from repository name FORK_OWNER=$(echo "$COMFYUI_FORK" | cut -d'/' -f1) echo "Creating PR from ${COMFYUI_FORK} to Comfy-Org/ComfyUI" # Configure git git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" # Create/update branch (reuse same branch name each release cycle) BRANCH="automation/comfyui-frontend-bump" git checkout -B "$BRANCH" git add requirements.txt if ! git diff --cached --quiet; then git commit -m "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}" else echo "No changes to commit" exit 0 fi # Force push to fork (overwrites previous release cycle's branch) # Note: This intentionally destroys branch history to maintain a single PR # Any review comments or manual commits will need to be re-applied if ! git push -f origin "$BRANCH"; then echo "Failed to push branch to fork" exit 1 fi # Create draft PR from fork to upstream PR_BODY=$(cat ../pr-body.txt) # Try to create PR, ignore error if it already exists if ! gh pr create \ --repo Comfy-Org/ComfyUI \ --head "${FORK_OWNER}:${BRANCH}" \ --base master \ --title "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}" \ --body "$PR_BODY" \ --draft 2>&1; then # Check if PR already exists set +e EXISTING_PR=$(gh pr list --repo Comfy-Org/ComfyUI --head "${FORK_OWNER}:${BRANCH}" --json number --jq '.[0].number' 2>&1) PR_LIST_EXIT=$? set -e if [ $PR_LIST_EXIT -ne 0 ]; then echo "Failed to check for existing PR: $EXISTING_PR" exit 1 fi if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then echo "PR already exists (#${EXISTING_PR}), refreshing title/body" gh pr edit "$EXISTING_PR" \ --repo Comfy-Org/ComfyUI \ --title "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}" \ --body-file ../pr-body.txt else echo "Failed to create PR and no existing PR found" exit 1 fi fi - name: Summary run: | echo "## ComfyUI PR Created" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "Draft PR created in Comfy-Org/ComfyUI" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### PR Body:" >> $GITHUB_STEP_SUMMARY cat pr-body.txt >> $GITHUB_STEP_SUMMARY