Compare commits
145 Commits
vue-nodes/
...
bl-test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c004a2b8bd | ||
|
|
d0aee031e9 | ||
|
|
cec1de0147 | ||
|
|
b4976c1ddc | ||
|
|
1611c7a224 | ||
|
|
d01081dab4 | ||
|
|
e5d4d07d32 | ||
|
|
f086377307 | ||
|
|
687b9e659c | ||
|
|
da0d51311b | ||
|
|
e314d9cbd9 | ||
|
|
95baf8d2f1 | ||
|
|
f951e07cea | ||
|
|
023e466dba | ||
|
|
abd6823744 | ||
|
|
c4c0e52e64 | ||
|
|
295332dc46 | ||
|
|
5c498348b8 | ||
|
|
8133bd4b7b | ||
|
|
fd12591756 | ||
|
|
b3c939ff15 | ||
|
|
0801778f60 | ||
|
|
8ffe63f54e | ||
|
|
893409dfc8 | ||
|
|
df2fda6077 | ||
|
|
4f5bbe0605 | ||
|
|
a975e50f1b | ||
|
|
a17c74fa0c | ||
|
|
5e625a5002 | ||
|
|
002fac0232 | ||
|
|
7e115543fa | ||
|
|
80d75bb164 | ||
|
|
d59885839a | ||
|
|
cbb0f765b8 | ||
|
|
726a2fbbc9 | ||
|
|
553b5aa02b | ||
|
|
2ff0d951ed | ||
|
|
1f88925144 | ||
|
|
250433a91a | ||
|
|
eb664f47af | ||
|
|
bc85d4e87b | ||
|
|
7585444ce6 | ||
|
|
a886798a10 | ||
|
|
37975e4eac | ||
|
|
a41b8a6d4f | ||
|
|
b264685052 | ||
|
|
78d0ea6fa5 | ||
|
|
ea4e57b602 | ||
|
|
4789d86fe8 | ||
|
|
09e7d1040e | ||
|
|
dfa1cbba4f | ||
|
|
08220d50d9 | ||
|
|
045232a99b | ||
|
|
fb07941700 | ||
|
|
6866e1277a | ||
|
|
ff5d0923ca | ||
|
|
6b59f839e0 | ||
|
|
6a01b08ebf | ||
|
|
ede43c5e5c | ||
|
|
0483630f82 | ||
|
|
15cffe9d9e | ||
|
|
f5b949762d | ||
|
|
6786d8e4fb | ||
|
|
71ca28a46f | ||
|
|
4ff18fd7f0 | ||
|
|
45a46be513 | ||
|
|
631746939a | ||
|
|
4f8e820c51 | ||
|
|
b8d8193a38 | ||
|
|
2b57291756 | ||
|
|
62897c669b | ||
|
|
9918914b9d | ||
|
|
e601bcb300 | ||
|
|
853038859b | ||
|
|
186e065528 | ||
|
|
237e807fc9 | ||
|
|
27ab355f9c | ||
|
|
4c8c4a1ad4 | ||
|
|
e3bb29ceb8 | ||
|
|
ca312fd1ea | ||
|
|
bbff9c8217 | ||
|
|
6349ceee6c | ||
|
|
8c6ee026c0 | ||
|
|
668500cfa5 | ||
|
|
07fbe7267e | ||
|
|
fe09f88ea3 | ||
|
|
96a663704f | ||
|
|
11cb525545 | ||
|
|
4eb13ca17c | ||
|
|
6017fc43a6 | ||
|
|
6e2d86520b | ||
|
|
6e2a3a0d07 | ||
|
|
c7325c4da9 | ||
|
|
d146a7896a | ||
|
|
6b166a9d2f | ||
|
|
c145fd9df1 | ||
|
|
c04af09956 | ||
|
|
ac107b45ea | ||
|
|
f6a115e182 | ||
|
|
aa7a99f4e3 | ||
|
|
8804755ffa | ||
|
|
f28ebcac19 | ||
|
|
bfef8d7d4b | ||
|
|
90bf8dc74a | ||
|
|
6f878abea4 | ||
|
|
b1917c6469 | ||
|
|
bf054113f8 | ||
|
|
4604bbd669 | ||
|
|
77ee20597f | ||
|
|
8ae50d140d | ||
|
|
63f194b394 | ||
|
|
2b8abca6d8 | ||
|
|
48d01745cd | ||
|
|
90b1b47dd0 | ||
|
|
0e01ca0a98 | ||
|
|
cf093d02a7 | ||
|
|
1845708ddb | ||
|
|
c588f2f457 | ||
|
|
29ad47d20f | ||
|
|
9aeeab9a9f | ||
|
|
6645b3917f | ||
|
|
4ec6223189 | ||
|
|
dfcbbec2b9 | ||
|
|
c051c3a507 | ||
|
|
54cbf42a84 | ||
|
|
ef7575b8d6 | ||
|
|
68845ce33a | ||
|
|
46f4ce3890 | ||
|
|
5df037dfc4 | ||
|
|
3bc25b7aeb | ||
|
|
08fe2829d4 | ||
|
|
e70ddea684 | ||
|
|
038f86fe84 | ||
|
|
cb0dab6cdc | ||
|
|
568be0c44c | ||
|
|
6ea021d595 | ||
|
|
7245213ed6 | ||
|
|
b72e22f6be | ||
|
|
5f045b335d | ||
|
|
44e470488d | ||
|
|
ca220440b2 | ||
|
|
2e64c64ac7 | ||
|
|
d561f315d3 | ||
|
|
080334754c | ||
|
|
169d7404fe |
@@ -67,9 +67,9 @@ This is critical for better file inspection:
|
|||||||
|
|
||||||
Use git locally for much faster analysis:
|
Use git locally for much faster analysis:
|
||||||
|
|
||||||
1. Get list of changed files: `git diff --name-only "origin/$BASE_BRANCH" > changed_files.txt`
|
1. Get list of changed files: `git diff --name-only "$BASE_SHA" > changed_files.txt`
|
||||||
2. Get the full diff: `git diff "origin/$BASE_BRANCH" > pr_diff.txt`
|
2. Get the full diff: `git diff "$BASE_SHA" > pr_diff.txt`
|
||||||
3. Get detailed file changes with status: `git diff --name-status "origin/$BASE_BRANCH" > file_changes.txt`
|
3. Get detailed file changes with status: `git diff --name-status "$BASE_SHA" > file_changes.txt`
|
||||||
|
|
||||||
### Step 1.5: Create Analysis Cache
|
### Step 1.5: Create Analysis Cache
|
||||||
|
|
||||||
|
|||||||
@@ -33,4 +33,3 @@ DISABLE_VUE_PLUGINS=false
|
|||||||
# Algolia credentials required for developing with the new custom node manager.
|
# Algolia credentials required for developing with the new custom node manager.
|
||||||
ALGOLIA_APP_ID=4E0RO38HS8
|
ALGOLIA_APP_ID=4E0RO38HS8
|
||||||
ALGOLIA_API_KEY=684d998c36b67a9a9fce8fc2d8860579
|
ALGOLIA_API_KEY=684d998c36b67a9a9fce8fc2d8860579
|
||||||
|
|
||||||
|
|||||||
@@ -7,3 +7,21 @@ c53f197de2a3e0fa66b16dedc65c131235c1c4b6
|
|||||||
|
|
||||||
# Reorganize renderer components into domain-driven folder structure
|
# Reorganize renderer components into domain-driven folder structure
|
||||||
c8a83a9caede7bdb5f8598c5492b07d08c339d49
|
c8a83a9caede7bdb5f8598c5492b07d08c339d49
|
||||||
|
|
||||||
|
# Domain-driven design (DDD) refactors - September 2025
|
||||||
|
# These commits reorganized the codebase into domain-driven architecture
|
||||||
|
|
||||||
|
# [refactor] Improve renderer domain organization (#5552)
|
||||||
|
6349ceee6c0a57fc7992e85635def9b6e22eaeb2
|
||||||
|
|
||||||
|
# [refactor] Improve settings domain organization (#5550)
|
||||||
|
4c8c4a1ad4f53354f700a33ea1b95262aeda2719
|
||||||
|
|
||||||
|
# [refactor] Improve workflow domain organization (#5584)
|
||||||
|
ca312fd1eab540cc4ddc0e3d244d38b3858574f0
|
||||||
|
|
||||||
|
# [refactor] Move thumbnail functionality to renderer/core domain (#5586)
|
||||||
|
e3bb29ceb8174b8bbca9e48ec7d42cd540f40efa
|
||||||
|
|
||||||
|
# [refactor] Improve updates/notifications domain organization (#5590)
|
||||||
|
27ab355f9c73415dc39f4d3f512b02308f847801
|
||||||
|
|||||||
2
.gitattributes
vendored
@@ -13,4 +13,4 @@
|
|||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
src/types/comfyRegistryTypes.ts linguist-generated=true
|
src/types/comfyRegistryTypes.ts linguist-generated=true
|
||||||
src/types/generatedManagerTypes.ts linguist-generated=true
|
src/workbench/extensions/manager/types/generatedManagerTypes.ts linguist-generated=true
|
||||||
|
|||||||
110
.github/workflows/backport.yaml
vendored
@@ -4,10 +4,25 @@ on:
|
|||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [closed, labeled]
|
types: [closed, labeled]
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
pr_number:
|
||||||
|
description: 'PR number to backport'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
force_rerun:
|
||||||
|
description: 'Force rerun even if backports exist'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
backport:
|
backport:
|
||||||
if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'needs-backport')
|
if: >
|
||||||
|
(github.event_name == 'pull_request_target' &&
|
||||||
|
github.event.pull_request.merged == true &&
|
||||||
|
contains(github.event.pull_request.labels.*.name, 'needs-backport')) ||
|
||||||
|
github.event_name == 'workflow_dispatch'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -15,6 +30,35 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Validate inputs for manual triggers
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
run: |
|
||||||
|
# Validate PR number format
|
||||||
|
if ! [[ "${{ inputs.pr_number }}" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "::error::Invalid PR number format. Must be a positive integer."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate PR exists and is merged
|
||||||
|
if ! gh pr view "${{ inputs.pr_number }}" --json merged >/dev/null 2>&1; then
|
||||||
|
echo "::error::PR #${{ inputs.pr_number }} not found or inaccessible."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MERGED=$(gh pr view "${{ inputs.pr_number }}" --json merged --jq '.merged')
|
||||||
|
if [ "$MERGED" != "true" ]; then
|
||||||
|
echo "::error::PR #${{ inputs.pr_number }} is not merged. Only merged PRs can be backported."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate PR has needs-backport label
|
||||||
|
if ! gh pr view "${{ inputs.pr_number }}" --json labels --jq '.labels[].name' | grep -q "needs-backport"; then
|
||||||
|
echo "::error::PR #${{ inputs.pr_number }} does not have 'needs-backport' label."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -29,7 +73,7 @@ jobs:
|
|||||||
id: check-existing
|
id: check-existing
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
|
||||||
run: |
|
run: |
|
||||||
# Check for existing backport PRs for this PR number
|
# Check for existing backport PRs for this PR number
|
||||||
EXISTING_BACKPORTS=$(gh pr list --state all --search "backport-${PR_NUMBER}-to" --json title,headRefName,baseRefName | jq -r '.[].headRefName')
|
EXISTING_BACKPORTS=$(gh pr list --state all --search "backport-${PR_NUMBER}-to" --json title,headRefName,baseRefName | jq -r '.[].headRefName')
|
||||||
@@ -39,6 +83,13 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# For manual triggers with force_rerun, proceed anyway
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.force_rerun }}" = "true" ]; then
|
||||||
|
echo "skip=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "::warning::Force rerun requested - existing backports will be updated"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Found existing backport PRs:"
|
echo "Found existing backport PRs:"
|
||||||
echo "$EXISTING_BACKPORTS"
|
echo "$EXISTING_BACKPORTS"
|
||||||
echo "skip=true" >> $GITHUB_OUTPUT
|
echo "skip=true" >> $GITHUB_OUTPUT
|
||||||
@@ -50,8 +101,17 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# Extract version labels (e.g., "1.24", "1.22")
|
# Extract version labels (e.g., "1.24", "1.22")
|
||||||
VERSIONS=""
|
VERSIONS=""
|
||||||
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
|
|
||||||
for label in $(echo "$LABELS" | jq -r '.[].name'); do
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
# For manual triggers, get labels from the PR
|
||||||
|
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
|
||||||
|
else
|
||||||
|
# For automatic triggers, extract from PR event
|
||||||
|
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
|
||||||
|
LABELS=$(echo "$LABELS" | jq -r '.[].name')
|
||||||
|
fi
|
||||||
|
|
||||||
|
for label in $LABELS; do
|
||||||
# Match version labels like "1.24" (major.minor only)
|
# Match version labels like "1.24" (major.minor only)
|
||||||
if [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then
|
if [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then
|
||||||
# Validate the branch exists before adding to list
|
# Validate the branch exists before adding to list
|
||||||
@@ -75,12 +135,20 @@ jobs:
|
|||||||
if: steps.check-existing.outputs.skip != 'true'
|
if: steps.check-existing.outputs.skip != 'true'
|
||||||
id: backport
|
id: backport
|
||||||
env:
|
env:
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
||||||
MERGE_COMMIT: ${{ github.event.pull_request.merge_commit_sha }}
|
|
||||||
run: |
|
run: |
|
||||||
FAILED=""
|
FAILED=""
|
||||||
SUCCESS=""
|
SUCCESS=""
|
||||||
|
|
||||||
|
# Get PR data for manual triggers
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,mergeCommit)
|
||||||
|
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
||||||
|
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
|
||||||
|
else
|
||||||
|
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||||
|
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
||||||
|
fi
|
||||||
|
|
||||||
for version in ${{ steps.versions.outputs.versions }}; do
|
for version in ${{ steps.versions.outputs.versions }}; do
|
||||||
echo "::group::Backporting to core/${version}"
|
echo "::group::Backporting to core/${version}"
|
||||||
@@ -133,11 +201,18 @@ jobs:
|
|||||||
if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success
|
if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
|
||||||
run: |
|
run: |
|
||||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
# Get PR data for manual triggers
|
||||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,author)
|
||||||
|
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
||||||
|
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
|
||||||
|
else
|
||||||
|
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||||
|
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
||||||
|
fi
|
||||||
|
|
||||||
for backport in ${{ steps.backport.outputs.success }}; do
|
for backport in ${{ steps.backport.outputs.success }}; do
|
||||||
IFS=':' read -r version branch <<< "${backport}"
|
IFS=':' read -r version branch <<< "${backport}"
|
||||||
|
|
||||||
@@ -166,9 +241,16 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json author,mergeCommit)
|
||||||
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
PR_NUMBER="${{ inputs.pr_number }}"
|
||||||
|
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
|
||||||
|
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
|
||||||
|
else
|
||||||
|
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||||
|
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
||||||
|
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
||||||
|
fi
|
||||||
|
|
||||||
for failure in ${{ steps.backport.outputs.failed }}; do
|
for failure in ${{ steps.backport.outputs.failed }}; do
|
||||||
IFS=':' read -r version reason conflicts <<< "${failure}"
|
IFS=':' read -r version reason conflicts <<< "${failure}"
|
||||||
|
|||||||
9
.github/workflows/claude-pr-review.yml
vendored
@@ -47,6 +47,7 @@ jobs:
|
|||||||
needs: wait-for-ci
|
needs: wait-for-ci
|
||||||
if: needs.wait-for-ci.outputs.should-proceed == 'true'
|
if: needs.wait-for-ci.outputs.should-proceed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -69,19 +70,17 @@ jobs:
|
|||||||
pnpm install -g typescript @vue/compiler-sfc
|
pnpm install -g typescript @vue/compiler-sfc
|
||||||
|
|
||||||
- name: Run Claude PR Review
|
- name: Run Claude PR Review
|
||||||
uses: anthropics/claude-code-action@main
|
uses: anthropics/claude-code-action@v1.0.6
|
||||||
with:
|
with:
|
||||||
label_trigger: "claude-review"
|
label_trigger: "claude-review"
|
||||||
direct_prompt: |
|
prompt: |
|
||||||
Read the file .claude/commands/comprehensive-pr-review.md and follow ALL the instructions exactly.
|
Read the file .claude/commands/comprehensive-pr-review.md and follow ALL the instructions exactly.
|
||||||
|
|
||||||
CRITICAL: You must post individual inline comments using the gh api commands shown in the file.
|
CRITICAL: You must post individual inline comments using the gh api commands shown in the file.
|
||||||
DO NOT create a summary comment.
|
DO NOT create a summary comment.
|
||||||
Each issue must be posted as a separate inline comment on the specific line of code.
|
Each issue must be posted as a separate inline comment on the specific line of code.
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
max_turns: 256
|
claude_args: "--max-turns 256 --allowedTools 'Bash(git:*),Bash(gh api:*),Bash(gh pr:*),Bash(gh repo:*),Bash(jq:*),Bash(echo:*),Read,Write,Edit,Glob,Grep,WebFetch'"
|
||||||
timeout_minutes: 30
|
|
||||||
allowed_tools: "Bash(git:*),Bash(gh api:*),Bash(gh pr:*),Bash(gh repo:*),Bash(jq:*),Bash(echo:*),Read,Write,Edit,Glob,Grep,WebFetch"
|
|
||||||
env:
|
env:
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
9
.github/workflows/i18n-custom-nodes.yaml
vendored
@@ -32,11 +32,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repository: Comfy-Org/ComfyUI_frontend
|
repository: Comfy-Org/ComfyUI_frontend
|
||||||
path: ComfyUI_frontend
|
path: ComfyUI_frontend
|
||||||
- name: Checkout ComfyUI_devtools
|
- name: Copy ComfyUI_devtools from frontend repo
|
||||||
uses: actions/checkout@v4
|
run: |
|
||||||
with:
|
mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools
|
||||||
repository: Comfy-Org/ComfyUI_devtools
|
cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/
|
||||||
path: ComfyUI/custom_nodes/ComfyUI_devtools
|
|
||||||
- name: Checkout custom node repository
|
- name: Checkout custom node repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
139
.github/workflows/publish-frontend-types.yaml
vendored
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
name: Publish Frontend Types
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Version to publish (e.g., 1.26.7)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
dist_tag:
|
||||||
|
description: 'npm dist-tag to use'
|
||||||
|
required: true
|
||||||
|
default: latest
|
||||||
|
type: string
|
||||||
|
ref:
|
||||||
|
description: 'Git ref to checkout (commit SHA, tag, or branch)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
dist_tag:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: latest
|
||||||
|
ref:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: publish-frontend-types-${{ github.workflow }}-${{ inputs.version }}-${{ inputs.dist_tag }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish_types_manual:
|
||||||
|
name: Publish @comfyorg/comfyui-frontend-types
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Validate inputs
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
SEMVER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$'
|
||||||
|
if [[ ! "$VERSION" =~ $SEMVER_REGEX ]]; then
|
||||||
|
echo "::error title=Invalid version::Version '$VERSION' must follow semantic versioning (x.y.z[-suffix][+build])" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Determine ref to checkout
|
||||||
|
id: resolve_ref
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
REF="${{ inputs.ref }}"
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
if [ -n "$REF" ]; then
|
||||||
|
if ! git check-ref-format --allow-onelevel "$REF"; then
|
||||||
|
echo "::error title=Invalid ref::Ref '$REF' fails git check-ref-format validation." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "ref=$REF" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "ref=refs/tags/v$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.resolve_ref.outputs.ref }}
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version: 'lts/*'
|
||||||
|
cache: 'pnpm'
|
||||||
|
registry-url: https://registry.npmjs.org
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
env:
|
||||||
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||||
|
|
||||||
|
- name: Build types
|
||||||
|
run: pnpm build:types
|
||||||
|
|
||||||
|
- name: Verify version matches input
|
||||||
|
id: verify
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
PKG_VERSION=$(node -p "require('./package.json').version")
|
||||||
|
TYPES_PKG_VERSION=$(node -p "require('./dist/package.json').version")
|
||||||
|
if [ "$PKG_VERSION" != "${{ inputs.version }}" ]; then
|
||||||
|
echo "Error: package.json version $PKG_VERSION does not match input ${{ inputs.version }}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$TYPES_PKG_VERSION" != "${{ inputs.version }}" ]; then
|
||||||
|
echo "Error: dist/package.json version $TYPES_PKG_VERSION does not match input ${{ inputs.version }}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Check if version already on npm
|
||||||
|
id: check_npm
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
NAME=$(node -p "require('./dist/package.json').name")
|
||||||
|
VER="${{ steps.verify.outputs.version }}"
|
||||||
|
STATUS=0
|
||||||
|
OUTPUT=$(npm view "${NAME}@${VER}" --json 2>&1) || STATUS=$?
|
||||||
|
if [ "$STATUS" -eq 0 ]; then
|
||||||
|
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "::warning title=Already published::${NAME}@${VER} already exists on npm. Skipping publish."
|
||||||
|
else
|
||||||
|
if echo "$OUTPUT" | grep -q "E404"; then
|
||||||
|
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::error title=Registry lookup failed::$OUTPUT" >&2
|
||||||
|
exit "$STATUS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Publish package
|
||||||
|
if: steps.check_npm.outputs.exists == 'false'
|
||||||
|
run: pnpm publish --access public --tag "${{ inputs.dist_tag }}" --no-git-checks
|
||||||
|
working-directory: dist
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
42
.github/workflows/release.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
|
is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Download dist artifact
|
- name: Download dist artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -98,7 +98,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Download dist artifact
|
- name: Download dist artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -126,34 +126,8 @@ jobs:
|
|||||||
|
|
||||||
publish_types:
|
publish_types:
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
uses: ./.github/workflows/publish-frontend-types.yaml
|
||||||
steps:
|
with:
|
||||||
- uses: actions/checkout@v4
|
version: ${{ needs.build.outputs.version }}
|
||||||
- name: Install pnpm
|
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
uses: pnpm/action-setup@v4
|
secrets: inherit
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 'lts/*'
|
|
||||||
cache: 'pnpm'
|
|
||||||
registry-url: https://registry.npmjs.org
|
|
||||||
|
|
||||||
- name: Cache tool outputs
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
.cache
|
|
||||||
tsconfig.tsbuildinfo
|
|
||||||
dist
|
|
||||||
key: types-tools-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
types-tools-cache-${{ runner.os }}-
|
|
||||||
|
|
||||||
- run: pnpm install --frozen-lockfile
|
|
||||||
- run: pnpm build:types
|
|
||||||
- name: Publish package
|
|
||||||
run: pnpm publish --access public
|
|
||||||
working-directory: dist
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|||||||
25
.github/workflows/test-ui.yaml
vendored
@@ -27,12 +27,10 @@ jobs:
|
|||||||
repository: 'Comfy-Org/ComfyUI_frontend'
|
repository: 'Comfy-Org/ComfyUI_frontend'
|
||||||
path: 'ComfyUI_frontend'
|
path: 'ComfyUI_frontend'
|
||||||
|
|
||||||
- name: Checkout ComfyUI_devtools
|
- name: Copy ComfyUI_devtools from frontend repo
|
||||||
uses: actions/checkout@v4
|
run: |
|
||||||
with:
|
mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools
|
||||||
repository: 'Comfy-Org/ComfyUI_devtools'
|
cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/
|
||||||
path: 'ComfyUI/custom_nodes/ComfyUI_devtools'
|
|
||||||
ref: 'd05fd48dd787a4192e16802d4244cfcc0e2f9684'
|
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -229,7 +227,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Run Playwright tests (${{ matrix.browser }})
|
- name: Run Playwright tests (${{ matrix.browser }})
|
||||||
id: playwright
|
id: playwright
|
||||||
run: npx playwright test --project=${{ matrix.browser }} --reporter=html
|
run: |
|
||||||
|
# Run tests with both HTML and JSON reporters
|
||||||
|
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
||||||
|
npx playwright test --project=${{ matrix.browser }} \
|
||||||
|
--reporter=list \
|
||||||
|
--reporter=html \
|
||||||
|
--reporter=json
|
||||||
working-directory: ComfyUI_frontend
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
@@ -275,7 +279,12 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Merge into HTML Report
|
- name: Merge into HTML Report
|
||||||
run: npx playwright merge-reports --reporter html ./all-blob-reports
|
run: |
|
||||||
|
# Generate HTML report
|
||||||
|
npx playwright merge-reports --reporter=html ./all-blob-reports
|
||||||
|
# Generate JSON report separately with explicit output path
|
||||||
|
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
||||||
|
npx playwright merge-reports --reporter=json ./all-blob-reports
|
||||||
working-directory: ComfyUI_frontend
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
- name: Upload HTML report
|
- name: Upload HTML report
|
||||||
|
|||||||
2
.github/workflows/update-electron-types.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- name: Get new version
|
- name: Get new version
|
||||||
id: get-version
|
id: get-version
|
||||||
run: |
|
run: |
|
||||||
NEW_VERSION=$(pnpm list @comfyorg/comfyui-electron-types --json --depth=0 | jq -r '.[0].version')
|
NEW_VERSION=$(pnpm list @comfyorg/comfyui-electron-types --json --depth=0 | jq -r '.[0].dependencies."@comfyorg/comfyui-electron-types".version')
|
||||||
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
|
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
|||||||
6
.gitignore
vendored
@@ -44,6 +44,7 @@ components.d.ts
|
|||||||
tests-ui/data/*
|
tests-ui/data/*
|
||||||
tests-ui/ComfyUI_examples
|
tests-ui/ComfyUI_examples
|
||||||
tests-ui/workflows/examples
|
tests-ui/workflows/examples
|
||||||
|
coverage/
|
||||||
|
|
||||||
# Browser tests
|
# Browser tests
|
||||||
/test-results/
|
/test-results/
|
||||||
@@ -51,6 +52,7 @@ tests-ui/workflows/examples
|
|||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
browser_tests/**/*-win32.png
|
browser_tests/**/*-win32.png
|
||||||
|
browser_tests/local/
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
@@ -77,8 +79,8 @@ vite.config.mts.timestamp-*.mjs
|
|||||||
*storybook.log
|
*storybook.log
|
||||||
storybook-static
|
storybook-static
|
||||||
|
|
||||||
|
# MCP Servers
|
||||||
|
.playwright-mcp/*
|
||||||
|
|
||||||
.nx/cache
|
.nx/cache
|
||||||
.nx/workspace-data
|
.nx/workspace-data
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ module.exports = defineConfig({
|
|||||||
entry: 'src/locales/en',
|
entry: 'src/locales/en',
|
||||||
entryLocale: 'en',
|
entryLocale: 'en',
|
||||||
output: 'src/locales',
|
output: 'src/locales',
|
||||||
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar'],
|
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr'],
|
||||||
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
|
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
|
||||||
'latent' is the short form of 'latent space'.
|
'latent' is the short form of 'latent space'.
|
||||||
'mask' is in the context of image processing.
|
'mask' is in the context of image processing.
|
||||||
|
|||||||
@@ -15,21 +15,32 @@ const config: StorybookConfig = {
|
|||||||
async viteFinal(config) {
|
async viteFinal(config) {
|
||||||
// Use dynamic import to avoid CJS deprecation warning
|
// Use dynamic import to avoid CJS deprecation warning
|
||||||
const { mergeConfig } = await import('vite')
|
const { mergeConfig } = await import('vite')
|
||||||
|
const { default: tailwindcss } = await import('@tailwindcss/vite')
|
||||||
|
|
||||||
// Filter out any plugins that might generate import maps
|
// Filter out any plugins that might generate import maps
|
||||||
if (config.plugins) {
|
if (config.plugins) {
|
||||||
config.plugins = config.plugins.filter((plugin: any) => {
|
config.plugins = config.plugins
|
||||||
if (plugin && plugin.name && plugin.name.includes('import-map')) {
|
// Type guard: ensure we have valid plugin objects with names
|
||||||
return false
|
.filter(
|
||||||
}
|
(plugin): plugin is NonNullable<typeof plugin> & { name: string } => {
|
||||||
return true
|
return (
|
||||||
})
|
plugin !== null &&
|
||||||
|
plugin !== undefined &&
|
||||||
|
typeof plugin === 'object' &&
|
||||||
|
'name' in plugin &&
|
||||||
|
typeof plugin.name === 'string'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// Business logic: filter out import-map plugins
|
||||||
|
.filter((plugin) => !plugin.name.includes('import-map'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return mergeConfig(config, {
|
return mergeConfig(config, {
|
||||||
// Replace plugins entirely to avoid inheritance issues
|
// Replace plugins entirely to avoid inheritance issues
|
||||||
plugins: [
|
plugins: [
|
||||||
// Only include plugins we explicitly need for Storybook
|
// Only include plugins we explicitly need for Storybook
|
||||||
|
tailwindcss(),
|
||||||
Icons({
|
Icons({
|
||||||
compiler: 'vue3',
|
compiler: 'vue3',
|
||||||
customCollections: {
|
customCollections: {
|
||||||
|
|||||||
@@ -57,9 +57,8 @@
|
|||||||
|
|
||||||
/* Override Storybook's problematic & selector styles */
|
/* Override Storybook's problematic & selector styles */
|
||||||
/* Reset only the specific properties that Storybook injects */
|
/* Reset only the specific properties that Storybook injects */
|
||||||
#storybook-root li+li,
|
li+li {
|
||||||
#storybook-docs li+li {
|
margin: 0;
|
||||||
margin: inherit;
|
padding: revert-layer;
|
||||||
padding: inherit;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { definePreset } from '@primevue/themes'
|
import { definePreset } from '@primevue/themes'
|
||||||
import Aura from '@primevue/themes/aura'
|
import Aura from '@primevue/themes/aura'
|
||||||
import { setup } from '@storybook/vue3'
|
import { setup } from '@storybook/vue3'
|
||||||
import type { Preview } from '@storybook/vue3-vite'
|
import type { Preview, StoryContext, StoryFn } from '@storybook/vue3-vite'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import 'primeicons/primeicons.css'
|
import 'primeicons/primeicons.css'
|
||||||
import PrimeVue from 'primevue/config'
|
import PrimeVue from 'primevue/config'
|
||||||
@@ -9,11 +9,9 @@ import ConfirmationService from 'primevue/confirmationservice'
|
|||||||
import ToastService from 'primevue/toastservice'
|
import ToastService from 'primevue/toastservice'
|
||||||
import Tooltip from 'primevue/tooltip'
|
import Tooltip from 'primevue/tooltip'
|
||||||
|
|
||||||
import '../src/assets/css/style.css'
|
import '@/assets/css/style.css'
|
||||||
import { i18n } from '../src/i18n'
|
import { i18n } from '@/i18n'
|
||||||
import '../src/lib/litegraph/public/css/litegraph.css'
|
import '@/lib/litegraph/public/css/litegraph.css'
|
||||||
import { useWidgetStore } from '../src/stores/widgetStore'
|
|
||||||
import { useColorPaletteStore } from '../src/stores/workspace/colorPaletteStore'
|
|
||||||
|
|
||||||
const ComfyUIPreset = definePreset(Aura, {
|
const ComfyUIPreset = definePreset(Aura, {
|
||||||
semantic: {
|
semantic: {
|
||||||
@@ -25,13 +23,11 @@ const ComfyUIPreset = definePreset(Aura, {
|
|||||||
// Setup Vue app for Storybook
|
// Setup Vue app for Storybook
|
||||||
setup((app) => {
|
setup((app) => {
|
||||||
app.directive('tooltip', Tooltip)
|
app.directive('tooltip', Tooltip)
|
||||||
|
|
||||||
|
// Create Pinia instance
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
|
|
||||||
// Initialize stores
|
|
||||||
useColorPaletteStore(pinia)
|
|
||||||
useWidgetStore(pinia)
|
|
||||||
|
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
app.use(PrimeVue, {
|
app.use(PrimeVue, {
|
||||||
theme: {
|
theme: {
|
||||||
@@ -50,8 +46,8 @@ setup((app) => {
|
|||||||
app.use(ToastService)
|
app.use(ToastService)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Dark theme decorator
|
// Theme and dialog decorator
|
||||||
export const withTheme = (Story: any, context: any) => {
|
export const withTheme = (Story: StoryFn, context: StoryContext) => {
|
||||||
const theme = context.globals.theme || 'light'
|
const theme = context.globals.theme || 'light'
|
||||||
|
|
||||||
// Apply theme class to document root
|
// Apply theme class to document root
|
||||||
@@ -63,7 +59,7 @@ export const withTheme = (Story: any, context: any) => {
|
|||||||
document.body.classList.remove('dark-theme')
|
document.body.classList.remove('dark-theme')
|
||||||
}
|
}
|
||||||
|
|
||||||
return Story()
|
return Story(context.args, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
|
|||||||
70
CODEOWNERS
@@ -1,17 +1,61 @@
|
|||||||
# Admins
|
# Desktop/Electron
|
||||||
* @Comfy-Org/comfy_frontend_devs
|
/src/types/desktop/ @webfiltered
|
||||||
|
/src/constants/desktopDialogs.ts @webfiltered
|
||||||
|
/src/constants/desktopMaintenanceTasks.ts @webfiltered
|
||||||
|
/src/stores/electronDownloadStore.ts @webfiltered
|
||||||
|
/src/extensions/core/electronAdapter.ts @webfiltered
|
||||||
|
/src/views/DesktopDialogView.vue @webfiltered
|
||||||
|
/src/components/install/ @webfiltered
|
||||||
|
/src/components/maintenance/ @webfiltered
|
||||||
|
/vite.electron.config.mts @webfiltered
|
||||||
|
|
||||||
# Maintainers
|
# Common UI Components
|
||||||
*.md @Comfy-Org/comfy_maintainer
|
/src/components/chip/ @viva-jinyi
|
||||||
/tests-ui/ @Comfy-Org/comfy_maintainer
|
/src/components/card/ @viva-jinyi
|
||||||
/browser_tests/ @Comfy-Org/comfy_maintainer
|
/src/components/button/ @viva-jinyi
|
||||||
/.env_example @Comfy-Org/comfy_maintainer
|
/src/components/input/ @viva-jinyi
|
||||||
|
|
||||||
# Translations (AIGODLIKE team + shinshin86)
|
# Topbar
|
||||||
/src/locales/ @Yorha4D @KarryCharon @DorotaLuna @shinshin86 @Comfy-Org/comfy_maintainer
|
/src/components/topbar/ @pythongosssss
|
||||||
|
|
||||||
# Load 3D extension
|
# Thumbnail
|
||||||
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs
|
/src/renderer/core/thumbnail/ @pythongosssss
|
||||||
|
|
||||||
# Mask Editor extension
|
# Legacy UI
|
||||||
/src/extensions/core/maskeditor.ts @brucew4yn3rp @trsommer @Comfy-Org/comfy_frontend_devs
|
/scripts/ui/ @pythongosssss
|
||||||
|
|
||||||
|
# Link rendering
|
||||||
|
/src/renderer/core/canvas/links/ @benceruleanlu
|
||||||
|
|
||||||
|
# Node help system
|
||||||
|
/src/utils/nodeHelpUtil.ts @benceruleanlu
|
||||||
|
/src/stores/workspace/nodeHelpStore.ts @benceruleanlu
|
||||||
|
/src/services/nodeHelpService.ts @benceruleanlu
|
||||||
|
|
||||||
|
# Selection toolbox
|
||||||
|
/src/components/graph/selectionToolbox/ @Myestery
|
||||||
|
|
||||||
|
# Minimap
|
||||||
|
/src/renderer/extensions/minimap/ @jtydhr88
|
||||||
|
|
||||||
|
# Assets
|
||||||
|
/src/platform/assets/ @arjansingh
|
||||||
|
|
||||||
|
# Workflow Templates
|
||||||
|
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki
|
||||||
|
/src/components/templates/ @Myestery @christian-byrne @comfyui-wiki
|
||||||
|
|
||||||
|
# Mask Editor
|
||||||
|
/src/extensions/core/maskeditor.ts @trsommer @brucew4yn3rp
|
||||||
|
/src/extensions/core/maskEditorLayerFilenames.ts @trsommer @brucew4yn3rp
|
||||||
|
/src/extensions/core/maskEditorOld.ts @trsommer @brucew4yn3rp
|
||||||
|
|
||||||
|
# 3D
|
||||||
|
/src/extensions/core/load3d.ts @jtydhr88
|
||||||
|
/src/components/load3d/ @jtydhr88
|
||||||
|
|
||||||
|
# Manager
|
||||||
|
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer
|
||||||
|
|||||||
@@ -16,9 +16,14 @@ Without this flag, parallel tests will conflict and fail randomly.
|
|||||||
|
|
||||||
### ComfyUI devtools
|
### ComfyUI devtools
|
||||||
|
|
||||||
Clone <https://github.com/Comfy-Org/ComfyUI_devtools> to your `custom_nodes` directory.
|
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 adds additional API endpoints and nodes to ComfyUI for browser testing._
|
_ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing._
|
||||||
|
|
||||||
|
For local development, copy the devtools files to your ComfyUI installation:
|
||||||
|
```bash
|
||||||
|
cp -r tools/devtools/* /path/to/your/ComfyUI/custom_nodes/ComfyUI_devtools/
|
||||||
|
```
|
||||||
|
|
||||||
### Node.js & Playwright Prerequisites
|
### Node.js & Playwright Prerequisites
|
||||||
|
|
||||||
Ensure you have Node.js v20 or v22 installed. Then, set up the Chromium test driver:
|
Ensure you have Node.js v20 or v22 installed. Then, set up the Chromium test driver:
|
||||||
|
|||||||
1
browser_tests/assets/vueNodes/simple-triple.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"id":"4412323e-2509-4258-8abc-68ddeea8f9e1","revision":0,"last_node_id":39,"last_link_id":29,"nodes":[{"id":37,"type":"KSampler","pos":[3635.923095703125,870.237548828125],"size":[428,437],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":null},{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":null},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":null},{"localized_name":"latent_image","name":"latent_image","type":"LATENT","link":null},{"localized_name":"seed","name":"seed","type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"steps","name":"steps","type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"cfg","name":"cfg","type":"FLOAT","widget":{"name":"cfg"},"link":null},{"localized_name":"sampler_name","name":"sampler_name","type":"COMBO","widget":{"name":"sampler_name"},"link":null},{"localized_name":"scheduler","name":"scheduler","type":"COMBO","widget":{"name":"scheduler"},"link":null},{"localized_name":"denoise","name":"denoise","type":"FLOAT","widget":{"name":"denoise"},"link":null}],"outputs":[{"localized_name":"LATENT","name":"LATENT","type":"LATENT","links":null}],"properties":{"Node name for S&R":"KSampler"},"widgets_values":[0,"randomize",20,8,"euler","simple",1]},{"id":38,"type":"VAEDecode","pos":[4164.01611328125,925.5230712890625],"size":[193.25,107],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"samples","name":"samples","type":"LATENT","link":null},{"localized_name":"vae","name":"vae","type":"VAE","link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":null}],"properties":{"Node name for S&R":"VAEDecode"}},{"id":39,"type":"CLIPTextEncode","pos":[3259.289794921875,927.2508544921875],"size":[239.9375,155],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"clip","name":"clip","type":"CLIP","link":null},{"localized_name":"text","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"localized_name":"CONDITIONING","name":"CONDITIONING","type":"CONDITIONING","links":null}],"properties":{"Node name for S&R":"CLIPTextEncode"},"widgets_values":[""]}],"links":[],"groups":[],"config":{},"extra":{"ds":{"scale":1.1576250000000001,"offset":[-2808.366467322067,-478.34316506594797]}},"version":0.4}
|
||||||
@@ -5,13 +5,14 @@ import dotenv from 'dotenv'
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
|
||||||
import type { LGraphNode } from '../../src/lib/litegraph/src/litegraph'
|
import type { LGraphNode } from '../../src/lib/litegraph/src/litegraph'
|
||||||
import type { NodeId } from '../../src/schemas/comfyWorkflowSchema'
|
import type { NodeId } from '../../src/platform/workflow/validation/schemas/workflowSchema'
|
||||||
import type { KeyCombo } from '../../src/schemas/keyBindingSchema'
|
import type { KeyCombo } from '../../src/schemas/keyBindingSchema'
|
||||||
import type { useWorkspaceStore } from '../../src/stores/workspaceStore'
|
import type { useWorkspaceStore } from '../../src/stores/workspaceStore'
|
||||||
import { NodeBadgeMode } from '../../src/types/nodeSource'
|
import { NodeBadgeMode } from '../../src/types/nodeSource'
|
||||||
import { ComfyActionbar } from '../helpers/actionbar'
|
import { ComfyActionbar } from '../helpers/actionbar'
|
||||||
import { ComfyTemplates } from '../helpers/templates'
|
import { ComfyTemplates } from '../helpers/templates'
|
||||||
import { ComfyMouse } from './ComfyMouse'
|
import { ComfyMouse } from './ComfyMouse'
|
||||||
|
import { VueNodeHelpers } from './VueNodeHelpers'
|
||||||
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
|
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
|
||||||
import { SettingDialog } from './components/SettingDialog'
|
import { SettingDialog } from './components/SettingDialog'
|
||||||
import {
|
import {
|
||||||
@@ -144,6 +145,7 @@ export class ComfyPage {
|
|||||||
public readonly templates: ComfyTemplates
|
public readonly templates: ComfyTemplates
|
||||||
public readonly settingDialog: SettingDialog
|
public readonly settingDialog: SettingDialog
|
||||||
public readonly confirmDialog: ConfirmDialog
|
public readonly confirmDialog: ConfirmDialog
|
||||||
|
public readonly vueNodes: VueNodeHelpers
|
||||||
|
|
||||||
/** Worker index to test user ID */
|
/** Worker index to test user ID */
|
||||||
public readonly userIds: string[] = []
|
public readonly userIds: string[] = []
|
||||||
@@ -172,6 +174,7 @@ export class ComfyPage {
|
|||||||
this.templates = new ComfyTemplates(page)
|
this.templates = new ComfyTemplates(page)
|
||||||
this.settingDialog = new SettingDialog(page, this)
|
this.settingDialog = new SettingDialog(page, this)
|
||||||
this.confirmDialog = new ConfirmDialog(page)
|
this.confirmDialog = new ConfirmDialog(page)
|
||||||
|
this.vueNodes = new VueNodeHelpers(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
convertLeafToContent(structure: FolderStructure): FolderStructure {
|
convertLeafToContent(structure: FolderStructure): FolderStructure {
|
||||||
@@ -1421,7 +1424,7 @@ export class ComfyPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async closeDialog() {
|
async closeDialog() {
|
||||||
await this.page.locator('.p-dialog-close-button').click()
|
await this.page.locator('.p-dialog-close-button').click({ force: true })
|
||||||
await expect(this.page.locator('.p-dialog')).toBeHidden()
|
await expect(this.page.locator('.p-dialog')).toBeHidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Page, test as base } from '@playwright/test'
|
import type { Page } from '@playwright/test'
|
||||||
|
import { test as base } from '@playwright/test'
|
||||||
|
|
||||||
export class UserSelectPage {
|
export class UserSelectPage {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
110
browser_tests/fixtures/VueNodeHelpers.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* Vue Node Test Helpers
|
||||||
|
*/
|
||||||
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
|
export class VueNodeHelpers {
|
||||||
|
constructor(private page: Page) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get locator for all Vue node components in the DOM
|
||||||
|
*/
|
||||||
|
get nodes(): Locator {
|
||||||
|
return this.page.locator('[data-node-id]')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get locator for selected Vue node components (using visual selection indicators)
|
||||||
|
*/
|
||||||
|
get selectedNodes(): Locator {
|
||||||
|
return this.page.locator(
|
||||||
|
'[data-node-id].outline-black, [data-node-id].outline-white'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total count of Vue nodes in the DOM
|
||||||
|
*/
|
||||||
|
async getNodeCount(): Promise<number> {
|
||||||
|
return await this.nodes.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get count of selected Vue nodes
|
||||||
|
*/
|
||||||
|
async getSelectedNodeCount(): Promise<number> {
|
||||||
|
return await this.selectedNodes.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all Vue node IDs currently in the DOM
|
||||||
|
*/
|
||||||
|
async getNodeIds(): Promise<string[]> {
|
||||||
|
return await this.nodes.evaluateAll((nodes) =>
|
||||||
|
nodes
|
||||||
|
.map((n) => n.getAttribute('data-node-id'))
|
||||||
|
.filter((id): id is string => id !== null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a specific Vue node by ID
|
||||||
|
*/
|
||||||
|
async selectNode(nodeId: string): Promise<void> {
|
||||||
|
await this.page.locator(`[data-node-id="${nodeId}"]`).click()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select multiple Vue nodes by IDs using Ctrl+click
|
||||||
|
*/
|
||||||
|
async selectNodes(nodeIds: string[]): Promise<void> {
|
||||||
|
if (nodeIds.length === 0) return
|
||||||
|
|
||||||
|
// Select first node normally
|
||||||
|
await this.selectNode(nodeIds[0])
|
||||||
|
|
||||||
|
// Add additional nodes with Ctrl+click
|
||||||
|
for (let i = 1; i < nodeIds.length; i++) {
|
||||||
|
await this.page.locator(`[data-node-id="${nodeIds[i]}"]`).click({
|
||||||
|
modifiers: ['Control']
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all selections by clicking empty space
|
||||||
|
*/
|
||||||
|
async clearSelection(): Promise<void> {
|
||||||
|
await this.page.mouse.click(50, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete selected Vue nodes using Delete key
|
||||||
|
*/
|
||||||
|
async deleteSelected(): Promise<void> {
|
||||||
|
await this.page.locator('#graph-canvas').focus()
|
||||||
|
await this.page.keyboard.press('Delete')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete selected Vue nodes using Backspace key
|
||||||
|
*/
|
||||||
|
async deleteSelectedWithBackspace(): Promise<void> {
|
||||||
|
await this.page.locator('#graph-canvas').focus()
|
||||||
|
await this.page.keyboard.press('Backspace')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for Vue nodes to be rendered
|
||||||
|
*/
|
||||||
|
async waitForNodes(expectedCount?: number): Promise<void> {
|
||||||
|
if (expectedCount !== undefined) {
|
||||||
|
await this.page.waitForFunction(
|
||||||
|
(count) => document.querySelectorAll('[data-node-id]').length >= count,
|
||||||
|
expectedCount
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await this.page.waitForSelector('[data-node-id]')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Locator, Page } from '@playwright/test'
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
export class ComfyNodeSearchFilterSelectionPanel {
|
export class ComfyNodeSearchFilterSelectionPanel {
|
||||||
constructor(public readonly page: Page) {}
|
constructor(public readonly page: Page) {}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Page } from '@playwright/test'
|
import type { Page } from '@playwright/test'
|
||||||
|
|
||||||
import { ComfyPage } from '../ComfyPage'
|
import type { ComfyPage } from '../ComfyPage'
|
||||||
|
|
||||||
export class SettingDialog {
|
export class SettingDialog {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Locator, Page } from '@playwright/test'
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
class SidebarTab {
|
class SidebarTab {
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Locator, Page, expect } from '@playwright/test'
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
export class Topbar {
|
export class Topbar {
|
||||||
private readonly menuLocator: Locator
|
private readonly menuLocator: Locator
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Page } from '@playwright/test'
|
import type { Page } from '@playwright/test'
|
||||||
|
|
||||||
import type { NodeId } from '../../../src/schemas/comfyWorkflowSchema'
|
import type { NodeId } from '../../../src/platform/workflow/validation/schemas/workflowSchema'
|
||||||
import { ManageGroupNode } from '../../helpers/manageGroupNode'
|
import { ManageGroupNode } from '../../helpers/manageGroupNode'
|
||||||
import type { ComfyPage } from '../ComfyPage'
|
import type { ComfyPage } from '../ComfyPage'
|
||||||
import type { Position, Size } from '../types'
|
import type { Position, Size } from '../types'
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ export const webSocketFixture = base.extend<{
|
|||||||
// so we can look it up to trigger messages
|
// so we can look it up to trigger messages
|
||||||
const store: Record<string, WebSocket> = ((window as any).__ws__ = {})
|
const store: Record<string, WebSocket> = ((window as any).__ws__ = {})
|
||||||
window.WebSocket = class extends window.WebSocket {
|
window.WebSocket = class extends window.WebSocket {
|
||||||
constructor() {
|
constructor(
|
||||||
// @ts-expect-error
|
...rest: ConstructorParameters<typeof window.WebSocket>
|
||||||
super(...arguments)
|
) {
|
||||||
|
super(...rest)
|
||||||
store[this.url] = this
|
store[this.url] = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FullConfig } from '@playwright/test'
|
import type { FullConfig } from '@playwright/test'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
import { backupPath } from './utils/backupUtils'
|
import { backupPath } from './utils/backupUtils'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FullConfig } from '@playwright/test'
|
import type { FullConfig } from '@playwright/test'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
import { restorePath } from './utils/backupUtils'
|
import { restorePath } from './utils/backupUtils'
|
||||||
|
|||||||
104
browser_tests/helpers/fitToView.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import type { ReadOnlyRect } from '../../src/lib/litegraph/src/interfaces'
|
||||||
|
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||||
|
|
||||||
|
interface FitToViewOptions {
|
||||||
|
selectionOnly?: boolean
|
||||||
|
zoom?: number
|
||||||
|
padding?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantly fits the canvas view to graph content without waiting for UI animation.
|
||||||
|
*
|
||||||
|
* Lives outside the shared fixture to keep the default ComfyPage interactions user-oriented.
|
||||||
|
*/
|
||||||
|
export async function fitToViewInstant(
|
||||||
|
comfyPage: ComfyPage,
|
||||||
|
options: FitToViewOptions = {}
|
||||||
|
) {
|
||||||
|
const { selectionOnly = false, zoom = 0.75, padding = 10 } = options
|
||||||
|
|
||||||
|
const rectangles = await comfyPage.page.evaluate<
|
||||||
|
ReadOnlyRect[] | null,
|
||||||
|
{ selectionOnly: boolean }
|
||||||
|
>(
|
||||||
|
({ selectionOnly }) => {
|
||||||
|
const app = window['app']
|
||||||
|
if (!app?.canvas) return null
|
||||||
|
|
||||||
|
const canvas = app.canvas
|
||||||
|
const items = (() => {
|
||||||
|
if (selectionOnly && canvas.selectedItems?.size) {
|
||||||
|
return Array.from(canvas.selectedItems)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Array.from(canvas.positionableItems ?? [])
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
if (!items.length) return null
|
||||||
|
|
||||||
|
const rects: ReadOnlyRect[] = []
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const rect = item?.boundingRect
|
||||||
|
if (!rect) continue
|
||||||
|
|
||||||
|
const x = Number(rect[0])
|
||||||
|
const y = Number(rect[1])
|
||||||
|
const width = Number(rect[2])
|
||||||
|
const height = Number(rect[3])
|
||||||
|
|
||||||
|
rects.push([x, y, width, height] as const)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rects.length ? rects : null
|
||||||
|
},
|
||||||
|
{ selectionOnly }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!rectangles || rectangles.length === 0) return
|
||||||
|
|
||||||
|
let minX = Infinity
|
||||||
|
let minY = Infinity
|
||||||
|
let maxX = -Infinity
|
||||||
|
let maxY = -Infinity
|
||||||
|
|
||||||
|
for (const [x, y, width, height] of rectangles) {
|
||||||
|
minX = Math.min(minX, Number(x))
|
||||||
|
minY = Math.min(minY, Number(y))
|
||||||
|
maxX = Math.max(maxX, Number(x) + Number(width))
|
||||||
|
maxY = Math.max(maxY, Number(y) + Number(height))
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasFiniteBounds =
|
||||||
|
Number.isFinite(minX) &&
|
||||||
|
Number.isFinite(minY) &&
|
||||||
|
Number.isFinite(maxX) &&
|
||||||
|
Number.isFinite(maxY)
|
||||||
|
|
||||||
|
if (!hasFiniteBounds) return
|
||||||
|
|
||||||
|
const bounds: ReadOnlyRect = [
|
||||||
|
minX - padding,
|
||||||
|
minY - padding,
|
||||||
|
maxX - minX + 2 * padding,
|
||||||
|
maxY - minY + 2 * padding
|
||||||
|
]
|
||||||
|
|
||||||
|
await comfyPage.page.evaluate(
|
||||||
|
({ bounds, zoom }) => {
|
||||||
|
const app = window['app']
|
||||||
|
if (!app?.canvas) return
|
||||||
|
|
||||||
|
const canvas = app.canvas
|
||||||
|
canvas.ds.fitToBounds(bounds, { zoom })
|
||||||
|
canvas.setDirty(true, true)
|
||||||
|
},
|
||||||
|
{ bounds, zoom }
|
||||||
|
)
|
||||||
|
|
||||||
|
await comfyPage.nextFrame()
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Locator, Page } from '@playwright/test'
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
export class ManageGroupNode {
|
export class ManageGroupNode {
|
||||||
footer: Locator
|
footer: Locator
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Locator, Page } from '@playwright/test'
|
import type { Locator, Page } from '@playwright/test'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
import {
|
import type {
|
||||||
TemplateInfo,
|
TemplateInfo,
|
||||||
WorkflowTemplates
|
WorkflowTemplates
|
||||||
} from '../../src/types/workflowTemplateTypes'
|
} from '../../src/platform/workflow/templates/types/template'
|
||||||
|
|
||||||
export class ComfyTemplates {
|
export class ComfyTemplates {
|
||||||
readonly content: Locator
|
readonly content: Locator
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ test.describe('Actionbar', () => {
|
|||||||
|
|
||||||
// Intercept the prompt queue endpoint
|
// Intercept the prompt queue endpoint
|
||||||
let promptNumber = 0
|
let promptNumber = 0
|
||||||
comfyPage.page.route('**/api/prompt', async (route, req) => {
|
await comfyPage.page.route('**/api/prompt', async (route, req) => {
|
||||||
await new Promise((r) => setTimeout(r, 100))
|
await new Promise((r) => setTimeout(r, 100))
|
||||||
route.fulfill({
|
await route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
prompt_id: promptNumber,
|
prompt_id: promptNumber,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||||
import {
|
import {
|
||||||
ComfyPage,
|
|
||||||
comfyExpect as expect,
|
comfyExpect as expect,
|
||||||
comfyPageFixture as test
|
comfyPageFixture as test
|
||||||
} from '../fixtures/ComfyPage'
|
} from '../fixtures/ComfyPage'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Page, expect } from '@playwright/test'
|
import type { Page } from '@playwright/test'
|
||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 100 KiB |
@@ -1,4 +1,5 @@
|
|||||||
import { Locator, expect } from '@playwright/test'
|
import type { Locator } from '@playwright/test'
|
||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
import type { Keybinding } from '../../src/schemas/keyBindingSchema'
|
import type { Keybinding } from '../../src/schemas/keyBindingSchema'
|
||||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||||
@@ -36,6 +37,10 @@ test('Does not report warning on undo/redo', async ({ comfyPage }) => {
|
|||||||
await comfyPage.loadWorkflow('missing/missing_nodes')
|
await comfyPage.loadWorkflow('missing/missing_nodes')
|
||||||
await comfyPage.closeDialog()
|
await comfyPage.closeDialog()
|
||||||
|
|
||||||
|
// Wait for any async operations to complete after dialog closes
|
||||||
|
await comfyPage.nextFrame()
|
||||||
|
await comfyPage.page.waitForTimeout(100)
|
||||||
|
|
||||||
// Make a change to the graph
|
// Make a change to the graph
|
||||||
await comfyPage.doubleClickCanvas()
|
await comfyPage.doubleClickCanvas()
|
||||||
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 95 KiB |
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from '@playwright/test'
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
import { SettingParams } from '../../src/types/settingTypes'
|
import type { SettingParams } from '../../src/platform/settings/types'
|
||||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||||
|
|
||||||
test.describe('Topbar commands', () => {
|
test.describe('Topbar commands', () => {
|
||||||
@@ -247,7 +247,7 @@ test.describe('Topbar commands', () => {
|
|||||||
test.describe('Dialog', () => {
|
test.describe('Dialog', () => {
|
||||||
test('Should allow showing a prompt dialog', async ({ comfyPage }) => {
|
test('Should allow showing a prompt dialog', async ({ comfyPage }) => {
|
||||||
await comfyPage.page.evaluate(() => {
|
await comfyPage.page.evaluate(() => {
|
||||||
window['app'].extensionManager.dialog
|
void window['app'].extensionManager.dialog
|
||||||
.prompt({
|
.prompt({
|
||||||
title: 'Test Prompt',
|
title: 'Test Prompt',
|
||||||
message: 'Test Prompt Message'
|
message: 'Test Prompt Message'
|
||||||
@@ -267,7 +267,7 @@ test.describe('Topbar commands', () => {
|
|||||||
comfyPage
|
comfyPage
|
||||||
}) => {
|
}) => {
|
||||||
await comfyPage.page.evaluate(() => {
|
await comfyPage.page.evaluate(() => {
|
||||||
window['app'].extensionManager.dialog
|
void window['app'].extensionManager.dialog
|
||||||
.confirm({
|
.confirm({
|
||||||
title: 'Test Confirm',
|
title: 'Test Confirm',
|
||||||
message: 'Test Confirm Message'
|
message: 'Test Confirm Message'
|
||||||
@@ -284,7 +284,7 @@ test.describe('Topbar commands', () => {
|
|||||||
test('Should allow dismissing a dialog', async ({ comfyPage }) => {
|
test('Should allow dismissing a dialog', async ({ comfyPage }) => {
|
||||||
await comfyPage.page.evaluate(() => {
|
await comfyPage.page.evaluate(() => {
|
||||||
window['value'] = 'foo'
|
window['value'] = 'foo'
|
||||||
window['app'].extensionManager.dialog
|
void window['app'].extensionManager.dialog
|
||||||
.confirm({
|
.confirm({
|
||||||
title: 'Test Confirm',
|
title: 'Test Confirm',
|
||||||
message: 'Test Confirm Message'
|
message: 'Test Confirm Message'
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 100 KiB |
@@ -1,6 +1,7 @@
|
|||||||
import { expect } from '@playwright/test'
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
import { ComfyPage, comfyPageFixture as test } from '../fixtures/ComfyPage'
|
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||||
|
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||||
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
|
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
|
||||||
|
|
||||||
test.describe('Group Node', () => {
|
test.describe('Group Node', () => {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Locator, expect } from '@playwright/test'
|
import type { Locator } from '@playwright/test'
|
||||||
import { Position } from '@vueuse/core'
|
import { expect } from '@playwright/test'
|
||||||
|
import type { Position } from '@vueuse/core'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type ComfyPage,
|
type ComfyPage,
|
||||||
comfyPageFixture as test,
|
comfyPageFixture as test,
|
||||||
testComfySnapToGridGridSize
|
testComfySnapToGridGridSize
|
||||||
} from '../fixtures/ComfyPage'
|
} from '../fixtures/ComfyPage'
|
||||||
import { type NodeReference } from '../fixtures/utils/litegraphUtils'
|
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
|
||||||
|
|
||||||
test.describe('Item Interaction', () => {
|
test.describe('Item Interaction', () => {
|
||||||
test('Can select/delete all items', async ({ comfyPage }) => {
|
test('Can select/delete all items', async ({ comfyPage }) => {
|
||||||
@@ -1012,6 +1013,8 @@ test.describe('Canvas Navigation', () => {
|
|||||||
test('Shift + mouse wheel should pan canvas horizontally', async ({
|
test('Shift + mouse wheel should pan canvas horizontally', async ({
|
||||||
comfyPage
|
comfyPage
|
||||||
}) => {
|
}) => {
|
||||||
|
await comfyPage.setSetting('Comfy.Canvas.MouseWheelScroll', 'panning')
|
||||||
|
|
||||||
await comfyPage.page.click('canvas')
|
await comfyPage.page.click('canvas')
|
||||||
await comfyPage.nextFrame()
|
await comfyPage.nextFrame()
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 99 KiB |