mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-05 12:09:08 +00:00
Compare commits
10 Commits
cloud/mode
...
fix/highli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c330529da8 | ||
|
|
50d339e2ba | ||
|
|
ed10909f38 | ||
|
|
65ff23c5af | ||
|
|
6ce60a11a4 | ||
|
|
3b5d124029 | ||
|
|
bd4920febc | ||
|
|
bd916096ac | ||
|
|
9be853f6b5 | ||
|
|
2103dcc788 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -11,6 +11,7 @@
|
||||
*.ts text eol=lf
|
||||
*.vue text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.yml text eol=lf
|
||||
|
||||
# Generated files
|
||||
packages/registry-types/src/comfyRegistryTypes.ts linguist-generated=true
|
||||
|
||||
@@ -104,14 +104,14 @@ runs:
|
||||
|
||||
- name: Find existing comment
|
||||
id: find
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
with:
|
||||
issue-number: ${{ inputs.issue-number || github.event.pull_request.number }}
|
||||
comment-author: github-actions[bot]
|
||||
body-includes: ${{ steps.build.outputs.marker_search }}
|
||||
|
||||
- name: Post or update comment
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
issue-number: ${{ inputs.issue-number || github.event.pull_request.number }}
|
||||
comment-id: ${{ steps.find.outputs.comment-id }}
|
||||
|
||||
@@ -16,7 +16,7 @@ runs:
|
||||
|
||||
# Checkout ComfyUI repo, install the dev_tools node and start server
|
||||
- name: Checkout ComfyUI
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: 'comfyanonymous/ComfyUI'
|
||||
path: 'ComfyUI'
|
||||
@@ -33,7 +33,7 @@ runs:
|
||||
fi
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
@@ -12,29 +12,17 @@ runs:
|
||||
|
||||
# Install pnpm, Node.js, build frontend
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: './pnpm-lock.yaml'
|
||||
|
||||
# Restore tool caches before running any build/lint operations
|
||||
- name: Restore tool output cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
./.cache
|
||||
./tsconfig.tsbuildinfo
|
||||
key: tool-cache-${{ runner.os }}-${{ hashFiles('./pnpm-lock.yaml') }}-${{ hashFiles('./src/**/*.{ts,vue,js,mts}', './*.config.*') }}
|
||||
restore-keys: |
|
||||
tool-cache-${{ runner.os }}-${{ hashFiles('./pnpm-lock.yaml') }}-
|
||||
tool-cache-${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -11,7 +11,7 @@ runs:
|
||||
echo "playwright-version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Playwright Browsers
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5 # v5.0.2
|
||||
id: cache-playwright-browsers
|
||||
with:
|
||||
path: '~/.cache/ms-playwright'
|
||||
@@ -13,15 +13,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: 'pnpm'
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: '[chore] Update electron-types to ${{ steps.get-version.outputs.NEW_VERSION }}'
|
||||
|
||||
@@ -18,15 +18,15 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: 'pnpm'
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Checkout ComfyUI-Manager repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: Comfy-Org/ComfyUI-Manager
|
||||
path: ComfyUI-Manager
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.check-changes.outputs.changed == 'true'
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: '[chore] Update ComfyUI-Manager API types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}'
|
||||
|
||||
@@ -17,15 +17,15 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: 'pnpm'
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Checkout comfy-api repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: Comfy-Org/comfy-api
|
||||
path: comfy-api
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.check-changes.outputs.changed == 'true'
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: '[chore] Update Comfy Registry API types from comfy-api@${{ steps.api-info.outputs.commit }}'
|
||||
|
||||
2
.github/workflows/ci-json-validation.yaml
vendored
2
.github/workflows/ci-json-validation.yaml
vendored
@@ -13,6 +13,6 @@ jobs:
|
||||
json-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Validate JSON syntax
|
||||
run: ./scripts/cicd/check-json.sh
|
||||
|
||||
21
.github/workflows/ci-lint-format.yaml
vendored
21
.github/workflows/ci-lint-format.yaml
vendored
@@ -18,23 +18,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ !github.event.pull_request.head.repo.fork && github.head_ref || github.ref }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
|
||||
- name: Run ESLint with auto-fix
|
||||
run: pnpm lint:fix
|
||||
@@ -73,7 +62,7 @@ jobs:
|
||||
- name: Comment on PR about auto-fix
|
||||
if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name == github.repository
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
@@ -86,7 +75,7 @@ jobs:
|
||||
- name: Comment on PR about manual fix needed
|
||||
if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name != github.repository
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
|
||||
4
.github/workflows/ci-python-validation.yaml
vendored
4
.github/workflows/ci-python-validation.yaml
vendored
@@ -16,10 +16,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
|
||||
19
.github/workflows/ci-size-data.yaml
vendored
19
.github/workflows/ci-size-data.yaml
vendored
@@ -17,21 +17,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
|
||||
- name: Build project
|
||||
run: pnpm build
|
||||
@@ -46,7 +35,7 @@ jobs:
|
||||
echo ${{ github.base_ref }} > ./temp/size/base.txt
|
||||
|
||||
- name: Upload size data
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: size-data
|
||||
path: temp/size
|
||||
|
||||
6
.github/workflows/ci-tests-e2e-forks.yaml
vendored
6
.github/workflows/ci-tests-e2e-forks.yaml
vendored
@@ -31,11 +31,11 @@ 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@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get PR Number
|
||||
id: pr
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
- name: Download and Deploy Reports
|
||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
31
.github/workflows/ci-tests-e2e.yaml
vendored
31
.github/workflows/ci-tests-e2e.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
with:
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
# Upload only built dist/ (containerized test jobs will pnpm install without cache)
|
||||
- name: Upload built frontend
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: dist/
|
||||
@@ -51,9 +51,9 @@ jobs:
|
||||
shardTotal: [8]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Download built frontend
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: dist/
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
PLAYWRIGHT_BLOB_OUTPUT_DIR: ./blob-report
|
||||
|
||||
- name: Upload blob report
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: blob-report-chromium-${{ matrix.shardIndex }}
|
||||
@@ -98,9 +98,9 @@ jobs:
|
||||
browser: [chromium-2x, chromium-0.5x, mobile-chrome]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Download built frontend
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: dist/
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
pnpm exec playwright merge-reports --reporter=json ./blob-report
|
||||
|
||||
- name: Upload Playwright report
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-${{ matrix.browser }}
|
||||
@@ -141,16 +141,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Download blob reports
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ./all-blob-reports
|
||||
pattern: blob-report-chromium-*
|
||||
@@ -165,7 +162,7 @@ jobs:
|
||||
pnpm dlx @playwright/test merge-reports --reporter=json ./all-blob-reports
|
||||
|
||||
- name: Upload HTML report
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: playwright-report-chromium
|
||||
path: ./playwright-report/
|
||||
@@ -183,7 +180,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get start time
|
||||
id: start-time
|
||||
@@ -210,10 +207,10 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download all playwright reports
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: playwright-report-*
|
||||
path: reports
|
||||
|
||||
@@ -31,11 +31,11 @@ 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@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Get PR Number
|
||||
id: pr
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
- 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
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
46
.github/workflows/ci-tests-storybook.yaml
vendored
46
.github/workflows/ci-tests-storybook.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Post starting comment
|
||||
env:
|
||||
@@ -36,21 +36,10 @@ jobs:
|
||||
workflow-url: ${{ steps.workflow-url.outputs.url }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- 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: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
|
||||
- name: Build Storybook
|
||||
run: pnpm build-storybook
|
||||
@@ -69,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Upload Storybook build
|
||||
if: success() && github.event.pull_request.head.repo.fork == false
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: storybook-static
|
||||
path: storybook-static/
|
||||
@@ -86,27 +75,16 @@ jobs:
|
||||
chromatic-storybook-url: ${{ steps.chromatic.outputs.storybookUrl }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
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: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
|
||||
- name: Build Storybook and run Chromatic
|
||||
id: chromatic
|
||||
uses: chromaui/action@latest
|
||||
uses: chromaui/action@07791f8243f4cb2698bf4d00426baf4b2d1cb7e0 # v13.3.5
|
||||
with:
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
buildScriptName: build-storybook
|
||||
@@ -136,11 +114,11 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download Storybook build
|
||||
if: needs.storybook-build.outputs.conclusion == 'success'
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: storybook-static
|
||||
path: storybook-static
|
||||
@@ -170,7 +148,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Update comment with Chromatic URLs
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const buildUrl = '${{ needs.chromatic-deployment.outputs.chromatic-build-url }}';
|
||||
|
||||
17
.github/workflows/ci-tests-unit.yaml
vendored
17
.github/workflows/ci-tests-unit.yaml
vendored
@@ -16,21 +16,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
|
||||
- name: Run Vitest tests
|
||||
run: pnpm test:unit
|
||||
|
||||
21
.github/workflows/ci-validate-action-pins.yaml
vendored
Normal file
21
.github/workflows/ci-validate-action-pins.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Validate Action SHA Pins
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- '.github/actions/**'
|
||||
- '.pinact.yaml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate-pins:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: suzuki-shunsuke/pinact-action@3d49c6412901042473ffa78becddab1aea46bbea # v1.3.1
|
||||
with:
|
||||
skip_push: 'true'
|
||||
4
.github/workflows/ci-yaml-validation.yaml
vendored
4
.github/workflows/ci-yaml-validation.yaml
vendored
@@ -17,10 +17,10 @@ jobs:
|
||||
yaml-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
|
||||
4
.github/workflows/cloud-backport-tag.yaml
vendored
4
.github/workflows/cloud-backport-tag.yaml
vendored
@@ -18,12 +18,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout merge commit
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
|
||||
2
.github/workflows/i18n-update-core.yaml
vendored
2
.github/workflows/i18n-update-core.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Setup playwright environment
|
||||
- name: Setup ComfyUI Frontend
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Setup playwright environment with custom node repository
|
||||
- name: Setup ComfyUI Server (without launching)
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
# Install the custom node repository
|
||||
- name: Checkout custom node repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ inputs.owner }}/${{ inputs.repository }}
|
||||
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
git commit -m "Update locales"
|
||||
|
||||
- name: Install SSH key For PUSH
|
||||
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4
|
||||
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4 # v2.7.0
|
||||
with:
|
||||
# PR private key from action server
|
||||
key: ${{ secrets.PR_SSH_PRIVATE_KEY }}
|
||||
|
||||
4
.github/workflows/i18n-update-nodes.yaml
vendored
4
.github/workflows/i18n-update-nodes.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
# Setup playwright environment
|
||||
- name: Setup ComfyUI Server (and start)
|
||||
uses: ./.github/actions/setup-comfyui-server
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: 'Update locales for node definitions'
|
||||
|
||||
2
.github/workflows/pr-backport.yaml
vendored
2
.github/workflows/pr-backport.yaml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
8
.github/workflows/pr-claude-review.yaml
vendored
8
.github/workflows/pr-claude-review.yaml
vendored
@@ -23,18 +23,18 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/head
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
pnpm install -g typescript @vue/compiler-sfc
|
||||
|
||||
- name: Run Claude PR Review
|
||||
uses: anthropics/claude-code-action@v1.0.6
|
||||
uses: anthropics/claude-code-action@ff34ce0ff04a470bd3fa56c1ef391c8f1c19f8e9 # v1.0.38
|
||||
with:
|
||||
label_trigger: 'claude-review'
|
||||
prompt: |
|
||||
|
||||
25
.github/workflows/pr-size-report.yaml
vendored
25
.github/workflows/pr-size-report.yaml
vendored
@@ -33,24 +33,13 @@ jobs:
|
||||
github.event_name == 'workflow_dispatch'
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
|
||||
- name: Download size data
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
|
||||
with:
|
||||
name: size-data
|
||||
run_id: ${{ github.event_name == 'workflow_dispatch' && inputs.run_id || github.event.workflow_run.id }}
|
||||
@@ -75,7 +64,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Download previous size data
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
|
||||
with:
|
||||
branch: ${{ steps.pr-base.outputs.content }}
|
||||
workflow: ci-size-data.yaml
|
||||
@@ -89,12 +78,12 @@ jobs:
|
||||
|
||||
- name: Read size report
|
||||
id: size-report
|
||||
uses: juliangruber/read-file-action@v1
|
||||
uses: juliangruber/read-file-action@b549046febe0fe86f8cb4f93c24e284433f9ab58 # v1.1.7
|
||||
with:
|
||||
path: ./size-report.md
|
||||
|
||||
- name: Create or update PR comment
|
||||
uses: actions-cool/maintain-one-comment@v3
|
||||
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
number: ${{ steps.pr-number.outputs.content }}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Find Update Comment
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
id: 'find-update-comment'
|
||||
with:
|
||||
issue-number: ${{ steps.pr-info.outputs.pr-number }}
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
body-includes: 'Updating Playwright Expectations'
|
||||
|
||||
- name: Add Starting Reaction
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
comment-id: ${{ steps.find-update-comment.outputs.comment-id }}
|
||||
issue-number: ${{ steps.pr-info.outputs.pr-number }}
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
reactions: eyes
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.pr-info.outputs.branch }}
|
||||
- name: Setup frontend
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
# Upload built dist/ (containerized test jobs will pnpm install without cache)
|
||||
- name: Upload built frontend
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: dist/
|
||||
@@ -91,11 +91,11 @@ jobs:
|
||||
shardTotal: [4]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.setup.outputs.branch }}
|
||||
- name: Download built frontend
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: dist/
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
|
||||
# Upload ONLY the changed files from this shard
|
||||
- name: Upload changed snapshots
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
if: steps.changed-snapshots.outputs.has-changes == 'true'
|
||||
with:
|
||||
name: snapshots-shard-${{ matrix.shardIndex }}
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload test report
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-shard-${{ matrix.shardIndex }}
|
||||
@@ -170,13 +170,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.setup.outputs.branch }}
|
||||
|
||||
# Download all changed snapshot files from shards
|
||||
- name: Download snapshot artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: snapshots-shard-*
|
||||
path: ./downloaded-snapshots
|
||||
@@ -301,7 +301,7 @@ jobs:
|
||||
echo "✓ Commit and push successful"
|
||||
|
||||
- name: Add Done Reaction
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
if: github.event_name == 'issue_comment' && steps.commit.outputs.has-changes == 'true'
|
||||
with:
|
||||
comment-id: ${{ needs.setup.outputs.comment-id }}
|
||||
|
||||
@@ -20,13 +20,13 @@ jobs:
|
||||
dist_tag: ${{ steps.dist.outputs.dist_tag }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24.x'
|
||||
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout merge commit
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
fetch-depth: 2
|
||||
|
||||
6
.github/workflows/publish-desktop-ui.yaml
vendored
6
.github/workflows/publish-desktop-ui.yaml
vendored
@@ -77,19 +77,19 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.resolve_ref.outputs.ref }}
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: 'pnpm'
|
||||
|
||||
10
.github/workflows/release-biweekly-comfyui.yaml
vendored
10
.github/workflows/release-biweekly-comfyui.yaml
vendored
@@ -61,13 +61,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout ComfyUI_frontend
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
path: frontend
|
||||
|
||||
- name: Checkout ComfyUI (sparse)
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: Comfy-Org/ComfyUI
|
||||
sparse-checkout: |
|
||||
@@ -75,12 +75,12 @@ jobs:
|
||||
path: comfyui
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
@@ -169,7 +169,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout ComfyUI fork
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }}
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
|
||||
4
.github/workflows/release-branch-create.yaml
vendored
4
.github/workflows/release-branch-create.yaml
vendored
@@ -18,13 +18,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
|
||||
|
||||
26
.github/workflows/release-draft-create.yaml
vendored
26
.github/workflows/release-draft-create.yaml
vendored
@@ -19,12 +19,12 @@ jobs:
|
||||
is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'pnpm'
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
pnpm build
|
||||
pnpm zipdist
|
||||
- name: Upload dist artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: dist-files
|
||||
path: |
|
||||
@@ -66,16 +66,13 @@ jobs:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
- name: Download dist artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: dist-files
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: >-
|
||||
softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -98,13 +95,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Download dist artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: dist-files
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install build dependencies
|
||||
@@ -119,8 +116,7 @@ jobs:
|
||||
env:
|
||||
COMFYUI_FRONTEND_VERSION: ${{ needs.build.outputs.version }}
|
||||
- name: Publish pypi package
|
||||
uses: >-
|
||||
pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
with:
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
packages-dir: comfyui_frontend_package/dist
|
||||
@@ -147,7 +143,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout merge commit
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
fetch-depth: 2
|
||||
|
||||
6
.github/workflows/release-npm-types.yaml
vendored
6
.github/workflows/release-npm-types.yaml
vendored
@@ -69,18 +69,18 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.resolve_ref.outputs.ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'pnpm'
|
||||
|
||||
16
.github/workflows/release-pypi-dev.yaml
vendored
16
.github/workflows/release-pypi-dev.yaml
vendored
@@ -15,12 +15,12 @@ jobs:
|
||||
version: ${{ steps.current_version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'pnpm'
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
pnpm build
|
||||
pnpm zipdist
|
||||
- name: Upload dist artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: dist-files
|
||||
path: |
|
||||
@@ -52,13 +52,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Download dist artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: dist-files
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install build dependencies
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
env:
|
||||
COMFYUI_FRONTEND_VERSION: ${{ format('{0}.dev{1}', needs.build.outputs.version, inputs.devVersion) }}
|
||||
- name: Publish pypi package
|
||||
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
with:
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
packages-dir: comfyui_frontend_package/dist
|
||||
|
||||
10
.github/workflows/release-version-bump.yaml
vendored
10
.github/workflows/release-version-bump.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
- name: Close stale nightly version bump PRs
|
||||
if: github.event_name == 'schedule'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
core.info(`Closed ${closed.length} stale PR(s).`)
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.prepared-inputs.outputs.branch }}
|
||||
fetch-depth: 0
|
||||
@@ -142,12 +142,12 @@ jobs:
|
||||
echo "✅ Branch '$BRANCH' exists"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
@@ -180,7 +180,7 @@ jobs:
|
||||
echo "capitalised=${CAPITALISED_TYPE@u}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: '[release] Increment version to ${{ steps.bump-version.outputs.NEW_VERSION }}'
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
fetch-depth: 0
|
||||
@@ -51,12 +51,12 @@ jobs:
|
||||
echo "✅ Branch '$BRANCH' exists"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24.x'
|
||||
cache: 'pnpm'
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
echo "capitalised=${VERSION_TYPE@u}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: '[release] Increment desktop-ui to ${{ steps.bump-version.outputs.NEW_VERSION }}'
|
||||
|
||||
12
.github/workflows/weekly-docs-check.yaml
vendored
12
.github/workflows/weekly-docs-check.yaml
vendored
@@ -22,18 +22,18 @@ jobs:
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-depth: 50
|
||||
ref: main
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Run Claude Documentation Review
|
||||
uses: anthropics/claude-code-action@v1.0.6
|
||||
uses: anthropics/claude-code-action@ff34ce0ff04a470bd3fa56c1ef391c8f1c19f8e9 # v1.0.38
|
||||
with:
|
||||
prompt: |
|
||||
Is all documentation still 100% accurate?
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
|
||||
- name: Create or Update Pull Request
|
||||
if: steps.check_changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: 'docs: weekly documentation accuracy update'
|
||||
|
||||
24
.pinact.yaml
Normal file
24
.pinact.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# pinact configuration
|
||||
# https://github.com/suzuki-shunsuke/pinact
|
||||
version: 3
|
||||
|
||||
files:
|
||||
- pattern: .github/workflows/*.yaml
|
||||
- pattern: .github/actions/**/*.yaml
|
||||
|
||||
# Actions that don't need SHA pinning (official GitHub actions are trusted)
|
||||
ignore_actions:
|
||||
- name: actions/cache
|
||||
ref: v5
|
||||
- name: actions/checkout
|
||||
ref: v6
|
||||
- name: actions/setup-node
|
||||
ref: v6
|
||||
- name: actions/setup-python
|
||||
ref: v6
|
||||
- name: actions/upload-artifact
|
||||
ref: v6
|
||||
- name: actions/download-artifact
|
||||
ref: v7
|
||||
- name: actions/github-script
|
||||
ref: v8
|
||||
@@ -8,3 +8,6 @@ rules:
|
||||
line-length: disable
|
||||
document-start: disable
|
||||
truthy: disable
|
||||
comments:
|
||||
min-spaces-from-content: 1
|
||||
|
||||
@@ -18,7 +18,8 @@ Basic setup for testing Pinia stores:
|
||||
|
||||
```typescript
|
||||
// Example from: tests-ui/tests/store/workflowStore.test.ts
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useWorkflowStore } from '@/domains/workflow/ui/stores/workflowStore'
|
||||
@@ -27,8 +28,8 @@ describe('useWorkflowStore', () => {
|
||||
let store: ReturnType<typeof useWorkflowStore>
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a fresh pinia and activate it for each test
|
||||
setActivePinia(createPinia())
|
||||
// Create a fresh testing pinia and activate it for each test
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
|
||||
// Initialize the store
|
||||
store = useWorkflowStore()
|
||||
|
||||
@@ -21,16 +21,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-badges">
|
||||
<Tag
|
||||
v-if="nodeDef.experimental"
|
||||
:value="$t('g.experimental')"
|
||||
severity="primary"
|
||||
/>
|
||||
<Tag
|
||||
v-if="nodeDef.deprecated"
|
||||
:value="$t('g.deprecated')"
|
||||
severity="danger"
|
||||
/>
|
||||
<Tag
|
||||
v-if="nodeDef.experimental"
|
||||
:value="$t('g.experimental')"
|
||||
severity="primary"
|
||||
/>
|
||||
<Tag v-if="nodeDef.dev_only" :value="$t('g.devOnly')" severity="info" />
|
||||
<Tag
|
||||
v-if="showNodeFrequency && nodeFrequency > 0"
|
||||
:value="formatNumberWithSuffix(nodeFrequency, { roundToInt: true })"
|
||||
|
||||
76
src/composables/graph/useGraphErrorState.ts
Normal file
76
src/composables/graph/useGraphErrorState.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { watch } from 'vue'
|
||||
|
||||
import type { LGraphNode, Subgraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useGraphErrorStateStore } from '@/stores/graphErrorStateStore'
|
||||
import { parseNodeLocatorId } from '@/types/nodeIdentification'
|
||||
import {
|
||||
findSubgraphByUuid,
|
||||
forEachNode,
|
||||
forEachSubgraphNode
|
||||
} from '@/utils/graphTraversalUtil'
|
||||
|
||||
export function useGraphErrorState(): void {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
watch(
|
||||
() => store.version,
|
||||
() => {
|
||||
const rootGraph = app.rootGraph
|
||||
if (!rootGraph) return
|
||||
|
||||
forEachNode(rootGraph, (node) => {
|
||||
node.has_errors = false
|
||||
if (node.inputs) {
|
||||
for (const slot of node.inputs) {
|
||||
slot.hasErrors = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
for (const [nodeId, keys] of store.keysByNode) {
|
||||
if (keys.size === 0) continue
|
||||
|
||||
const parsed = parseNodeLocatorId(nodeId)
|
||||
if (!parsed) continue
|
||||
|
||||
const targetGraph = parsed.subgraphUuid
|
||||
? findSubgraphByUuid(rootGraph, parsed.subgraphUuid)
|
||||
: rootGraph
|
||||
if (!targetGraph) continue
|
||||
|
||||
const node = targetGraph.getNodeById(parsed.localNodeId)
|
||||
if (!node) continue
|
||||
|
||||
node.has_errors = true
|
||||
|
||||
for (const key of keys) {
|
||||
const error = store.errorsByKey.get(key)
|
||||
if (error && error.target.kind === 'slot' && node.inputs) {
|
||||
const slotName = error.target.slotName
|
||||
const slot = node.inputs.find((s) => s.name === slotName)
|
||||
if (slot) {
|
||||
slot.hasErrors = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
propagateErrorToParents(node)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
}
|
||||
|
||||
function propagateErrorToParents(node: LGraphNode): void {
|
||||
const subgraph = node.graph as Subgraph | undefined
|
||||
if (!subgraph || subgraph.isRootGraph) return
|
||||
|
||||
const subgraphId = subgraph.id
|
||||
if (!subgraphId) return
|
||||
|
||||
forEachSubgraphNode(app.rootGraph, subgraphId, (subgraphNode) => {
|
||||
subgraphNode.has_errors = true
|
||||
propagateErrorToParents(subgraphNode)
|
||||
})
|
||||
}
|
||||
122
src/composables/graph/useRequiredConnectionValidator.test.ts
Normal file
122
src/composables/graph/useRequiredConnectionValidator.test.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useGraphErrorStateStore } from '@/stores/graphErrorStateStore'
|
||||
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/scripts/app', () => ({
|
||||
app: {
|
||||
rootGraph: null
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/nodeDefStore', () => ({
|
||||
useNodeDefStore: vi.fn(() => ({
|
||||
nodeDefsByName: {}
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/graphTraversalUtil', () => ({
|
||||
forEachNode: vi.fn()
|
||||
}))
|
||||
|
||||
describe('useRequiredConnectionValidator', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('store integration', () => {
|
||||
it('adds errors for missing required connections', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:missing:1:model',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '1', slotName: 'model' },
|
||||
code: 'MISSING_REQUIRED_INPUT'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
expect(store.hasSlotError('1', 'model')).toBe(true)
|
||||
expect(store.hasErrorsForNode('1')).toBe(true)
|
||||
})
|
||||
|
||||
it('clears errors when connections are made', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:missing:1:model',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '1', slotName: 'model' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
expect(store.hasSlotError('1', 'model')).toBe(true)
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: []
|
||||
})
|
||||
|
||||
expect(store.hasSlotError('1', 'model')).toBe(false)
|
||||
})
|
||||
|
||||
it('preserves backend errors when frontend errors change', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'backend',
|
||||
errors: [
|
||||
{
|
||||
key: 'backend:node:2',
|
||||
source: 'backend',
|
||||
target: { kind: 'node', nodeId: '2' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:missing:1:model',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '1', slotName: 'model' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
expect(store.hasErrorsForNode('1')).toBe(true)
|
||||
expect(store.hasErrorsForNode('2')).toBe(true)
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: []
|
||||
})
|
||||
|
||||
expect(store.hasErrorsForNode('1')).toBe(false)
|
||||
expect(store.hasErrorsForNode('2')).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
74
src/composables/graph/useRequiredConnectionValidator.ts
Normal file
74
src/composables/graph/useRequiredConnectionValidator.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { onUnmounted } from 'vue'
|
||||
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useGraphErrorStateStore } from '@/stores/graphErrorStateStore'
|
||||
import type { GraphError } from '@/stores/graphErrorStateStore'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { createNodeLocatorId } from '@/types/nodeIdentification'
|
||||
import { forEachNode } from '@/utils/graphTraversalUtil'
|
||||
|
||||
export function useRequiredConnectionValidator(): void {
|
||||
const errorStore = useGraphErrorStateStore()
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
|
||||
function validate(): void {
|
||||
const rootGraph = app.rootGraph
|
||||
if (!rootGraph) return
|
||||
|
||||
const errors: GraphError[] = []
|
||||
|
||||
forEachNode(rootGraph, (node: LGraphNode) => {
|
||||
const nodeDef = nodeDefStore.nodeDefsByName[node.type ?? '']
|
||||
if (!nodeDef?.input?.required) return
|
||||
|
||||
const subgraphId =
|
||||
node.graph && !node.graph.isRootGraph ? node.graph.id : null
|
||||
const locatorId = subgraphId
|
||||
? createNodeLocatorId(subgraphId, node.id)
|
||||
: String(node.id)
|
||||
|
||||
for (const inputName of Object.keys(nodeDef.input.required)) {
|
||||
const slot = node.inputs?.find((s) => s.name === inputName)
|
||||
|
||||
const hasConnection = slot?.link !== null && slot?.link !== undefined
|
||||
const hasWidgetValue = hasWidgetValueForInput(node, inputName)
|
||||
|
||||
if (!hasConnection && !hasWidgetValue) {
|
||||
errors.push({
|
||||
key: `frontend:missing:${locatorId}:${inputName}`,
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: locatorId, slotName: inputName },
|
||||
code: 'MISSING_REQUIRED_INPUT'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
errorStore.execute({ type: 'REPLACE_SOURCE', source: 'frontend', errors })
|
||||
}
|
||||
|
||||
function hasWidgetValueForInput(
|
||||
node: LGraphNode,
|
||||
inputName: string
|
||||
): boolean {
|
||||
if (!node.widgets) return false
|
||||
const widget = node.widgets.find((w) => w.name === inputName)
|
||||
if (!widget) return false
|
||||
return (
|
||||
widget.value !== undefined && widget.value !== null && widget.value !== ''
|
||||
)
|
||||
}
|
||||
|
||||
const debouncedValidate = useDebounceFn(validate, 200)
|
||||
|
||||
api.addEventListener('graphChanged', debouncedValidate)
|
||||
|
||||
onUnmounted(() => {
|
||||
api.removeEventListener('graphChanged', debouncedValidate)
|
||||
})
|
||||
|
||||
validate()
|
||||
}
|
||||
@@ -10,17 +10,8 @@ import { NodeSlot } from '@/lib/litegraph/src/node/NodeSlot'
|
||||
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
|
||||
import type {
|
||||
IBaseWidget,
|
||||
IWidgetAssetOptions,
|
||||
TWidgetValue
|
||||
} from '@/lib/litegraph/src/types/widgets'
|
||||
import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog'
|
||||
import {
|
||||
assetFilenameSchema,
|
||||
assetItemSchema
|
||||
} from '@/platform/assets/schemas/assetSchema'
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
import { getAssetFilename } from '@/platform/assets/utils/assetMetadataUtils'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import type { InputSpec } from '@/schemas/nodeDefSchema'
|
||||
import { app } from '@/scripts/app'
|
||||
import {
|
||||
@@ -28,10 +19,10 @@ import {
|
||||
addValueControlWidgets,
|
||||
isValidWidgetType
|
||||
} from '@/scripts/widgets'
|
||||
import { isPrimitiveNode } from '@/renderer/utils/nodeTypeGuards'
|
||||
import { CONFIG, GET_CONFIG } from '@/services/litegraphService'
|
||||
import { mergeInputSpec } from '@/utils/nodeDefUtil'
|
||||
import { applyTextReplacements } from '@/utils/searchAndReplace'
|
||||
import { isPrimitiveNode } from '@/renderer/utils/nodeTypeGuards'
|
||||
|
||||
const replacePropertyName = 'Run widget replace on values'
|
||||
export class PrimitiveNode extends LGraphNode {
|
||||
@@ -237,20 +228,6 @@ export class PrimitiveNode extends LGraphNode {
|
||||
// Store current size as addWidget resizes the node
|
||||
const [oldWidth, oldHeight] = this.size
|
||||
let widget: IBaseWidget
|
||||
|
||||
// Cloud: Use asset widget for model-eligible inputs
|
||||
if (isCloud && type === 'COMBO') {
|
||||
const isEligible = assetService.isAssetBrowserEligible(
|
||||
node.comfyClass,
|
||||
widgetName
|
||||
)
|
||||
if (isEligible) {
|
||||
widget = this.#createAssetWidget(node, widgetName, inputData)
|
||||
this.#finalizeWidget(widget, oldWidth, oldHeight, recreating)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (isValidWidgetType(type)) {
|
||||
widget = (ComfyWidgets[type](this, 'value', inputData, app) || {}).widget
|
||||
} else {
|
||||
@@ -300,84 +277,20 @@ export class PrimitiveNode extends LGraphNode {
|
||||
}
|
||||
}
|
||||
|
||||
this.#finalizeWidget(widget, oldWidth, oldHeight, recreating)
|
||||
}
|
||||
|
||||
#createAssetWidget(
|
||||
targetNode: LGraphNode,
|
||||
widgetName: string,
|
||||
inputData: InputSpec
|
||||
): IBaseWidget {
|
||||
const defaultValue = inputData[1]?.default as string | undefined
|
||||
const assetBrowserDialog = useAssetBrowserDialog()
|
||||
|
||||
const openModal = async (widget: IBaseWidget) => {
|
||||
await assetBrowserDialog.show({
|
||||
nodeType: targetNode.comfyClass ?? '',
|
||||
inputName: widgetName,
|
||||
currentValue: widget.value as string,
|
||||
onAssetSelected: (asset) => {
|
||||
const validatedAsset = assetItemSchema.safeParse(asset)
|
||||
if (!validatedAsset.success) {
|
||||
console.error('Invalid asset item:', validatedAsset.error.errors)
|
||||
return
|
||||
}
|
||||
|
||||
const filename = getAssetFilename(validatedAsset.data)
|
||||
const validatedFilename = assetFilenameSchema.safeParse(filename)
|
||||
if (!validatedFilename.success) {
|
||||
console.error(
|
||||
'Invalid asset filename:',
|
||||
validatedFilename.error.errors
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const oldValue = widget.value
|
||||
widget.value = validatedFilename.data
|
||||
widget.callback?.(
|
||||
widget.value,
|
||||
app.canvas,
|
||||
this,
|
||||
app.canvas.graph_mouse,
|
||||
{} as CanvasPointerEvent
|
||||
)
|
||||
this.onWidgetChanged?.(
|
||||
widget.name,
|
||||
validatedFilename.data,
|
||||
oldValue,
|
||||
widget
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const options: IWidgetAssetOptions = { openModal }
|
||||
return this.addWidget(
|
||||
'asset',
|
||||
'value',
|
||||
defaultValue ?? '',
|
||||
() => {},
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
#finalizeWidget(
|
||||
widget: IBaseWidget,
|
||||
oldWidth: number,
|
||||
oldHeight: number,
|
||||
recreating: boolean
|
||||
) {
|
||||
// When our value changes, update other widgets to reflect our changes
|
||||
// e.g. so LoadImage shows correct image
|
||||
widget.callback = useChainCallback(widget.callback, () => {
|
||||
this.applyToGraph()
|
||||
})
|
||||
|
||||
// Use the biggest dimensions in case the widgets caused the node to grow
|
||||
this.setSize([
|
||||
Math.max(this.size[0], oldWidth),
|
||||
Math.max(this.size[1], oldHeight)
|
||||
])
|
||||
|
||||
if (!recreating) {
|
||||
// Grow our node more if required
|
||||
const sz = this.computeSize()
|
||||
if (this.size[0] < sz[0]) {
|
||||
this.size[0] = sz[0]
|
||||
|
||||
@@ -233,6 +233,14 @@ export class LGraphNode
|
||||
static description?: string
|
||||
static filter?: string
|
||||
static skip_list?: boolean
|
||||
static nodeData?: {
|
||||
dev_only?: boolean
|
||||
deprecated?: boolean
|
||||
experimental?: boolean
|
||||
output_node?: boolean
|
||||
api_node?: boolean
|
||||
name?: string
|
||||
}
|
||||
|
||||
static resizeHandleSize = 15
|
||||
static resizeEdgeSize = 5
|
||||
|
||||
@@ -121,6 +121,7 @@
|
||||
"customize": "Customize",
|
||||
"experimental": "BETA",
|
||||
"deprecated": "DEPR",
|
||||
"devOnly": "DEV",
|
||||
"loadWorkflow": "Load Workflow",
|
||||
"goToNode": "Go to Node",
|
||||
"setAsBackground": "Set as Background",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as VueI18nModule from 'vue-i18n'
|
||||
|
||||
@@ -79,7 +80,7 @@ describe('useSubscriptionCredits', () => {
|
||||
let authStore: ReturnType<typeof useFirebaseAuthStore>
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
authStore = useFirebaseAuthStore()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { until } from '@vueuse/core'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
@@ -40,7 +41,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
let mockSettingStore: { get: ReturnType<typeof vi.fn> }
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
|
||||
// Clear the mock dismissal storage
|
||||
mockDismissalStorage.value = {}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
@@ -66,7 +67,7 @@ describe('useWorkflowStore', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useWorkflowStore()
|
||||
bookmarkStore = useWorkflowBookmarkStore()
|
||||
vi.clearAllMocks()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as I18n from 'vue-i18n'
|
||||
|
||||
@@ -108,7 +109,7 @@ describe('useWorkflowPersistence', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date('2025-01-01T00:00:00Z'))
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
vi.clearAllMocks()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useTeamWorkspaceStore } from './teamWorkspaceStore'
|
||||
@@ -111,7 +112,7 @@ const mockMemberWorkspace = {
|
||||
|
||||
describe('useTeamWorkspaceStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.clearAllMocks()
|
||||
vi.stubGlobal('localStorage', mockLocalStorage)
|
||||
sessionStorage.clear()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
@@ -15,7 +16,7 @@ vi.mock('@/stores/workspace/colorPaletteStore', () => ({
|
||||
|
||||
describe('useMinimapSettings', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
|
||||
@@ -150,7 +150,9 @@
|
||||
v-if="!isCollapsed && nodeData.resizable !== false"
|
||||
role="button"
|
||||
:aria-label="t('g.resizeFromBottomRight')"
|
||||
:class="cn(baseResizeHandleClasses, 'right-0 bottom-0 cursor-se-resize')"
|
||||
:class="
|
||||
cn(baseResizeHandleClasses, '-right-1 -bottom-1 cursor-se-resize')
|
||||
"
|
||||
@pointerdown.stop="handleResizePointerDown"
|
||||
/>
|
||||
</div>
|
||||
@@ -344,7 +346,7 @@ function initSizeStyles() {
|
||||
}
|
||||
|
||||
const baseResizeHandleClasses =
|
||||
'absolute h-3 w-3 opacity-0 pointer-events-auto focus-visible:outline focus-visible:outline-2 focus-visible:outline-white/40'
|
||||
'absolute h-5 w-5 opacity-0 pointer-events-auto focus-visible:outline focus-visible:outline-2 focus-visible:outline-white/40'
|
||||
|
||||
const MIN_NODE_WIDTH = 225
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
@@ -29,7 +30,7 @@ const makeNodeData = (overrides: Partial<VueNodeData> = {}): VueNodeData => ({
|
||||
})
|
||||
|
||||
const setupMockStores = () => {
|
||||
const pinia = createPinia()
|
||||
const pinia = createTestingPinia({ stubActions: false })
|
||||
setActivePinia(pinia)
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable vue/one-component-per-file */
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
@@ -84,7 +84,7 @@ const mountSlots = (nodeData: VueNodeData, readonly = false) => {
|
||||
})
|
||||
return mount(NodeSlots, {
|
||||
global: {
|
||||
plugins: [i18n, createPinia()],
|
||||
plugins: [i18n, createTestingPinia({ stubActions: false })],
|
||||
stubs: {
|
||||
InputSlot: InputSlotStub,
|
||||
OutputSlot: OutputSlotStub
|
||||
|
||||
@@ -174,6 +174,7 @@ export const zComfyNodeDef = z.object({
|
||||
python_module: z.string(),
|
||||
deprecated: z.boolean().optional(),
|
||||
experimental: z.boolean().optional(),
|
||||
dev_only: z.boolean().optional(),
|
||||
api_node: z.boolean().optional()
|
||||
})
|
||||
|
||||
|
||||
@@ -257,6 +257,7 @@ export const zComfyNodeDef = z.object({
|
||||
python_module: z.string(),
|
||||
deprecated: z.boolean().optional(),
|
||||
experimental: z.boolean().optional(),
|
||||
dev_only: z.boolean().optional(),
|
||||
/**
|
||||
* Whether the node is an API node. Running API nodes requires login to
|
||||
* Comfy Org account.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { CORE_KEYBINDINGS } from '@/constants/coreKeybindings'
|
||||
@@ -30,7 +31,7 @@ describe('keybindingService - Escape key handling', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
|
||||
// Mock command store execute
|
||||
const commandStore = useCommandStore()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { app } from '@/scripts/app'
|
||||
@@ -68,7 +69,7 @@ describe('keybindingService - Event Forwarding', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
|
||||
// Mock command store execute
|
||||
const commandStore = useCommandStore()
|
||||
|
||||
@@ -261,7 +261,7 @@ export const useLitegraphService = () => {
|
||||
static comfyClass: string
|
||||
static override title: string
|
||||
static override category: string
|
||||
static nodeData: ComfyNodeDefV1 & ComfyNodeDefV2
|
||||
static override nodeData: ComfyNodeDefV1 & ComfyNodeDefV2
|
||||
|
||||
_initialMinSize = { width: 1, height: 1 }
|
||||
|
||||
@@ -394,7 +394,7 @@ export const useLitegraphService = () => {
|
||||
static comfyClass: string
|
||||
static override title: string
|
||||
static override category: string
|
||||
static nodeData: ComfyNodeDefV1 & ComfyNodeDefV2
|
||||
static override nodeData: ComfyNodeDefV1 & ComfyNodeDefV2
|
||||
|
||||
_initialMinSize = { width: 1, height: 1 }
|
||||
|
||||
@@ -496,6 +496,13 @@ export const useLitegraphService = () => {
|
||||
// because `registerNodeType` will overwrite the assignments.
|
||||
node.category = nodeDef.category
|
||||
node.title = nodeDef.display_name || nodeDef.name
|
||||
|
||||
// Set skip_list for dev-only nodes based on current DevMode setting
|
||||
// This ensures nodes registered after initial load respect the current setting
|
||||
if (nodeDef.dev_only) {
|
||||
const settingStore = useSettingStore()
|
||||
node.skip_list = !settingStore.get('Comfy.DevMode')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -876,7 +883,11 @@ export const useLitegraphService = () => {
|
||||
|
||||
function getCanvasCenter(): Point {
|
||||
const dpi = Math.max(window.devicePixelRatio ?? 1, 1)
|
||||
const [x, y, w, h] = app.canvas.ds.visible_area
|
||||
const visibleArea = app.canvas?.ds?.visible_area
|
||||
if (!visibleArea) {
|
||||
return [0, 0]
|
||||
}
|
||||
const [x, y, w, h] = visibleArea
|
||||
return [x + w / dpi / 2, y + h / dpi / 2]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
@@ -83,7 +84,7 @@ describe('useComfyRegistryStore', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.clearAllMocks()
|
||||
mockRegistryService = {
|
||||
isLoading: ref(false),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
@@ -11,7 +12,7 @@ const MockComponent = defineComponent({
|
||||
|
||||
describe('dialogStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
describe('priority system', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { useDomWidgetStore } from '@/stores/domWidgetStore'
|
||||
@@ -30,7 +31,7 @@ describe('domWidgetStore', () => {
|
||||
let store: ReturnType<typeof useDomWidgetStore>
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useDomWidgetStore()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { app } from '@/scripts/app'
|
||||
@@ -59,7 +60,7 @@ describe('useExecutionStore - NodeLocatorId conversions', () => {
|
||||
mockNodeIdToNodeLocatorId.mockReset()
|
||||
mockNodeLocatorIdToNodeExecutionId.mockReset()
|
||||
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useExecutionStore()
|
||||
})
|
||||
|
||||
@@ -137,7 +138,7 @@ describe('useExecutionStore - Node Error Lookups', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useExecutionStore()
|
||||
})
|
||||
|
||||
|
||||
@@ -29,10 +29,11 @@ import type {
|
||||
} from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useGraphErrorStateStore } from '@/stores/graphErrorStateStore'
|
||||
import type { GraphError } from '@/stores/graphErrorStateStore'
|
||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||
import type { NodeLocatorId } from '@/types/nodeIdentification'
|
||||
import { createNodeLocatorId } from '@/types/nodeIdentification'
|
||||
import { forEachNode, getNodeByExecutionId } from '@/utils/graphTraversalUtil'
|
||||
|
||||
interface QueuedPrompt {
|
||||
/**
|
||||
@@ -584,59 +585,47 @@ export const useExecutionStore = defineStore('execution', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update node and slot error flags when validation errors change.
|
||||
* Propagates errors up subgraph chains.
|
||||
* Sync backend validation errors to centralized graph error store.
|
||||
* The store handles flag updates and subgraph propagation.
|
||||
*/
|
||||
watch(lastNodeErrors, () => {
|
||||
if (!app.rootGraph) return
|
||||
const errorStore = useGraphErrorStateStore()
|
||||
|
||||
// Clear all error flags
|
||||
forEachNode(app.rootGraph, (node) => {
|
||||
node.has_errors = false
|
||||
if (node.inputs) {
|
||||
for (const slot of node.inputs) {
|
||||
slot.hasErrors = false
|
||||
}
|
||||
}
|
||||
})
|
||||
if (!lastNodeErrors.value) {
|
||||
errorStore.execute({ type: 'CLEAR_SOURCE', source: 'backend' })
|
||||
return
|
||||
}
|
||||
|
||||
if (!lastNodeErrors.value) return
|
||||
const errors: GraphError[] = []
|
||||
|
||||
// Set error flags on nodes and slots
|
||||
for (const [executionId, nodeError] of Object.entries(
|
||||
lastNodeErrors.value
|
||||
)) {
|
||||
const node = getNodeByExecutionId(app.rootGraph, executionId)
|
||||
if (!node) continue
|
||||
const locatorId = executionIdToNodeLocatorId(executionId)
|
||||
if (!locatorId) continue
|
||||
|
||||
node.has_errors = true
|
||||
errors.push({
|
||||
key: `backend:node:${locatorId}`,
|
||||
source: 'backend',
|
||||
target: { kind: 'node', nodeId: locatorId },
|
||||
message: nodeError.errors[0]?.message
|
||||
})
|
||||
|
||||
// Mark input slots with errors
|
||||
if (node.inputs) {
|
||||
for (const error of nodeError.errors) {
|
||||
const slotName = error.extra_info?.input_name
|
||||
if (!slotName) continue
|
||||
|
||||
const slot = node.inputs.find((s) => s.name === slotName)
|
||||
if (slot) {
|
||||
slot.hasErrors = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate errors to parent subgraph nodes
|
||||
const parts = executionId.split(':')
|
||||
for (let i = parts.length - 1; i > 0; i--) {
|
||||
const parentExecutionId = parts.slice(0, i).join(':')
|
||||
const parentNode = getNodeByExecutionId(
|
||||
app.rootGraph,
|
||||
parentExecutionId
|
||||
)
|
||||
if (parentNode) {
|
||||
parentNode.has_errors = true
|
||||
for (const error of nodeError.errors) {
|
||||
const slotName = error.extra_info?.input_name
|
||||
if (slotName) {
|
||||
errors.push({
|
||||
key: `backend:slot:${locatorId}:${slotName}`,
|
||||
source: 'backend',
|
||||
target: { kind: 'slot', nodeId: locatorId, slotName },
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errorStore.execute({ type: 'REPLACE_SOURCE', source: 'backend', errors })
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FirebaseError } from 'firebase/app'
|
||||
import * as firebaseAuth from 'firebase/auth'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import * as vuefire from 'vuefire'
|
||||
|
||||
@@ -153,7 +154,7 @@ describe('useFirebaseAuthStore', () => {
|
||||
})
|
||||
|
||||
// Initialize Pinia
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useFirebaseAuthStore()
|
||||
|
||||
// Reset and set up getIdToken mock
|
||||
@@ -175,7 +176,7 @@ describe('useFirebaseAuthStore', () => {
|
||||
|
||||
vi.mocked(vuefire.useFirebaseAuth).mockReturnValue(mockAuth as any)
|
||||
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useFirebaseAuthStore()
|
||||
})
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
}
|
||||
|
||||
const createCustomer = async (): Promise<CreateCustomerResponse> => {
|
||||
const authHeader = await getFirebaseAuthHeader()
|
||||
const authHeader = await getAuthHeader()
|
||||
if (!authHeader) {
|
||||
throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated'))
|
||||
}
|
||||
|
||||
257
src/stores/graphErrorStateStore.test.ts
Normal file
257
src/stores/graphErrorStateStore.test.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { useGraphErrorStateStore } from './graphErrorStateStore'
|
||||
import type { GraphError } from './graphErrorStateStore'
|
||||
|
||||
describe('graphErrorStateStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
describe('REPLACE_SOURCE command', () => {
|
||||
it('adds errors for a source', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
const errors: GraphError[] = [
|
||||
{
|
||||
key: 'frontend:slot:123:model',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '123', slotName: 'model' },
|
||||
code: 'MISSING_REQUIRED_INPUT'
|
||||
}
|
||||
]
|
||||
|
||||
store.execute({ type: 'REPLACE_SOURCE', source: 'frontend', errors })
|
||||
|
||||
expect(store.hasErrorsForNode('123')).toBe(true)
|
||||
expect(store.hasSlotError('123', 'model')).toBe(true)
|
||||
expect(store.version).toBe(1)
|
||||
})
|
||||
|
||||
it('replaces all errors for a source', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:slot:1:a',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '1', slotName: 'a' }
|
||||
},
|
||||
{
|
||||
key: 'frontend:slot:2:b',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '2', slotName: 'b' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
expect(store.hasErrorsForNode('1')).toBe(true)
|
||||
expect(store.hasErrorsForNode('2')).toBe(true)
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:slot:3:c',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '3', slotName: 'c' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
expect(store.hasErrorsForNode('1')).toBe(false)
|
||||
expect(store.hasErrorsForNode('2')).toBe(false)
|
||||
expect(store.hasErrorsForNode('3')).toBe(true)
|
||||
})
|
||||
|
||||
it('preserves errors from other sources', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'backend',
|
||||
errors: [
|
||||
{
|
||||
key: 'backend:node:1',
|
||||
source: 'backend',
|
||||
target: { kind: 'node', nodeId: '1' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:slot:2:a',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '2', slotName: 'a' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
expect(store.hasErrorsForNode('1')).toBe(true)
|
||||
expect(store.hasErrorsForNode('2')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('CLEAR_SOURCE command', () => {
|
||||
it('clears errors for a source', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:slot:1:a',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '1', slotName: 'a' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
store.execute({ type: 'CLEAR_SOURCE', source: 'frontend' })
|
||||
|
||||
expect(store.hasErrorsForNode('1')).toBe(false)
|
||||
})
|
||||
|
||||
it('preserves other sources', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'backend',
|
||||
errors: [
|
||||
{
|
||||
key: 'backend:node:1',
|
||||
source: 'backend',
|
||||
target: { kind: 'node', nodeId: '1' }
|
||||
}
|
||||
]
|
||||
})
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:slot:2:a',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '2', slotName: 'a' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
store.execute({ type: 'CLEAR_SOURCE', source: 'frontend' })
|
||||
|
||||
expect(store.hasErrorsForNode('1')).toBe(true)
|
||||
expect(store.hasErrorsForNode('2')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('CLEAR_ALL command', () => {
|
||||
it('clears all errors', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'backend',
|
||||
errors: [
|
||||
{
|
||||
key: 'backend:node:1',
|
||||
source: 'backend',
|
||||
target: { kind: 'node', nodeId: '1' }
|
||||
}
|
||||
]
|
||||
})
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:slot:2:a',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '2', slotName: 'a' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
store.execute({ type: 'CLEAR_ALL' })
|
||||
|
||||
expect(store.hasErrorsForNode('1')).toBe(false)
|
||||
expect(store.hasErrorsForNode('2')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getErrorsForNode', () => {
|
||||
it('returns all errors for a node', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:slot:1:a',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '1', slotName: 'a' }
|
||||
},
|
||||
{
|
||||
key: 'frontend:slot:1:b',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '1', slotName: 'b' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const errors = store.getErrorsForNode('1')
|
||||
expect(errors).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('returns empty array for node without errors', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
expect(store.getErrorsForNode('999')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSlotErrors', () => {
|
||||
it('returns only slot errors for specific slot', () => {
|
||||
const store = useGraphErrorStateStore()
|
||||
|
||||
store.execute({
|
||||
type: 'REPLACE_SOURCE',
|
||||
source: 'frontend',
|
||||
errors: [
|
||||
{
|
||||
key: 'frontend:node:1',
|
||||
source: 'frontend',
|
||||
target: { kind: 'node', nodeId: '1' }
|
||||
},
|
||||
{
|
||||
key: 'frontend:slot:1:model',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '1', slotName: 'model' }
|
||||
},
|
||||
{
|
||||
key: 'frontend:slot:1:clip',
|
||||
source: 'frontend',
|
||||
target: { kind: 'slot', nodeId: '1', slotName: 'clip' }
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const slotErrors = store.getSlotErrors('1', 'model')
|
||||
expect(slotErrors).toHaveLength(1)
|
||||
expect(slotErrors[0].target).toEqual({
|
||||
kind: 'slot',
|
||||
nodeId: '1',
|
||||
slotName: 'model'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
144
src/stores/graphErrorStateStore.ts
Normal file
144
src/stores/graphErrorStateStore.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { shallowRef } from 'vue'
|
||||
|
||||
import type { NodeLocatorId } from '@/types/nodeIdentification'
|
||||
|
||||
type GraphErrorSource = 'frontend' | 'backend'
|
||||
|
||||
type GraphErrorTarget =
|
||||
| { kind: 'node'; nodeId: NodeLocatorId }
|
||||
| { kind: 'slot'; nodeId: NodeLocatorId; slotName: string }
|
||||
|
||||
export interface GraphError {
|
||||
key: string
|
||||
source: GraphErrorSource
|
||||
target: GraphErrorTarget
|
||||
code?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
type GraphErrorCommand =
|
||||
| { type: 'REPLACE_SOURCE'; source: GraphErrorSource; errors: GraphError[] }
|
||||
| { type: 'CLEAR_SOURCE'; source: GraphErrorSource }
|
||||
| { type: 'CLEAR_ALL' }
|
||||
|
||||
export const useGraphErrorStateStore = defineStore('graphErrorState', () => {
|
||||
const errorsByKey = shallowRef(new Map<string, GraphError>())
|
||||
const keysBySource = shallowRef(new Map<GraphErrorSource, Set<string>>())
|
||||
const keysByNode = shallowRef(new Map<NodeLocatorId, Set<string>>())
|
||||
const version = shallowRef(0)
|
||||
|
||||
function addErrorInternal(error: GraphError): void {
|
||||
const newErrorsByKey = new Map(errorsByKey.value)
|
||||
newErrorsByKey.set(error.key, error)
|
||||
errorsByKey.value = newErrorsByKey
|
||||
|
||||
const newKeysBySource = new Map(keysBySource.value)
|
||||
if (!newKeysBySource.has(error.source)) {
|
||||
newKeysBySource.set(error.source, new Set())
|
||||
}
|
||||
newKeysBySource.get(error.source)!.add(error.key)
|
||||
keysBySource.value = newKeysBySource
|
||||
|
||||
const nodeId = error.target.nodeId
|
||||
const newKeysByNode = new Map(keysByNode.value)
|
||||
if (!newKeysByNode.has(nodeId)) {
|
||||
newKeysByNode.set(nodeId, new Set())
|
||||
}
|
||||
newKeysByNode.get(nodeId)!.add(error.key)
|
||||
keysByNode.value = newKeysByNode
|
||||
}
|
||||
|
||||
function clearSourceInternal(source: GraphErrorSource): void {
|
||||
const keys = keysBySource.value.get(source)
|
||||
if (!keys || keys.size === 0) return
|
||||
|
||||
const newErrorsByKey = new Map(errorsByKey.value)
|
||||
const newKeysByNode = new Map(keysByNode.value)
|
||||
|
||||
for (const key of keys) {
|
||||
const error = newErrorsByKey.get(key)
|
||||
if (error) {
|
||||
const nodeId = error.target.nodeId
|
||||
const nodeKeys = newKeysByNode.get(nodeId)
|
||||
if (nodeKeys) {
|
||||
const newNodeKeys = new Set(nodeKeys)
|
||||
newNodeKeys.delete(key)
|
||||
if (newNodeKeys.size === 0) {
|
||||
newKeysByNode.delete(nodeId)
|
||||
} else {
|
||||
newKeysByNode.set(nodeId, newNodeKeys)
|
||||
}
|
||||
}
|
||||
newErrorsByKey.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
errorsByKey.value = newErrorsByKey
|
||||
keysByNode.value = newKeysByNode
|
||||
|
||||
const newKeysBySource = new Map(keysBySource.value)
|
||||
newKeysBySource.delete(source)
|
||||
keysBySource.value = newKeysBySource
|
||||
}
|
||||
|
||||
function execute(command: GraphErrorCommand): void {
|
||||
switch (command.type) {
|
||||
case 'REPLACE_SOURCE': {
|
||||
clearSourceInternal(command.source)
|
||||
for (const error of command.errors) {
|
||||
addErrorInternal(error)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'CLEAR_SOURCE': {
|
||||
clearSourceInternal(command.source)
|
||||
break
|
||||
}
|
||||
case 'CLEAR_ALL': {
|
||||
errorsByKey.value = new Map()
|
||||
keysBySource.value = new Map()
|
||||
keysByNode.value = new Map()
|
||||
break
|
||||
}
|
||||
}
|
||||
version.value++
|
||||
}
|
||||
|
||||
function getErrorsForNode(nodeId: NodeLocatorId): GraphError[] {
|
||||
const keys = keysByNode.value.get(nodeId)
|
||||
if (!keys) return []
|
||||
return [...keys]
|
||||
.map((k) => errorsByKey.value.get(k))
|
||||
.filter((e): e is GraphError => e !== undefined)
|
||||
}
|
||||
|
||||
function hasErrorsForNode(nodeId: NodeLocatorId): boolean {
|
||||
const keys = keysByNode.value.get(nodeId)
|
||||
return keys !== undefined && keys.size > 0
|
||||
}
|
||||
|
||||
function getSlotErrors(
|
||||
nodeId: NodeLocatorId,
|
||||
slotName: string
|
||||
): GraphError[] {
|
||||
return getErrorsForNode(nodeId).filter(
|
||||
(e) => e.target.kind === 'slot' && e.target.slotName === slotName
|
||||
)
|
||||
}
|
||||
|
||||
function hasSlotError(nodeId: NodeLocatorId, slotName: string): boolean {
|
||||
return getSlotErrors(nodeId, slotName).length > 0
|
||||
}
|
||||
|
||||
return {
|
||||
version,
|
||||
errorsByKey,
|
||||
keysByNode,
|
||||
execute,
|
||||
getErrorsForNode,
|
||||
hasErrorsForNode,
|
||||
getSlotErrors,
|
||||
hasSlotError
|
||||
}
|
||||
})
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
@@ -30,7 +31,7 @@ const createMockOutputs = (
|
||||
|
||||
describe('imagePreviewStore getPreviewParam', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(litegraphUtil.isVideoNode).mockReturnValue(false)
|
||||
})
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { KeybindingImpl, useKeybindingStore } from '@/stores/keybindingStore'
|
||||
|
||||
describe('useKeybindingStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
it('should add and retrieve default keybindings', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
@@ -89,7 +90,7 @@ describe('useModelStore', () => {
|
||||
let store: ReturnType<typeof useModelStore>
|
||||
|
||||
beforeEach(async () => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.resetAllMocks()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
|
||||
@@ -82,7 +83,7 @@ vi.mock('@/stores/nodeDefStore', async (importOriginal) => {
|
||||
|
||||
describe('useModelToNodeStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
@@ -330,7 +331,7 @@ describe('useModelToNodeStore', () => {
|
||||
|
||||
it('should not register when nodeDefStore is empty', () => {
|
||||
// Create fresh Pinia for this test to avoid state persistence
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
|
||||
vi.mocked(useNodeDefStore, { partial: true }).mockReturnValue({
|
||||
nodeDefsByName: {}
|
||||
@@ -355,7 +356,7 @@ describe('useModelToNodeStore', () => {
|
||||
|
||||
it('should return empty Record when nodeDefStore is empty', () => {
|
||||
// Create fresh Pinia for this test to avoid state persistence
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
|
||||
vi.mocked(useNodeDefStore, { partial: true }).mockReturnValue({
|
||||
nodeDefsByName: {}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
@@ -9,7 +10,7 @@ describe('useNodeDefStore', () => {
|
||||
let store: ReturnType<typeof useNodeDefStore>
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useNodeDefStore()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import axios from 'axios'
|
||||
import _ from 'es-toolkit/compat'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watchEffect } from 'vue'
|
||||
|
||||
import { isProxyWidget } from '@/core/graph/subgraph/proxyWidget'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { transformNodeDefV1ToV2 } from '@/schemas/nodeDef/migration'
|
||||
import type {
|
||||
@@ -17,6 +18,7 @@ import type {
|
||||
ComfyOutputTypesSpec as ComfyOutputSpecV1,
|
||||
PriceBadge
|
||||
} from '@/schemas/nodeDefSchema'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { NodeSearchService } from '@/services/nodeSearchService'
|
||||
import { useSubgraphStore } from '@/stores/subgraphStore'
|
||||
import { NodeSourceType, getNodeSource } from '@/types/nodeSource'
|
||||
@@ -41,6 +43,7 @@ export class ComfyNodeDefImpl
|
||||
readonly help: string
|
||||
readonly deprecated: boolean
|
||||
readonly experimental: boolean
|
||||
readonly dev_only: boolean
|
||||
readonly output_node: boolean
|
||||
readonly api_node: boolean
|
||||
/**
|
||||
@@ -133,6 +136,7 @@ export class ComfyNodeDefImpl
|
||||
this.deprecated = obj.deprecated ?? obj.category === ''
|
||||
this.experimental =
|
||||
obj.experimental ?? obj.category.startsWith('_for_testing')
|
||||
this.dev_only = obj.dev_only ?? false
|
||||
this.output_node = obj.output_node
|
||||
this.api_node = !!obj.api_node
|
||||
this.input = obj.input ?? {}
|
||||
@@ -174,6 +178,7 @@ export class ComfyNodeDefImpl
|
||||
get nodeLifeCycleBadgeText(): string {
|
||||
if (this.deprecated) return '[DEPR]'
|
||||
if (this.experimental) return '[BETA]'
|
||||
if (this.dev_only) return '[DEV]'
|
||||
return ''
|
||||
}
|
||||
}
|
||||
@@ -299,12 +304,27 @@ export interface NodeDefFilter {
|
||||
}
|
||||
|
||||
export const useNodeDefStore = defineStore('nodeDef', () => {
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const nodeDefsByName = ref<Record<string, ComfyNodeDefImpl>>({})
|
||||
const nodeDefsByDisplayName = ref<Record<string, ComfyNodeDefImpl>>({})
|
||||
const showDeprecated = ref(false)
|
||||
const showExperimental = ref(false)
|
||||
const showDevOnly = computed(() => settingStore.get('Comfy.DevMode'))
|
||||
const nodeDefFilters = ref<NodeDefFilter[]>([])
|
||||
|
||||
// Update skip_list on all registered node types when dev mode changes
|
||||
// This ensures LiteGraph's getNodeTypesCategories/getNodeTypesInCategory
|
||||
// correctly filter dev-only nodes from the right-click context menu
|
||||
watchEffect(() => {
|
||||
const devModeEnabled = showDevOnly.value
|
||||
for (const nodeType of Object.values(LiteGraph.registered_node_types)) {
|
||||
if (nodeType.nodeData?.dev_only) {
|
||||
nodeType.skip_list = !devModeEnabled
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const nodeDefs = computed(() => {
|
||||
const subgraphStore = useSubgraphStore()
|
||||
// Blueprints first for discoverability in the node library sidebar
|
||||
@@ -422,6 +442,14 @@ export const useNodeDefStore = defineStore('nodeDef', () => {
|
||||
predicate: (nodeDef) => showExperimental.value || !nodeDef.experimental
|
||||
})
|
||||
|
||||
// Dev-only nodes filter
|
||||
registerNodeDefFilter({
|
||||
id: 'core.dev_only',
|
||||
name: 'Hide Dev-Only Nodes',
|
||||
description: 'Hides nodes marked as dev-only unless dev mode is enabled',
|
||||
predicate: (nodeDef) => showDevOnly.value || !nodeDef.dev_only
|
||||
})
|
||||
|
||||
// Subgraph nodes filter
|
||||
// Filter out litegraph typed subgraphs, saved blueprints are added in separately
|
||||
registerNodeDefFilter({
|
||||
@@ -446,6 +474,7 @@ export const useNodeDefStore = defineStore('nodeDef', () => {
|
||||
nodeDefsByDisplayName,
|
||||
showDeprecated,
|
||||
showExperimental,
|
||||
showDevOnly,
|
||||
nodeDefFilters,
|
||||
|
||||
nodeDefs,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type {
|
||||
@@ -71,7 +72,7 @@ describe('TaskItemImpl.loadWorkflow - workflow fetching', () => {
|
||||
let mockFetchApi: ReturnType<typeof vi.fn>
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.clearAllMocks()
|
||||
|
||||
mockFetchApi = vi.fn()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { JobListItem } from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
@@ -240,7 +241,7 @@ describe('useQueueStore', () => {
|
||||
let store: ReturnType<typeof useQueueStore>
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useQueueStore()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import type { ServerConfig } from '@/constants/serverConfig'
|
||||
@@ -14,7 +15,7 @@ describe('useServerConfigStore', () => {
|
||||
let store: ReturnType<typeof useServerConfigStore>
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useServerConfigStore()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
@@ -47,7 +48,7 @@ vi.mock('@/utils/graphTraversalUtil', () => ({
|
||||
|
||||
describe('useSubgraphNavigationStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
it('should not clear navigation stack when workflow internal state changes', async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
@@ -46,7 +47,7 @@ const mockCanvas = app.canvas as any
|
||||
|
||||
describe('useSubgraphNavigationStore - Viewport Persistence', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
// Reset canvas state
|
||||
mockCanvas.ds.scale = 1
|
||||
mockCanvas.ds.offset = [0, 0]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
|
||||
@@ -78,7 +79,7 @@ describe('useSubgraphStore', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useSubgraphStore()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { api } from '@/scripts/api'
|
||||
@@ -25,7 +26,7 @@ describe('useSystemStatsStore', () => {
|
||||
beforeEach(() => {
|
||||
// Mock API to prevent automatic fetch on store creation
|
||||
vi.mocked(api.getSystemStats).mockResolvedValue(null as any)
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useSystemStatsStore()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useTemplateRankingStore } from '@/stores/templateRankingStore'
|
||||
@@ -12,7 +13,7 @@ vi.mock('axios', () => ({
|
||||
|
||||
describe('templateRankingStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { api } from '@/scripts/api'
|
||||
@@ -19,7 +20,7 @@ describe('useUserFileStore', () => {
|
||||
let store: ReturnType<typeof useUserFileStore>
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useUserFileStore()
|
||||
vi.resetAllMocks()
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
@@ -53,7 +54,7 @@ vi.mock('@/utils/envUtil', () => ({
|
||||
|
||||
describe('useBottomPanelStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
it('should initialize with empty panels', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore'
|
||||
@@ -14,7 +15,7 @@ describe('nodeHelpStore', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
})
|
||||
|
||||
it('should initialize with empty state', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type NodeSearchBoxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
|
||||
@@ -34,7 +35,7 @@ function createMockSettingStore(): ReturnType<typeof useSettingStore> {
|
||||
|
||||
describe('useSearchBoxStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('nodeFilterUtil', () => {
|
||||
): LGraphNode => {
|
||||
// Create a custom class with the nodeData static property
|
||||
class MockNode extends LGraphNode {
|
||||
static nodeData = isOutputNode ? { output_node: true } : {}
|
||||
static override nodeData = isOutputNode ? { output_node: true } : {}
|
||||
}
|
||||
|
||||
const node = new MockNode('')
|
||||
@@ -71,11 +71,11 @@ describe('nodeFilterUtil', () => {
|
||||
})
|
||||
|
||||
it('should handle nodes with undefined output_node', () => {
|
||||
class MockNodeWithOtherData extends LGraphNode {
|
||||
static nodeData = { someOtherProperty: true }
|
||||
class MockNodeWithEmptyData extends LGraphNode {
|
||||
static override nodeData = {}
|
||||
}
|
||||
|
||||
const node = new MockNodeWithOtherData('')
|
||||
const node = new MockNodeWithEmptyData('')
|
||||
node.id = 1
|
||||
|
||||
const result = filterOutputNodes([node])
|
||||
|
||||
@@ -45,6 +45,8 @@ import UnloadWindowConfirmDialog from '@/components/dialog/UnloadWindowConfirmDi
|
||||
import GraphCanvas from '@/components/graph/GraphCanvas.vue'
|
||||
import GlobalToast from '@/components/toast/GlobalToast.vue'
|
||||
import RerouteMigrationToast from '@/components/toast/RerouteMigrationToast.vue'
|
||||
import { useGraphErrorState } from '@/composables/graph/useGraphErrorState'
|
||||
import { useRequiredConnectionValidator } from '@/composables/graph/useRequiredConnectionValidator'
|
||||
import { useBrowserTabTitle } from '@/composables/useBrowserTabTitle'
|
||||
import { useCoreCommands } from '@/composables/useCoreCommands'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
@@ -85,6 +87,8 @@ import ManagerProgressToast from '@/workbench/extensions/manager/components/Mana
|
||||
setupAutoQueueHandler()
|
||||
useProgressFavicon()
|
||||
useBrowserTabTitle()
|
||||
useGraphErrorState()
|
||||
useRequiredConnectionValidator()
|
||||
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { computed, ref } from 'vue'
|
||||
@@ -44,11 +45,11 @@ vi.mock(
|
||||
)
|
||||
|
||||
describe('NodeConflictDialogContent', () => {
|
||||
let pinia: ReturnType<typeof createPinia>
|
||||
let pinia: ReturnType<typeof createTestingPinia>
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
pinia = createPinia()
|
||||
pinia = createTestingPinia({ stubActions: false })
|
||||
setActivePinia(pinia)
|
||||
// Reset mock data
|
||||
mockConflictData.value = []
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { VueWrapper } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import Tooltip from 'primevue/tooltip'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
@@ -89,7 +89,7 @@ describe('PackVersionBadge', () => {
|
||||
...props
|
||||
},
|
||||
global: {
|
||||
plugins: [PrimeVue, createPinia(), i18n],
|
||||
plugins: [PrimeVue, createTestingPinia({ stubActions: false }), i18n],
|
||||
directives: {
|
||||
tooltip: Tooltip
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { VueWrapper } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import Listbox from 'primevue/listbox'
|
||||
@@ -115,7 +115,7 @@ describe('PackVersionSelectorPopover', () => {
|
||||
...props
|
||||
},
|
||||
global: {
|
||||
plugins: [PrimeVue, createPinia(), i18n],
|
||||
plugins: [PrimeVue, createTestingPinia({ stubActions: false }), i18n],
|
||||
components: {
|
||||
Listbox,
|
||||
VerifiedIcon,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { VueWrapper } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import ToggleSwitch from 'primevue/toggleswitch'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
@@ -81,7 +81,7 @@ describe('PackEnableToggle', () => {
|
||||
...props
|
||||
},
|
||||
global: {
|
||||
plugins: [PrimeVue, createPinia(), i18n]
|
||||
plugins: [PrimeVue, createTestingPinia({ stubActions: false }), i18n]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { VueWrapper } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
@@ -73,7 +73,7 @@ describe('PackCardFooter', () => {
|
||||
...props
|
||||
},
|
||||
global: {
|
||||
plugins: [PrimeVue, createPinia(), i18n],
|
||||
plugins: [PrimeVue, createTestingPinia({ stubActions: false }), i18n],
|
||||
provide: {
|
||||
[IsInstallingKey]: ref(false)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { VueWrapper } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
@@ -32,7 +32,7 @@ describe('GridSkeleton', () => {
|
||||
...props
|
||||
},
|
||||
global: {
|
||||
plugins: [PrimeVue, createPinia(), i18n],
|
||||
plugins: [PrimeVue, createTestingPinia({ stubActions: false }), i18n],
|
||||
stubs: {
|
||||
PackCardSkeleton: true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
@@ -36,7 +37,7 @@ describe('usePacksSelection', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
const pinia = createPinia()
|
||||
const pinia = createTestingPinia({ stubActions: false })
|
||||
setActivePinia(pinia)
|
||||
|
||||
managerStore = useComfyManagerStore()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
@@ -48,7 +49,7 @@ describe('usePacksStatus', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setActivePinia(createPinia())
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
conflictDetectionStore = useConflictDetectionStore()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createSharedComposable } from '@vueuse/core'
|
||||
import { computed, onUnmounted, ref } from 'vue'
|
||||
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
@@ -9,7 +10,6 @@ import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { mapAllNodes } from '@/utils/graphTraversalUtil'
|
||||
import { useNodePacks } from '@/workbench/extensions/manager/composables/nodePack/useNodePacks'
|
||||
import type { UseNodePacksOptions } from '@/workbench/extensions/manager/types/comfyManagerTypes'
|
||||
|
||||
type WorkflowPack = {
|
||||
id:
|
||||
@@ -22,9 +22,10 @@ const CORE_NODES_PACK_NAME = 'comfy-core'
|
||||
|
||||
/**
|
||||
* Handles parsing node pack metadata from nodes on the graph and fetching the
|
||||
* associated node packs from the registry
|
||||
* associated node packs from the registry.
|
||||
* This is a shared singleton composable - all components use the same instance.
|
||||
*/
|
||||
export const useWorkflowPacks = (options: UseNodePacksOptions = {}) => {
|
||||
const _useWorkflowPacks = () => {
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const systemStatsStore = useSystemStatsStore()
|
||||
const { inferPackFromNodeName } = useComfyRegistryStore()
|
||||
@@ -129,7 +130,7 @@ export const useWorkflowPacks = (options: UseNodePacksOptions = {}) => {
|
||||
)
|
||||
|
||||
const { startFetch, cleanup, error, isLoading, nodePacks, isReady } =
|
||||
useNodePacks(workflowPacksIds, options)
|
||||
useNodePacks(workflowPacksIds)
|
||||
|
||||
const isIdInWorkflow = (packId: string) =>
|
||||
workflowPacksIds.value.includes(packId)
|
||||
@@ -153,3 +154,5 @@ export const useWorkflowPacks = (options: UseNodePacksOptions = {}) => {
|
||||
filterWorkflowPack
|
||||
}
|
||||
}
|
||||
|
||||
export const useWorkflowPacks = createSharedComposable(_useWorkflowPacks)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user