Merge branch 'main' into sno-storybook--settings-panel
67
.github/actions/setup-frontend/action.yml
vendored
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
57
.github/workflows/chromatic.yaml
vendored
@@ -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
|
||||
|
||||
13
.github/workflows/claude-pr-review.yml
vendored
@@ -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 }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
|
||||
@@ -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:
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
2
.github/workflows/lint-and-format.yaml
vendored
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
126
.github/workflows/pr-storybook-comment.yaml
vendored
@@ -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: '<!-- STORYBOOK_BUILD_STATUS -->'
|
||||
comment-author: 'github-actions[bot]'
|
||||
edit-mode: replace
|
||||
body: |
|
||||
<!-- STORYBOOK_BUILD_STATUS -->
|
||||
## 🎨 Storybook Build Status
|
||||
|
||||
<img alt='comfy-loading-gif' src='https://github.com/user-attachments/assets/755c86ee-e445-4ea8-bc2c-cca85df48686' width='14px' height='14px' style='vertical-align: middle; margin-right: 4px;' /> **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: '<!-- STORYBOOK_BUILD_STATUS -->'
|
||||
comment-author: 'github-actions[bot]'
|
||||
edit-mode: replace
|
||||
body: |
|
||||
<!-- STORYBOOK_BUILD_STATUS -->
|
||||
## 🎨 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.'
|
||||
}}
|
||||
90
.github/workflows/pr-storybook-deploy-forks.yaml
vendored
Normal file
@@ -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"
|
||||
231
.github/workflows/storybook-and-chromatic-ci.yaml
vendored
Normal file
@@ -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('<!-- STORYBOOK_BUILD_STATUS -->')
|
||||
);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
2
.github/workflows/update-electron-types.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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 }}'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -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
|
||||
2
.github/workflows/version-bump.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||

|
||||

|
||||
|
||||
For CI or headless testing:
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
247
scripts/cicd/pr-storybook-deploy-and-comment.sh
Executable file
@@ -0,0 +1,247 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Deploy Storybook to Cloudflare Pages and comment on PR
|
||||
# Usage: ./pr-storybook-deploy-and-comment.sh <pr_number> <branch_name> <status> [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="<!-- STORYBOOK_BUILD_STATUS -->"
|
||||
|
||||
# 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 <<EOF
|
||||
$COMMENT_MARKER
|
||||
## 🎨 Storybook Build Status
|
||||
|
||||
<img alt='loading' src='https://github.com/user-attachments/assets/755c86ee-e445-4ea8-bc2c-cca85df48686' width='14px' height='14px'/> **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 <<EOF
|
||||
$COMMENT_MARKER
|
||||
## 🎨 Storybook Build Status
|
||||
|
||||
<img alt='loading' src='https://github.com/user-attachments/assets/755c86ee-e445-4ea8-bc2c-cca85df48686' width='14px' height='14px'/> **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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="lg-node-header p-4 rounded-t-2xl w-full cursor-move"
|
||||
class="lg-node-header p-4 rounded-t-2xl cursor-move"
|
||||
:style="headerStyle"
|
||||
:data-testid="`node-header-${nodeData?.id || ''}`"
|
||||
@dblclick="handleDoubleClick"
|
||||
>
|
||||
<div class="flex items-center justify-between relative">
|
||||
<div class="flex items-center justify-between gap-2.5 relative">
|
||||
<!-- Collapse/Expand Button -->
|
||||
<button
|
||||
v-show="!readonly"
|
||||
@@ -43,24 +43,22 @@
|
||||
data-testid="node-pin-indicator"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!readonly" class="flex items-center lod-toggle shrink-0">
|
||||
<IconButton
|
||||
v-if="isSubgraphNode"
|
||||
size="sm"
|
||||
type="transparent"
|
||||
class="text-stone-200 dark-theme:text-slate-300"
|
||||
data-testid="subgraph-enter-button"
|
||||
title="Enter Subgraph"
|
||||
@click.stop="handleEnterSubgraph"
|
||||
@dblclick.stop
|
||||
>
|
||||
<i class="pi pi-external-link"></i>
|
||||
</IconButton>
|
||||
</div>
|
||||
<LODFallback />
|
||||
</div>
|
||||
|
||||
<!-- Title Buttons -->
|
||||
<div v-if="!readonly" class="flex items-center lod-toggle">
|
||||
<IconButton
|
||||
v-if="isSubgraphNode"
|
||||
size="sm"
|
||||
type="transparent"
|
||||
class="text-stone-200 dark-theme:text-slate-300"
|
||||
data-testid="subgraph-enter-button"
|
||||
title="Enter Subgraph"
|
||||
@click.stop="handleEnterSubgraph"
|
||||
@dblclick.stop
|
||||
>
|
||||
<i class="pi pi-external-link"></i>
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string>
|
||||
nodeLocatorIdMaybe: MaybeRefOrGetter<string | undefined>
|
||||
) => {
|
||||
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
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
onBlur: handleBlur
|
||||
}
|
||||
}"
|
||||
data-capture-wheel="true"
|
||||
@update:model-value="onChange"
|
||||
@click.stop
|
||||
@keydown.stop
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
:pt="{
|
||||
option: 'text-xs'
|
||||
}"
|
||||
data-capture-wheel="true"
|
||||
@update:model-value="onChange"
|
||||
/>
|
||||
</WidgetLayoutField>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
:placeholder="placeholder || widget.name || ''"
|
||||
size="small"
|
||||
rows="3"
|
||||
data-capture-wheel="true"
|
||||
@update:model-value="onChange"
|
||||
/>
|
||||
<LODFallback />
|
||||
|
||||
6
src/types/vue-shim.d.ts
vendored
@@ -1,6 +0,0 @@
|
||||
// vue-shim.d.ts
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||