diff --git a/.github/actions/setup-frontend/action.yml b/.github/actions/setup-frontend/action.yml new file mode 100644 index 000000000..3ebc12eb3 --- /dev/null +++ b/.github/actions/setup-frontend/action.yml @@ -0,0 +1,67 @@ +name: Setup Frontend +description: 'Setup ComfyUI frontend development environment' +inputs: + extra_server_params: + description: 'Additional parameters to pass to ComfyUI server' + required: false + default: '' +runs: + using: 'composite' + steps: + - name: Checkout ComfyUI + uses: actions/checkout@v4 + with: + repository: 'comfyanonymous/ComfyUI' + path: 'ComfyUI' + + - name: Checkout ComfyUI_frontend + uses: actions/checkout@v4 + with: + repository: 'Comfy-Org/ComfyUI_frontend' + path: 'ComfyUI_frontend' + + - name: Copy ComfyUI_devtools from frontend repo + shell: bash + run: | + mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools + cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/ + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + cache: 'pnpm' + cache-dependency-path: 'ComfyUI_frontend/pnpm-lock.yaml' + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Python requirements + shell: bash + working-directory: ComfyUI + run: | + python -m pip install --upgrade pip + pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu + pip install -r requirements.txt + pip install wait-for-it + + - name: Build & Install ComfyUI_frontend + shell: bash + working-directory: ComfyUI_frontend + run: | + pnpm install --frozen-lockfile + pnpm build + + - name: Start ComfyUI server + shell: bash + working-directory: ComfyUI + run: | + python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist ${{ inputs.extra_server_params }} & + wait-for-it --service 127.0.0.1:8188 -t 600 \ No newline at end of file diff --git a/.github/workflows/backport.yaml b/.github/workflows/auto-backport.yaml similarity index 99% rename from .github/workflows/backport.yaml rename to .github/workflows/auto-backport.yaml index 178bd4ee8..20eadc0c9 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/auto-backport.yaml @@ -60,7 +60,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/chromatic.yaml b/.github/workflows/chromatic.yaml deleted file mode 100644 index 127186d68..000000000 --- a/.github/workflows/chromatic.yaml +++ /dev/null @@ -1,57 +0,0 @@ -name: 'Chromatic' - -# - [Automate Chromatic with GitHub Actions â€ĸ Chromatic docs]( https://www.chromatic.com/docs/github-actions/ ) - -on: - workflow_dispatch: # Allow manual triggering - pull_request: - branches: [main] - -jobs: - chromatic-deployment: - runs-on: ubuntu-latest - # Only run for PRs from version-bump-* branches or manual triggers - if: github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'version-bump-') - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Required for Chromatic baseline - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - - - name: Cache tool outputs - uses: actions/cache@v4 - with: - path: | - .cache - storybook-static - tsconfig.tsbuildinfo - key: storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', '*.config.*', '.storybook/**/*') }} - restore-keys: | - storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}- - storybook-cache-${{ runner.os }}- - storybook-tools-cache-${{ runner.os }}- - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build Storybook and run Chromatic - id: chromatic - uses: chromaui/action@latest - with: - projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - buildScriptName: build-storybook - autoAcceptChanges: 'main' # Auto-accept changes on main branch - exitOnceUploaded: true # Don't wait for UI tests to complete - diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index d1eecafe3..08ad70727 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -29,11 +29,9 @@ jobs: - name: Check if we should proceed id: check-status run: | - # Get all check runs for this commit - CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("lint-and-format|test|playwright-tests")) | {name, conclusion}') - - # Check if any required checks failed - if echo "$CHECK_RUNS" | grep -q '"conclusion": "failure"'; then + CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("lint-and-format")) | {name, conclusion}') + + if echo "$CHECK_RUNS" | grep -Eq '"conclusion": "(failure|cancelled|timed_out|action_required)"'; then echo "Some CI checks failed - skipping Claude review" echo "proceed=false" >> $GITHUB_OUTPUT else @@ -50,9 +48,10 @@ jobs: timeout-minutes: 30 steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 + ref: refs/pull/${{ github.event.pull_request.number }}/head - name: Install pnpm uses: pnpm/action-setup@v4 @@ -86,4 +85,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMMIT_SHA: ${{ github.event.pull_request.head.sha }} BASE_SHA: ${{ github.event.pull_request.base.sha }} - REPOSITORY: ${{ github.repository }} \ No newline at end of file + REPOSITORY: ${{ github.repository }} diff --git a/.github/workflows/dev-release.yaml b/.github/workflows/create-dev-pypi-package.yaml similarity index 97% rename from .github/workflows/dev-release.yaml rename to .github/workflows/create-dev-pypi-package.yaml index 0b45420c6..b592a8371 100644 --- a/.github/workflows/dev-release.yaml +++ b/.github/workflows/create-dev-pypi-package.yaml @@ -15,7 +15,7 @@ jobs: version: ${{ steps.current_version.outputs.version }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 with: @@ -62,7 +62,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Download dist artifact uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/create-release-candidate-branch.yaml b/.github/workflows/create-release-branch.yaml similarity index 99% rename from .github/workflows/create-release-candidate-branch.yaml rename to .github/workflows/create-release-branch.yaml index e3fcd9e2b..7891a845d 100644 --- a/.github/workflows/create-release-candidate-branch.yaml +++ b/.github/workflows/create-release-branch.yaml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/create-release-draft.yaml similarity index 100% rename from .github/workflows/release.yaml rename to .github/workflows/create-release-draft.yaml diff --git a/.github/workflows/devtools-python.yaml b/.github/workflows/devtools-python-check.yaml similarity index 93% rename from .github/workflows/devtools-python.yaml rename to .github/workflows/devtools-python-check.yaml index 49ec4c0fe..f0893e99d 100644 --- a/.github/workflows/devtools-python.yaml +++ b/.github/workflows/devtools-python-check.yaml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/lint-and-format.yaml b/.github/workflows/lint-and-format.yaml index 3b6bf1538..d9904b0e0 100644 --- a/.github/workflows/lint-and-format.yaml +++ b/.github/workflows/lint-and-format.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: token: ${{ secrets.GITHUB_TOKEN }} ref: ${{ github.event.pull_request.head.ref }} diff --git a/.github/workflows/pr-playwright-deploy.yaml b/.github/workflows/pr-playwright-deploy-forks.yaml similarity index 99% rename from .github/workflows/pr-playwright-deploy.yaml rename to .github/workflows/pr-playwright-deploy-forks.yaml index 19bb28253..660fee77f 100644 --- a/.github/workflows/pr-playwright-deploy.yaml +++ b/.github/workflows/pr-playwright-deploy-forks.yaml @@ -30,7 +30,7 @@ jobs: echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}" - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Get PR Number id: pr diff --git a/.github/workflows/pr-storybook-comment.yaml b/.github/workflows/pr-storybook-comment.yaml deleted file mode 100644 index 53691d826..000000000 --- a/.github/workflows/pr-storybook-comment.yaml +++ /dev/null @@ -1,126 +0,0 @@ -name: PR Storybook Comment - -on: - workflow_run: - workflows: ['Chromatic'] - types: [requested, completed] - -jobs: - comment-storybook: - runs-on: ubuntu-latest - if: >- - github.repository == 'Comfy-Org/ComfyUI_frontend' - && github.event.workflow_run.event == 'pull_request' - && startsWith(github.event.workflow_run.head_branch, 'version-bump-') - permissions: - pull-requests: write - actions: read - steps: - - name: Get PR number - id: pr - uses: actions/github-script@v7 - with: - script: | - const { data: pullRequests } = await github.rest.pulls.list({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`, - }); - - if (pullRequests.length === 0) { - console.log('No open PR found for this branch'); - return null; - } - - return pullRequests[0].number; - - - name: Log when no PR found - if: steps.pr.outputs.result == 'null' - run: | - echo "âš ī¸ No open PR found for branch: ${{ github.event.workflow_run.head_branch }}" - echo "Workflow run ID: ${{ github.event.workflow_run.id }}" - echo "Repository: ${{ github.event.workflow_run.repository.full_name }}" - echo "Event: ${{ github.event.workflow_run.event }}" - - - name: Get workflow run details - if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' - id: workflow-run - uses: actions/github-script@v7 - with: - script: | - const run = await github.rest.actions.getWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - - return { - conclusion: run.data.conclusion, - html_url: run.data.html_url - }; - - - name: Get completion time - id: completion-time - run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT - - - name: Comment PR - Storybook Started - if: steps.pr.outputs.result != 'null' && github.event.action == 'requested' - uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0 - with: - issue-number: ${{ steps.pr.outputs.result }} - body-includes: '' - comment-author: 'github-actions[bot]' - edit-mode: replace - body: | - - ## 🎨 Storybook Build Status - - comfy-loading-gif **Build is starting...** - - ⏰ Started at: ${{ steps.completion-time.outputs.time }} UTC - - ### 🚀 Building Storybook - - đŸ“Ļ Installing dependencies... - - 🔧 Building Storybook components... - - 🎨 Running Chromatic visual tests... - - --- - âąī¸ Please wait while the Storybook build is in progress... - - - name: Comment PR - Storybook Complete - if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' - uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0 - with: - issue-number: ${{ steps.pr.outputs.result }} - body-includes: '' - comment-author: 'github-actions[bot]' - edit-mode: replace - body: | - - ## 🎨 Storybook Build Status - - ${{ - fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && '✅' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && 'â­ī¸' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && 'đŸšĢ' - || '❌' - }} **${{ - fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && 'Build completed successfully!' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && 'Build skipped.' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && 'Build cancelled.' - || 'Build failed!' - }}** - - ⏰ Completed at: ${{ steps.completion-time.outputs.time }} UTC - - ### 🔗 Links - - [📊 View Workflow Run](${{ fromJSON(steps.workflow-run.outputs.result).html_url }}) - - --- - ${{ - fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && '🎉 Your Storybook is ready for review!' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && 'â„šī¸ Chromatic was skipped for this PR.' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && 'â„šī¸ The Chromatic run was cancelled.' - || 'âš ī¸ Please check the workflow logs for error details.' - }} diff --git a/.github/workflows/pr-storybook-deploy-forks.yaml b/.github/workflows/pr-storybook-deploy-forks.yaml new file mode 100644 index 000000000..da27867c4 --- /dev/null +++ b/.github/workflows/pr-storybook-deploy-forks.yaml @@ -0,0 +1,90 @@ +name: PR Storybook Deploy (Forks) + +on: + workflow_run: + workflows: ['Storybook and Chromatic CI'] + types: [requested, completed] + +env: + DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p' + +jobs: + deploy-and-comment-forked-pr: + runs-on: ubuntu-latest + if: | + github.repository == 'Comfy-Org/ComfyUI_frontend' && + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.head_repository != null && + github.event.workflow_run.repository != null && + github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name + permissions: + pull-requests: write + actions: read + steps: + - name: Log workflow trigger info + run: | + echo "Repository: ${{ github.repository }}" + echo "Event: ${{ github.event.workflow_run.event }}" + echo "Head repo: ${{ github.event.workflow_run.head_repository.full_name || 'null' }}" + echo "Base repo: ${{ github.event.workflow_run.repository.full_name || 'null' }}" + echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}" + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Get PR Number + id: pr + uses: actions/github-script@v7 + with: + script: | + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + }); + + const pr = prs.find(p => p.head.sha === context.payload.workflow_run.head_sha); + + if (!pr) { + console.log('No PR found for SHA:', context.payload.workflow_run.head_sha); + return null; + } + + console.log(`Found PR #${pr.number} from fork: ${context.payload.workflow_run.head_repository.full_name}`); + return pr.number; + + - name: Handle Storybook Start + if: steps.pr.outputs.result != 'null' && github.event.action == 'requested' + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh + ./scripts/cicd/pr-storybook-deploy-and-comment.sh \ + "${{ steps.pr.outputs.result }}" \ + "${{ github.event.workflow_run.head_branch }}" \ + "starting" \ + "$(date -u '${{ env.DATE_FORMAT }}')" + + - name: Download and Deploy Storybook + if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success' + uses: actions/download-artifact@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + name: storybook-static + path: storybook-static + + - name: Handle Storybook Completion + if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + GITHUB_TOKEN: ${{ github.token }} + WORKFLOW_CONCLUSION: ${{ github.event.workflow_run.conclusion }} + WORKFLOW_URL: ${{ github.event.workflow_run.html_url }} + run: | + chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh + ./scripts/cicd/pr-storybook-deploy-and-comment.sh \ + "${{ steps.pr.outputs.result }}" \ + "${{ github.event.workflow_run.head_branch }}" \ + "completed" \ No newline at end of file diff --git a/.github/workflows/storybook-and-chromatic-ci.yaml b/.github/workflows/storybook-and-chromatic-ci.yaml new file mode 100644 index 000000000..bfac96530 --- /dev/null +++ b/.github/workflows/storybook-and-chromatic-ci.yaml @@ -0,0 +1,231 @@ +name: Storybook and Chromatic CI + +# - [Automate Chromatic with GitHub Actions â€ĸ Chromatic docs]( https://www.chromatic.com/docs/github-actions/ ) + +on: + workflow_dispatch: # Allow manual triggering + pull_request: + branches: [main] + +jobs: + # Post starting comment for non-forked PRs + comment-on-pr-start: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false + permissions: + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Post starting comment + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh + ./scripts/cicd/pr-storybook-deploy-and-comment.sh \ + "${{ github.event.pull_request.number }}" \ + "${{ github.head_ref }}" \ + "starting" \ + "$(date -u '+%m/%d/%Y, %I:%M:%S %p')" + + # Build Storybook for all PRs (free Cloudflare deployment) + storybook-build: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + outputs: + conclusion: ${{ steps.job-status.outputs.conclusion }} + workflow-url: ${{ steps.workflow-url.outputs.url }} + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Cache tool outputs + uses: actions/cache@v4 + with: + path: | + .cache + storybook-static + tsconfig.tsbuildinfo + key: storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', '*.config.*', '.storybook/**/*') }} + restore-keys: | + storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}- + storybook-cache-${{ runner.os }}- + storybook-tools-cache-${{ runner.os }}- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build Storybook + run: pnpm build-storybook + + - name: Set job status + id: job-status + if: always() + run: | + echo "conclusion=${{ job.status }}" >> $GITHUB_OUTPUT + + - name: Get workflow URL + id: workflow-url + if: always() + run: | + echo "url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_OUTPUT + + - name: Upload Storybook build + if: success() && github.event.pull_request.head.repo.fork == false + uses: actions/upload-artifact@v4 + with: + name: storybook-static + path: storybook-static/ + retention-days: 7 + + # Chromatic deployment only for version-bump-* branches or manual triggers + chromatic-deployment: + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'version-bump-')) + outputs: + conclusion: ${{ steps.job-status.outputs.conclusion }} + workflow-url: ${{ steps.workflow-url.outputs.url }} + chromatic-build-url: ${{ steps.chromatic.outputs.buildUrl }} + chromatic-storybook-url: ${{ steps.chromatic.outputs.storybookUrl }} + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 # Required for Chromatic baseline + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Cache tool outputs + uses: actions/cache@v4 + with: + path: | + .cache + storybook-static + tsconfig.tsbuildinfo + key: storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', '*.config.*', '.storybook/**/*') }} + restore-keys: | + storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}- + storybook-cache-${{ runner.os }}- + storybook-tools-cache-${{ runner.os }}- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build Storybook and run Chromatic + id: chromatic + uses: chromaui/action@latest + with: + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + buildScriptName: build-storybook + autoAcceptChanges: 'main' # Auto-accept changes on main branch + exitOnceUploaded: true # Don't wait for UI tests to complete + onlyChanged: true # Only capture changed stories + + - name: Set job status + id: job-status + if: always() + run: | + echo "conclusion=${{ job.status }}" >> $GITHUB_OUTPUT + + - name: Get workflow URL + id: workflow-url + if: always() + run: | + echo "url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_OUTPUT + + # Deploy and comment for non-forked PRs only + deploy-and-comment: + needs: [storybook-build] + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false && always() + permissions: + pull-requests: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Download Storybook build + if: needs.storybook-build.outputs.conclusion == 'success' + uses: actions/download-artifact@v4 + with: + name: storybook-static + path: storybook-static + + - name: Make deployment script executable + run: chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh + + - name: Deploy Storybook and comment on PR + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + GITHUB_TOKEN: ${{ github.token }} + WORKFLOW_CONCLUSION: ${{ needs.storybook-build.outputs.conclusion }} + WORKFLOW_URL: ${{ needs.storybook-build.outputs.workflow-url }} + run: | + ./scripts/cicd/pr-storybook-deploy-and-comment.sh \ + "${{ github.event.pull_request.number }}" \ + "${{ github.head_ref }}" \ + "completed" + + # Update comment with Chromatic URLs for version-bump branches + update-comment-with-chromatic: + needs: [chromatic-deployment, deploy-and-comment] + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false && startsWith(github.head_ref, 'version-bump-') && needs.chromatic-deployment.outputs.chromatic-build-url != '' + permissions: + pull-requests: write + steps: + - name: Update comment with Chromatic URLs + uses: actions/github-script@v7 + with: + script: | + const buildUrl = '${{ needs.chromatic-deployment.outputs.chromatic-build-url }}'; + const storybookUrl = '${{ needs.chromatic-deployment.outputs.chromatic-storybook-url }}'; + + // Find the existing Storybook comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ github.event.pull_request.number }} + }); + + const storybookComment = comments.find(comment => + comment.body.includes('') + ); + + if (storybookComment && buildUrl && storybookUrl) { + // Append Chromatic info to existing comment + const updatedBody = storybookComment.body.replace( + /---\n(.*)$/s, + `---\n### 🎨 Chromatic Visual Tests\n- 📊 [View Chromatic Build](${buildUrl})\n- 📚 [View Chromatic Storybook](${storybookUrl})\n\n$1` + ); + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: storybookComment.id, + body: updatedBody + }); + } diff --git a/.github/workflows/test-ui.yaml b/.github/workflows/tests-ci.yaml similarity index 98% rename from .github/workflows/test-ui.yaml rename to .github/workflows/tests-ci.yaml index 640615d99..01b180b4a 100644 --- a/.github/workflows/test-ui.yaml +++ b/.github/workflows/tests-ci.yaml @@ -15,14 +15,14 @@ jobs: playwright-version: ${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }} steps: - name: Checkout ComfyUI - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: 'comfyanonymous/ComfyUI' path: 'ComfyUI' ref: master - name: Checkout ComfyUI_frontend - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: 'Comfy-Org/ComfyUI_frontend' path: 'ComfyUI_frontend' @@ -250,7 +250,7 @@ jobs: if: ${{ !cancelled() }} steps: - name: Checkout ComfyUI_frontend - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: 'Comfy-Org/ComfyUI_frontend' path: 'ComfyUI_frontend' @@ -306,7 +306,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Get start time id: start-time @@ -333,7 +333,7 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Download all playwright reports uses: actions/download-artifact@v4 diff --git a/.github/workflows/update-registry-types.yaml b/.github/workflows/update-comfy-registry-api-types.yaml similarity index 98% rename from .github/workflows/update-registry-types.yaml rename to .github/workflows/update-comfy-registry-api-types.yaml index c7d31d1d9..41a4db9ab 100644 --- a/.github/workflows/update-registry-types.yaml +++ b/.github/workflows/update-comfy-registry-api-types.yaml @@ -16,7 +16,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 @@ -50,7 +50,7 @@ jobs: comfy-api-repo-${{ runner.os }}- - name: Checkout comfy-api repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: Comfy-Org/comfy-api path: comfy-api diff --git a/.github/workflows/update-manager-types.yaml b/.github/workflows/update-comfyui-manager-api-types.yaml similarity index 98% rename from .github/workflows/update-manager-types.yaml rename to .github/workflows/update-comfyui-manager-api-types.yaml index de5b799da..7e307dfda 100644 --- a/.github/workflows/update-manager-types.yaml +++ b/.github/workflows/update-comfyui-manager-api-types.yaml @@ -17,7 +17,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 @@ -51,7 +51,7 @@ jobs: comfyui-manager-repo-${{ runner.os }}- - name: Checkout ComfyUI-Manager repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: Comfy-Org/ComfyUI-Manager path: ComfyUI-Manager diff --git a/.github/workflows/update-electron-types.yaml b/.github/workflows/update-electron-types.yaml index 96f85f6b0..45d959b86 100644 --- a/.github/workflows/update-electron-types.yaml +++ b/.github/workflows/update-electron-types.yaml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 diff --git a/.github/workflows/i18n-custom-nodes.yaml b/.github/workflows/update-locales-for-given-custom-node-repository.yaml similarity index 98% rename from .github/workflows/i18n-custom-nodes.yaml rename to .github/workflows/update-locales-for-given-custom-node-repository.yaml index 959d01739..74d0267cf 100644 --- a/.github/workflows/i18n-custom-nodes.yaml +++ b/.github/workflows/update-locales-for-given-custom-node-repository.yaml @@ -22,13 +22,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout ComfyUI - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: comfyanonymous/ComfyUI path: ComfyUI ref: master - name: Checkout ComfyUI_frontend - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: Comfy-Org/ComfyUI_frontend path: ComfyUI_frontend @@ -37,7 +37,7 @@ jobs: mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/ - name: Checkout custom node repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: ${{ inputs.owner }}/${{ inputs.repository }} path: 'ComfyUI/custom_nodes/${{ inputs.repository }}' diff --git a/.github/workflows/i18n.yaml b/.github/workflows/update-locales.yaml similarity index 97% rename from .github/workflows/i18n.yaml rename to .github/workflows/update-locales.yaml index 566a335b5..2871209a1 100644 --- a/.github/workflows/i18n.yaml +++ b/.github/workflows/update-locales.yaml @@ -14,7 +14,8 @@ jobs: if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-')) runs-on: ubuntu-latest steps: - - uses: Comfy-Org/ComfyUI_frontend_setup_action@v3 + - name: Setup Frontend + uses: ./.github/actions/setup-frontend - name: Cache tool outputs uses: actions/cache@v4 diff --git a/.github/workflows/i18n-node-defs.yaml b/.github/workflows/update-node-definitions-locales.yaml similarity index 95% rename from .github/workflows/i18n-node-defs.yaml rename to .github/workflows/update-node-definitions-locales.yaml index d9105a4ac..71cb417d4 100644 --- a/.github/workflows/i18n-node-defs.yaml +++ b/.github/workflows/update-node-definitions-locales.yaml @@ -13,7 +13,8 @@ jobs: update-locales: runs-on: ubuntu-latest steps: - - uses: Comfy-Org/ComfyUI_frontend_setup_action@v3 + - name: Setup Frontend + uses: ./.github/actions/setup-frontend - name: Install Playwright Browsers run: pnpm exec playwright install chromium --with-deps working-directory: ComfyUI_frontend diff --git a/.github/workflows/test-browser-exp.yaml b/.github/workflows/update-playwright-expectations.yaml similarity index 92% rename from .github/workflows/test-browser-exp.yaml rename to .github/workflows/update-playwright-expectations.yaml index e174e89c3..4f643cad8 100644 --- a/.github/workflows/test-browser-exp.yaml +++ b/.github/workflows/update-playwright-expectations.yaml @@ -10,7 +10,10 @@ jobs: runs-on: ubuntu-latest if: github.event.label.name == 'New Browser Test Expectations' steps: - - uses: Comfy-Org/ComfyUI_frontend_setup_action@v3 + - name: Checkout workflow repo + uses: actions/checkout@v5 + - name: Setup Frontend + uses: ./.github/actions/setup-frontend - name: Cache Playwright browsers uses: actions/cache@v4 with: diff --git a/.github/workflows/json-validate.yaml b/.github/workflows/validate-json.yaml similarity index 86% rename from .github/workflows/json-validate.yaml rename to .github/workflows/validate-json.yaml index a29499528..2986d23ed 100644 --- a/.github/workflows/json-validate.yaml +++ b/.github/workflows/validate-json.yaml @@ -10,6 +10,6 @@ jobs: json-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Validate JSON syntax run: ./scripts/cicd/check-json.sh diff --git a/.github/workflows/version-bump.yaml b/.github/workflows/version-bump.yaml index 77021e5c7..4073729db 100644 --- a/.github/workflows/version-bump.yaml +++ b/.github/workflows/version-bump.yaml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 diff --git a/.github/workflows/vitest.yaml b/.github/workflows/vitest-tests.yaml similarity index 97% rename from .github/workflows/vitest.yaml rename to .github/workflows/vitest-tests.yaml index cba1dbe05..ea49d4a62 100644 --- a/.github/workflows/vitest.yaml +++ b/.github/workflows/vitest-tests.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 diff --git a/.npmrc b/.npmrc index ae90f7051..1b28e47ce 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ ignore-workspace-root-check=true +catalog-mode=prefer diff --git a/browser_tests/README.md b/browser_tests/README.md index 4954ba9fd..500d665c2 100644 --- a/browser_tests/README.md +++ b/browser_tests/README.md @@ -16,7 +16,7 @@ Without this flag, parallel tests will conflict and fail randomly. ### ComfyUI devtools -ComfyUI_devtools is now included in this repository under `tools/devtools/`. During CI/CD, these files are automatically copied to the `custom_nodes` directory. +ComfyUI_devtools is included in this repository under `tools/devtools/`. During CI/CD, these files are automatically copied to the `custom_nodes` directory. _ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing._ For local development, copy the devtools files to your ComfyUI installation: @@ -84,7 +84,7 @@ UI mode features: - **Console/Network Tabs**: View logs and API calls at each step - **Attachments Tab**: View all snapshots with expected and actual images -![Playwright UI Mode](https://github.com/user-attachments/assets/c158c93f-b39a-44c5-a1a1-e0cc975ee9f2) +![Playwright UI Mode](https://github.com/user-attachments/assets/9b9cb09f-6da7-4fa0-81df-2effceced755) For CI or headless testing: diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png index 0a7817cd9..d19e6a4b8 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png index 955397d55..feb588707 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png b/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png index 8fd456924..a61beb8fd 100644 Binary files a/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png and b/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png index e23bdb168..4cc6eb1a9 100644 Binary files a/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png index c4db539bc..9660dea43 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png index 685637384..541ca18a6 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png index 0f660f23e..838145eb4 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png index 22f5c69f4..59dd9288d 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png index 7eeef693d..99a372c0b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png index 64d140449..a055bb525 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png index ceb84698c..a507a9ca2 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png index 5997ea26e..8209a045c 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png index 0df00782f..618b2d871 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png index c61bcbc46..1c19eb714 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png index 2bbaeaa64..4636050fd 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png index 57bca67fa..b332c5f13 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png index ac5b685ca..f4624a45b 100644 Binary files a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png and b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ diff --git a/package.json b/package.json index bdfdc0378..ee0f556cf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.28.3", + "version": "1.28.4", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0b9e0d06b..50bb8f97a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,114 @@ packages: - apps/** - packages/** +catalog: + # Core frameworks + typescript: ^5.9.2 + vue: ^3.5.13 + + # Build tools + '@nx/eslint': 21.4.1 + '@nx/playwright': 21.4.1 + '@nx/storybook': 21.4.1 + '@nx/vite': 21.4.1 + nx: 21.4.1 + tsx: ^4.15.6 + vite: ^5.4.19 + '@vitejs/plugin-vue': ^5.1.4 + 'vite-plugin-dts': ^4.5.4 + vue-tsc: ^3.0.7 + + # Testing + 'happy-dom': ^15.11.0 + jsdom: ^26.1.0 + '@pinia/testing': ^0.1.5 + '@playwright/test': ^1.52.0 + '@vitest/coverage-v8': ^3.2.4 + '@vitest/ui': ^3.0.0 + vitest: ^3.2.4 + '@vue/test-utils': ^2.4.6 + + # Linting & Formatting + '@eslint/js': ^9.35.0 + eslint: ^9.34.0 + 'eslint-config-prettier': ^10.1.8 + 'eslint-plugin-prettier': ^5.5.4 + 'eslint-plugin-storybook': ^9.1.6 + 'eslint-plugin-unused-imports': ^4.2.0 + 'eslint-plugin-vue': ^10.4.0 + globals: ^15.9.0 + '@intlify/eslint-plugin-vue-i18n': ^4.1.0 + prettier: ^3.3.2 + 'typescript-eslint': ^8.44.0 + 'vue-eslint-parser': ^10.2.0 + + # Vue ecosystem + '@sentry/vue': ^8.48.0 + '@vueuse/core': ^11.0.0 + '@vueuse/integrations': ^13.9.0 + 'vite-plugin-html': ^3.2.2 + 'vite-plugin-vue-devtools': ^7.7.6 + pinia: ^2.1.7 + 'vue-i18n': ^9.14.3 + 'vue-router': ^4.4.3 + vuefire: ^3.2.1 + + # PrimeVue UI framework + '@primeuix/forms': 0.0.2 + '@primeuix/styled': 0.3.2 + '@primeuix/utils': ^0.3.2 + '@primevue/core': ^4.2.5 + '@primevue/forms': ^4.2.5 + '@primevue/icons': 4.2.5 + '@primevue/themes': ^4.2.5 + primeicons: ^7.0.0 + primevue: ^4.2.5 + + # Tailwind CSS and design + '@iconify/json': ^2.2.380 + '@iconify-json/lucide': ^1.1.178 + '@iconify/tailwind': ^1.1.3 + '@tailwindcss/vite': ^4.1.12 + tailwindcss: ^4.1.12 + 'tailwindcss-primeui': ^0.6.1 + 'tw-animate-css': ^1.3.8 + 'unplugin-icons': ^0.22.0 + 'unplugin-vue-components': ^0.28.0 + + # Storybook + '@storybook/addon-docs': ^9.1.1 + storybook: ^9.1.6 + '@storybook/vue3': ^9.1.1 + '@storybook/vue3-vite': ^9.1.1 + + # Data and validation + algoliasearch: ^5.21.0 + firebase: ^11.6.0 + yjs: ^13.6.27 + zod: ^3.23.8 + 'zod-validation-error': ^3.3.0 + + # Dev tools + dotenv: ^16.4.5 + husky: ^9.0.11 + jiti: 2.4.2 + knip: ^5.62.0 + 'lint-staged': ^15.2.7 + + # Type definitions + '@types/fs-extra': ^11.0.4 + '@types/jsdom': ^21.1.7 + '@types/node': ^20.14.8 + '@types/semver': ^7.7.0 + '@types/three': ^0.169.0 + 'vue-component-type-helpers': ^3.0.7 + 'zod-to-json-schema': ^3.24.1 + + # i18n + '@alloc/quick-lru': ^5.2.0 + '@lobehub/i18n-cli': ^1.25.1 + '@trivago/prettier-plugin-sort-imports': ^5.2.0 + ignoredBuiltDependencies: - '@firebase/util' - protobufjs diff --git a/scripts/cicd/pr-storybook-deploy-and-comment.sh b/scripts/cicd/pr-storybook-deploy-and-comment.sh new file mode 100755 index 000000000..a2a1d37e9 --- /dev/null +++ b/scripts/cicd/pr-storybook-deploy-and-comment.sh @@ -0,0 +1,247 @@ +#!/bin/bash +set -e + +# Deploy Storybook to Cloudflare Pages and comment on PR +# Usage: ./pr-storybook-deploy-and-comment.sh [start_time] + +# Input validation +# Validate PR number is numeric +case "$1" in + ''|*[!0-9]*) + echo "Error: PR_NUMBER must be numeric" >&2 + exit 1 + ;; +esac +PR_NUMBER="$1" + +# Sanitize and validate branch name (allow alphanumeric, dots, dashes, underscores, slashes) +BRANCH_NAME=$(echo "$2" | sed 's/[^a-zA-Z0-9._/-]//g') +if [ -z "$BRANCH_NAME" ]; then + echo "Error: Invalid or empty branch name" >&2 + exit 1 +fi + +# Validate status parameter +STATUS="${3:-completed}" +case "$STATUS" in + starting|completed) ;; + *) + echo "Error: STATUS must be 'starting' or 'completed'" >&2 + exit 1 + ;; +esac + +START_TIME="${4:-$(date -u '+%m/%d/%Y, %I:%M:%S %p')}" + +# Required environment variables +: "${GITHUB_TOKEN:?GITHUB_TOKEN is required}" +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}" + +# Cloudflare variables only required for deployment +if [ "$STATUS" = "completed" ]; then + : "${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN is required for deployment}" + : "${CLOUDFLARE_ACCOUNT_ID:?CLOUDFLARE_ACCOUNT_ID is required for deployment}" +fi + +# Configuration +COMMENT_MARKER="" + +# Install wrangler if not available (output to stderr for debugging) +if ! command -v wrangler > /dev/null 2>&1; then + echo "Installing wrangler v4..." >&2 + npm install -g wrangler@^4.0.0 >&2 || { + echo "Failed to install wrangler" >&2 + echo "failed" + return + } +fi + +# Deploy Storybook report, WARN: ensure inputs are sanitized before calling this function +deploy_storybook() { + dir="$1" + branch="$2" + + [ ! -d "$dir" ] && echo "failed" && return + + project="comfy-storybook" + + echo "Deploying Storybook to project $project on branch $branch..." >&2 + + # Try deployment up to 3 times + i=1 + while [ $i -le 3 ]; do + echo "Deployment attempt $i of 3..." >&2 + # Branch is already sanitized, use it directly + if output=$(wrangler pages deploy "$dir" \ + --project-name="$project" \ + --branch="$branch" 2>&1); then + + # Extract URL from output (improved regex for valid URL characters) + url=$(echo "$output" | grep -oE 'https://[a-zA-Z0-9.-]+\.pages\.dev\S*' | head -1) + result="${url:-https://${branch}.${project}.pages.dev}" + echo "Success! URL: $result" >&2 + echo "$result" # Only this goes to stdout for capture + return + else + echo "Deployment failed on attempt $i: $output" >&2 + fi + [ $i -lt 3 ] && sleep 10 + i=$((i + 1)) + done + + echo "failed" +} + +# Post or update GitHub comment +post_comment() { + body="$1" + temp_file=$(mktemp) + echo "$body" > "$temp_file" + + if command -v gh > /dev/null 2>&1; then + # Find existing comment ID + existing=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \ + --jq ".[] | select(.body | contains(\"$COMMENT_MARKER\")) | .id" | head -1) + + if [ -n "$existing" ]; then + # Update specific comment by ID + gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$existing" \ + --field body="$(cat "$temp_file")" + else + gh pr comment "$PR_NUMBER" --body-file "$temp_file" + fi + else + echo "GitHub CLI not available, outputting comment:" + cat "$temp_file" + fi + + rm -f "$temp_file" +} + +# Main execution +if [ "$STATUS" = "starting" ]; then + # Check if this is a version-bump branch + IS_VERSION_BUMP="false" + if echo "$BRANCH_NAME" | grep -q "^version-bump-"; then + IS_VERSION_BUMP="true" + fi + + # Post starting comment with appropriate message + if [ "$IS_VERSION_BUMP" = "true" ]; then + comment=$(cat < **Build is starting...** + +⏰ Started at: $START_TIME UTC + +### 🚀 Building Storybook +- đŸ“Ļ Installing dependencies... +- 🔧 Building Storybook components... +- 🎨 Running Chromatic visual tests... + +--- +âąī¸ Please wait while the Storybook build is in progress... +EOF +) + else + comment=$(cat < **Build is starting...** + +⏰ Started at: $START_TIME UTC + +### 🚀 Building Storybook +- đŸ“Ļ Installing dependencies... +- 🔧 Building Storybook components... +- 🌐 Preparing deployment to Cloudflare Pages... + +--- +âąī¸ Please wait while the Storybook build is in progress... +EOF +) + fi + post_comment "$comment" + +elif [ "$STATUS" = "completed" ]; then + # Deploy and post completion comment + # Convert branch name to Cloudflare-compatible format (lowercase, only alphanumeric and dashes) + cloudflare_branch=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | \ + sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g') + + echo "Looking for Storybook build in: $(pwd)/storybook-static" + + # Deploy Storybook if build exists + deployment_url="Not deployed" + if [ -d "storybook-static" ]; then + echo "Found Storybook build, deploying..." + url=$(deploy_storybook "storybook-static" "$cloudflare_branch") + if [ "$url" != "failed" ] && [ -n "$url" ]; then + deployment_url="[View Storybook]($url)" + else + deployment_url="Deployment failed" + fi + else + echo "Storybook build not found at storybook-static" + fi + + # Get workflow conclusion from environment or default to success + WORKFLOW_CONCLUSION="${WORKFLOW_CONCLUSION:-success}" + WORKFLOW_URL="${WORKFLOW_URL:-}" + + # Generate completion comment based on conclusion + if [ "$WORKFLOW_CONCLUSION" = "success" ]; then + status_icon="✅" + status_text="Build completed successfully!" + footer_text="🎉 Your Storybook is ready for review!" + elif [ "$WORKFLOW_CONCLUSION" = "skipped" ]; then + status_icon="â­ī¸" + status_text="Build skipped." + footer_text="â„šī¸ Chromatic was skipped for this PR." + elif [ "$WORKFLOW_CONCLUSION" = "cancelled" ]; then + status_icon="đŸšĢ" + status_text="Build cancelled." + footer_text="â„šī¸ The Chromatic run was cancelled." + else + status_icon="❌" + status_text="Build failed!" + footer_text="âš ī¸ Please check the workflow logs for error details." + fi + + comment="$COMMENT_MARKER +## 🎨 Storybook Build Status + +$status_icon **$status_text** + +⏰ Completed at: $(date -u '+%m/%d/%Y, %I:%M:%S %p') UTC + +### 🔗 Links +- [📊 View Workflow Run]($WORKFLOW_URL)" + + # Add deployment status + if [ "$deployment_url" != "Not deployed" ]; then + if [ "$deployment_url" = "Deployment failed" ]; then + comment="$comment +- ❌ Storybook deployment failed" + elif [ "$WORKFLOW_CONCLUSION" = "success" ]; then + comment="$comment +- 🎨 $deployment_url" + else + comment="$comment +- âš ī¸ Build failed - $deployment_url" + fi + elif [ "$WORKFLOW_CONCLUSION" != "success" ]; then + comment="$comment +- â­ī¸ Storybook deployment skipped (build did not succeed)" + fi + + comment="$comment + +--- +$footer_text" + + post_comment "$comment" +fi \ No newline at end of file diff --git a/src/platform/workflow/persistence/composables/useWorkflowPersistence.ts b/src/platform/workflow/persistence/composables/useWorkflowPersistence.ts index 0e3d92b15..f6f5f1828 100644 --- a/src/platform/workflow/persistence/composables/useWorkflowPersistence.ts +++ b/src/platform/workflow/persistence/composables/useWorkflowPersistence.ts @@ -104,7 +104,7 @@ export function useWorkflowPersistence() { } const paths = openWorkflows.value - .filter((workflow) => workflow?.isPersisted && !workflow.isModified) + .filter((workflow) => workflow?.isPersisted) .map((workflow) => workflow.path) const activeIndex = openWorkflows.value.findIndex( (workflow) => workflow.path === activeWorkflow.value?.path diff --git a/src/renderer/core/canvas/useCanvasInteractions.ts b/src/renderer/core/canvas/useCanvasInteractions.ts index a32d5e864..46ce4ae46 100644 --- a/src/renderer/core/canvas/useCanvasInteractions.ts +++ b/src/renderer/core/canvas/useCanvasInteractions.ts @@ -30,6 +30,15 @@ export function useCanvasInteractions() { * when appropriate (e.g., Ctrl+wheel for zoom in standard mode) */ const handleWheel = (event: WheelEvent) => { + // Check if the wheel event is from an element that wants to capture wheel events + const target = event.target as HTMLElement + const captureElement = target?.closest('[data-capture-wheel="true"]') + + if (captureElement) { + // Element wants to capture wheel events, don't forward to canvas + return + } + // In standard mode, Ctrl+wheel should go to canvas for zoom if (isStandardNavMode.value && (event.ctrlKey || event.metaKey)) { forwardEventToCanvas(event) @@ -72,6 +81,15 @@ export function useCanvasInteractions() { const forwardEventToCanvas = ( event: WheelEvent | PointerEvent | MouseEvent ) => { + // Check if the wheel event is from an element that wants to capture wheel events + const target = event.target as HTMLElement + const captureElement = target?.closest('[data-capture-wheel="true"]') + + if (captureElement) { + // Element wants to capture wheel events, don't forward to canvas + return + } + const canvasEl = app.canvas?.canvas if (!canvasEl) return event.preventDefault() diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index a61111bcf..bced99338 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -211,7 +211,8 @@ const isSelected = computed(() => { }) // Use execution state composable -const { executing, progress } = useNodeExecutionState(() => nodeData.id) +const nodeLocatorId = computed(() => getLocatorIdFromNodeData(nodeData)) +const { executing, progress } = useNodeExecutionState(nodeLocatorId) // Direct access to execution store for error state const executionStore = useExecutionStore() diff --git a/src/renderer/extensions/vueNodes/components/NodeHeader.vue b/src/renderer/extensions/vueNodes/components/NodeHeader.vue index 4f84ff531..252736a26 100644 --- a/src/renderer/extensions/vueNodes/components/NodeHeader.vue +++ b/src/renderer/extensions/vueNodes/components/NodeHeader.vue @@ -4,12 +4,12 @@
-
+
+
+ + + +
- - -
- - - -
diff --git a/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts b/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts index e69d21f56..b13e9e23f 100644 --- a/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts +++ b/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts @@ -1,11 +1,77 @@ import type { TooltipDirectivePassThroughOptions } from 'primevue' -import { type MaybeRef, type Ref, computed, unref } from 'vue' +import { type MaybeRef, type Ref, computed, ref, unref } from 'vue' import type { SafeWidgetData } from '@/composables/graph/useGraphNodeManager' import { st } from '@/i18n' import { useSettingStore } from '@/platform/settings/settingStore' import { useNodeDefStore } from '@/stores/nodeDefStore' import { normalizeI18nKey } from '@/utils/formatUtil' +import { cn } from '@/utils/tailwindUtil' + +/** + * Hide all visible tooltips by dispatching mouseleave events + * + * + * IMPORTANT: this escape is needed for many reason due to primevue's directive tooltip system. + * We cannot use PT to conditonally render the tooltips because the entire PT object only run + * once during the intialization of the directive not every mount/unmount. + * Once the directive is constructed its no longer reactive in the traditional sense. + * We have to use something non destructive like mouseevents to dismiss the tooltip. + * + * TODO: use a better tooltip component like RekaUI for vue nodes specifically. + */ + +const tooltipsTemporarilyDisabled = ref(false) + +const hideTooltipsGlobally = () => { + // Get all visible tooltip elements + const tooltips = document.querySelectorAll('.p-tooltip') + + // Early return if no tooltips are visible + if (tooltips.length === 0) return + + tooltips.forEach((tooltipEl) => { + const tooltipId = tooltipEl.id + if (!tooltipId) return + + // Find the target element that owns this tooltip + const targetElements = document.querySelectorAll('[data-pd-tooltip="true"]') + for (const targetEl of targetElements) { + if ((targetEl as any).$_ptooltipId === tooltipId) { + ;(targetEl as HTMLElement).dispatchEvent( + new MouseEvent('mouseleave', { bubbles: true }) + ) + break + } + } + }) + + // Disable tooltips temporarily after hiding (for drag operations) + tooltipsTemporarilyDisabled.value = true +} + +/** + * Re-enable tooltips after pointer interaction ends + */ +const handlePointerUp = () => { + tooltipsTemporarilyDisabled.value = false +} + +// Global tooltip hiding system +const globalTooltipState = { listenersSetup: false } + +function setupGlobalTooltipHiding() { + if (globalTooltipState.listenersSetup) return + + document.addEventListener('pointerdown', hideTooltipsGlobally) + document.addEventListener('pointerup', handlePointerUp) + window.addEventListener('wheel', hideTooltipsGlobally, { + capture: true, //Need this to bypass the event layer from Litegraph + passive: true + }) + + globalTooltipState.listenersSetup = true +} /** * Composable for managing Vue node tooltips @@ -18,6 +84,9 @@ export function useNodeTooltips( const nodeDefStore = useNodeDefStore() const settingsStore = useSettingStore() + // Setup global pointerdown listener once + setupGlobalTooltipHiding() + // Check if tooltips are globally enabled const tooltipsEnabled = computed(() => settingsStore.get('Comfy.EnableTooltips') @@ -76,6 +145,7 @@ export function useNodeTooltips( /** * Create tooltip configuration object for v-tooltip directive + * Components wrap this in computed() for reactivity */ const createTooltipConfig = (text: string) => { const tooltipDelay = settingsStore.get('LiteGraph.Node.TooltipDelay') @@ -84,21 +154,33 @@ export function useNodeTooltips( const config: { value: string showDelay: number + hideDelay: number disabled: boolean appendTo?: HTMLElement pt?: TooltipDirectivePassThroughOptions } = { value: tooltipText, showDelay: tooltipDelay as number, - disabled: !tooltipsEnabled.value || !tooltipText, + hideDelay: 0, // Immediate hiding + disabled: + !tooltipsEnabled.value || + !tooltipText || + tooltipsTemporarilyDisabled.value, // this reactive value works but only on next mount, + // so if the tooltip is already visible changing this will not hide it pt: { text: { class: - 'bg-pure-white dark-theme:bg-charcoal-800 border dark-theme:border-slate-300 rounded-md px-4 py-2 text-charcoal-700 dark-theme:text-pure-white text-sm font-normal leading-tight max-w-75 shadow-none' + 'border-sand-100 bg-pure-white dark-theme:bg-charcoal-800 border dark-theme:border-slate-300 rounded-md px-4 py-2 text-charcoal-700 dark-theme:text-pure-white text-sm font-normal leading-tight max-w-75 shadow-none' }, - arrow: { - class: 'before:border-slate-300' - } + arrow: ({ context }) => ({ + class: cn( + context?.top && 'border-t-sand-100 dark-theme:border-t-slate-300', + context?.bottom && + 'border-b-sand-100 dark-theme:border-b-slate-300', + context?.left && 'border-l-sand-100 dark-theme:border-l-slate-300', + context?.right && 'border-r-sand-100 dark-theme:border-r-slate-300' + ) + }) } } diff --git a/src/renderer/extensions/vueNodes/execution/useNodeExecutionState.ts b/src/renderer/extensions/vueNodes/execution/useNodeExecutionState.ts index aa4867db9..8debe0666 100644 --- a/src/renderer/extensions/vueNodes/execution/useNodeExecutionState.ts +++ b/src/renderer/extensions/vueNodes/execution/useNodeExecutionState.ts @@ -9,27 +9,27 @@ import { useExecutionStore } from '@/stores/executionStore' * Provides reactive access to execution state and progress for a specific node * by injecting execution data from the parent GraphCanvas provider. * - * @param nodeIdMaybe - The ID of the node to track execution state for + * @param nodeLocatorIdMaybe - Locator ID (root or subgraph scoped) of the node to track * @returns Object containing reactive execution state and progress */ export const useNodeExecutionState = ( - nodeIdMaybe: MaybeRefOrGetter + nodeLocatorIdMaybe: MaybeRefOrGetter ) => { - const nodeId = toValue(nodeIdMaybe) - const { uniqueExecutingNodeIdStrings, nodeProgressStates } = - storeToRefs(useExecutionStore()) + const locatorId = computed(() => toValue(nodeLocatorIdMaybe) ?? '') + const { nodeLocationProgressStates } = storeToRefs(useExecutionStore()) - const executing = computed(() => { - return uniqueExecutingNodeIdStrings.value.has(nodeId) + const progressState = computed(() => { + const id = locatorId.value + return id ? nodeLocationProgressStates.value[id] : undefined }) + const executing = computed(() => progressState.value?.state === 'running') + const progress = computed(() => { - const state = nodeProgressStates.value[nodeId] - return state?.max > 0 ? state.value / state.max : undefined + const state = progressState.value + return state && state.max > 0 ? state.value / state.max : undefined }) - const progressState = computed(() => nodeProgressStates.value[nodeId]) - const progressPercentage = computed(() => { const prog = progress.value return prog !== undefined ? Math.round(prog * 100) : undefined diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.vue index 7f3995f9e..3ffe47102 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.vue @@ -23,6 +23,7 @@ onBlur: handleBlur } }" + data-capture-wheel="true" @update:model-value="onChange" @click.stop @keydown.stop diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDefault.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDefault.vue index 4ac007669..4242c13ae 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDefault.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDefault.vue @@ -10,6 +10,7 @@ :pt="{ option: 'text-xs' }" + data-capture-wheel="true" @update:model-value="onChange" /> diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue index bd7ac7818..a2ec58de6 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue @@ -8,6 +8,7 @@ :placeholder="placeholder || widget.name || ''" size="small" rows="3" + data-capture-wheel="true" @update:model-value="onChange" /> diff --git a/src/types/vue-shim.d.ts b/src/types/vue-shim.d.ts deleted file mode 100644 index 664e5764a..000000000 --- a/src/types/vue-shim.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -// vue-shim.d.ts -declare module '*.vue' { - import { DefineComponent } from 'vue' - const component: DefineComponent<{}, {}, any> - export default component -}