mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
Compare commits
3 Commits
v1.32.6
...
graphMutat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46ba1629eb | ||
|
|
aef0697b4b | ||
|
|
b58fe1184c |
@@ -67,9 +67,9 @@ This is critical for better file inspection:
|
|||||||
|
|
||||||
Use git locally for much faster analysis:
|
Use git locally for much faster analysis:
|
||||||
|
|
||||||
1. Get list of changed files: `git diff --name-only "$BASE_SHA" > changed_files.txt`
|
1. Get list of changed files: `git diff --name-only "origin/$BASE_BRANCH" > changed_files.txt`
|
||||||
2. Get the full diff: `git diff "$BASE_SHA" > pr_diff.txt`
|
2. Get the full diff: `git diff "origin/$BASE_BRANCH" > pr_diff.txt`
|
||||||
3. Get detailed file changes with status: `git diff --name-status "$BASE_SHA" > file_changes.txt`
|
3. Get detailed file changes with status: `git diff --name-status "origin/$BASE_BRANCH" > file_changes.txt`
|
||||||
|
|
||||||
### Step 1.5: Create Analysis Cache
|
### Step 1.5: Create Analysis Cache
|
||||||
|
|
||||||
|
|||||||
@@ -294,6 +294,7 @@ echo "Last stable release: $LAST_STABLE"
|
|||||||
1. Run complete test suite:
|
1. Run complete test suite:
|
||||||
```bash
|
```bash
|
||||||
pnpm test:unit
|
pnpm test:unit
|
||||||
|
pnpm test:component
|
||||||
```
|
```
|
||||||
2. Run type checking:
|
2. Run type checking:
|
||||||
```bash
|
```bash
|
||||||
@@ -458,15 +459,15 @@ echo "Workflow triggered. Waiting for PR creation..."
|
|||||||
3. **IMMEDIATELY CHECK**: Did release workflow trigger?
|
3. **IMMEDIATELY CHECK**: Did release workflow trigger?
|
||||||
```bash
|
```bash
|
||||||
sleep 10
|
sleep 10
|
||||||
gh run list --workflow=release-draft-create.yaml --limit=1
|
gh run list --workflow=release.yaml --limit=1
|
||||||
```
|
```
|
||||||
4. **For Minor/Major Version Releases**: The release-branch-create workflow will automatically:
|
4. **For Minor/Major Version Releases**: The create-release-candidate-branch workflow will automatically:
|
||||||
- Create a `core/x.yy` branch for the PREVIOUS minor version
|
- Create a `core/x.yy` branch for the PREVIOUS minor version
|
||||||
- Apply branch protection rules
|
- Apply branch protection rules
|
||||||
- Document the feature freeze policy
|
- Document the feature freeze policy
|
||||||
```bash
|
```bash
|
||||||
# Monitor branch creation (for minor/major releases)
|
# Monitor branch creation (for minor/major releases)
|
||||||
gh run list --workflow=release-branch-create.yaml --limit=1
|
gh run list --workflow=create-release-candidate-branch.yaml --limit=1
|
||||||
```
|
```
|
||||||
4. If workflow didn't trigger due to [skip ci]:
|
4. If workflow didn't trigger due to [skip ci]:
|
||||||
```bash
|
```bash
|
||||||
@@ -477,7 +478,7 @@ echo "Workflow triggered. Waiting for PR creation..."
|
|||||||
```
|
```
|
||||||
5. If workflow triggered, monitor execution:
|
5. If workflow triggered, monitor execution:
|
||||||
```bash
|
```bash
|
||||||
WORKFLOW_RUN_ID=$(gh run list --workflow=release-draft-create.yaml --limit=1 --json databaseId --jq '.[0].databaseId')
|
WORKFLOW_RUN_ID=$(gh run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[0].databaseId')
|
||||||
gh run watch ${WORKFLOW_RUN_ID}
|
gh run watch ${WORKFLOW_RUN_ID}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ For each commit:
|
|||||||
3. Merge the PR: `gh pr merge --merge`
|
3. Merge the PR: `gh pr merge --merge`
|
||||||
4. Monitor release workflow:
|
4. Monitor release workflow:
|
||||||
```bash
|
```bash
|
||||||
gh run list --workflow=release-draft-create.yaml --limit=1
|
gh run list --workflow=release.yaml --limit=1
|
||||||
gh run watch
|
gh run watch
|
||||||
```
|
```
|
||||||
5. Track progress:
|
5. Track progress:
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ echo "Available commands:"
|
|||||||
echo " pnpm dev - Start development server"
|
echo " pnpm dev - Start development server"
|
||||||
echo " pnpm build - Build for production"
|
echo " pnpm build - Build for production"
|
||||||
echo " pnpm test:unit - Run unit tests"
|
echo " pnpm test:unit - Run unit tests"
|
||||||
|
echo " pnpm test:component - Run component tests"
|
||||||
echo " pnpm typecheck - Run TypeScript checks"
|
echo " pnpm typecheck - Run TypeScript checks"
|
||||||
echo " pnpm lint - Run ESLint"
|
echo " pnpm lint - Run ESLint"
|
||||||
echo " pnpm format - Format code with Prettier"
|
echo " pnpm format - Format code with Prettier"
|
||||||
|
|||||||
17
.env_example
17
.env_example
@@ -5,10 +5,6 @@ PLAYWRIGHT_TEST_URL=http://localhost:5173
|
|||||||
|
|
||||||
# Proxy target of the local development server
|
# Proxy target of the local development server
|
||||||
# Note: localhost:8188 does not work.
|
# Note: localhost:8188 does not work.
|
||||||
# Cloud auto-detection: Setting this to any *.comfy.org URL automatically enables
|
|
||||||
# cloud mode (DISTRIBUTION=cloud) without needing to set DISTRIBUTION separately.
|
|
||||||
# Examples: https://testcloud.comfy.org/, https://stagingcloud.comfy.org/,
|
|
||||||
# https://pr-123.testenvs.comfy.org/, https://cloud.comfy.org/
|
|
||||||
DEV_SERVER_COMFYUI_URL=http://127.0.0.1:8188
|
DEV_SERVER_COMFYUI_URL=http://127.0.0.1:8188
|
||||||
|
|
||||||
# Allow dev server access from remote IP addresses.
|
# Allow dev server access from remote IP addresses.
|
||||||
@@ -23,10 +19,10 @@ TEST_COMFYUI_DIR=/home/ComfyUI
|
|||||||
# Whether to enable minification of the frontend code.
|
# Whether to enable minification of the frontend code.
|
||||||
ENABLE_MINIFY=true
|
ENABLE_MINIFY=true
|
||||||
|
|
||||||
# Whether to disable proxying the `/templates` route. If true, allows you to
|
# Whether to disable proxying the `/templates` route. If true, allows you to
|
||||||
# serve templates from the ComfyUI_frontend/public/templates folder (for
|
# serve templates from the ComfyUI_frontend/public/templates folder (for
|
||||||
# locally testing changes to templates). When false or nonexistent, the
|
# locally testing changes to templates). When false or nonexistent, the
|
||||||
# templates are served via the normal method from the server's python site
|
# templates are served via the normal method from the server's python site
|
||||||
# packages.
|
# packages.
|
||||||
DISABLE_TEMPLATES_PROXY=false
|
DISABLE_TEMPLATES_PROXY=false
|
||||||
|
|
||||||
@@ -37,8 +33,3 @@ DISABLE_VUE_PLUGINS=false
|
|||||||
# Algolia credentials required for developing with the new custom node manager.
|
# Algolia credentials required for developing with the new custom node manager.
|
||||||
ALGOLIA_APP_ID=4E0RO38HS8
|
ALGOLIA_APP_ID=4E0RO38HS8
|
||||||
ALGOLIA_API_KEY=684d998c36b67a9a9fce8fc2d8860579
|
ALGOLIA_API_KEY=684d998c36b67a9a9fce8fc2d8860579
|
||||||
|
|
||||||
# Sentry ENV vars replace with real ones for debugging
|
|
||||||
# SENTRY_AUTH_TOKEN=private-token # get from sentry
|
|
||||||
# SENTRY_ORG=comfy-org
|
|
||||||
# SENTRY_PROJECT=cloud-frontend-staging
|
|
||||||
|
|||||||
@@ -25,6 +25,3 @@ e3bb29ceb8174b8bbca9e48ec7d42cd540f40efa
|
|||||||
|
|
||||||
# [refactor] Improve updates/notifications domain organization (#5590)
|
# [refactor] Improve updates/notifications domain organization (#5590)
|
||||||
27ab355f9c73415dc39f4d3f512b02308f847801
|
27ab355f9c73415dc39f4d3f512b02308f847801
|
||||||
|
|
||||||
# Migrate Tailwind styles to design-system package
|
|
||||||
9f19d8fb4bd22518879343b49c05634dca777df0
|
|
||||||
|
|||||||
5
.gitattributes
vendored
5
.gitattributes
vendored
@@ -7,11 +7,10 @@
|
|||||||
*.json text eol=lf
|
*.json text eol=lf
|
||||||
*.mjs text eol=lf
|
*.mjs text eol=lf
|
||||||
*.mts text eol=lf
|
*.mts text eol=lf
|
||||||
*.snap text eol=lf
|
|
||||||
*.ts text eol=lf
|
*.ts text eol=lf
|
||||||
*.vue text eol=lf
|
*.vue text eol=lf
|
||||||
*.yaml text eol=lf
|
*.yaml text eol=lf
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
packages/registry-types/src/comfyRegistryTypes.ts linguist-generated=true
|
src/types/comfyRegistryTypes.ts linguist-generated=true
|
||||||
src/workbench/extensions/manager/types/generatedManagerTypes.ts linguist-generated=true
|
src/types/generatedManagerTypes.ts linguist-generated=true
|
||||||
|
|||||||
6
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
6
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
@@ -36,9 +36,9 @@ body:
|
|||||||
3. Click Queue Prompt
|
3. Click Queue Prompt
|
||||||
4. See error
|
4. See error
|
||||||
value: |
|
value: |
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
3.
|
3.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
119
.github/actions/comment-release-links/action.yaml
vendored
119
.github/actions/comment-release-links/action.yaml
vendored
@@ -1,119 +0,0 @@
|
|||||||
name: Post Release Summary Comment
|
|
||||||
description: Post or update a PR comment summarizing release links with diff, derived versions, and optional extras.
|
|
||||||
author: ComfyUI Frontend Team
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
issue-number:
|
|
||||||
description: Optional PR number override (defaults to the current pull request)
|
|
||||||
default: ''
|
|
||||||
version_file:
|
|
||||||
description: Path to the JSON file containing the current version (relative to repo root)
|
|
||||||
required: true
|
|
||||||
|
|
||||||
outputs:
|
|
||||||
prev_version:
|
|
||||||
description: Previous version derived from the parent commit
|
|
||||||
value: ${{ steps.build.outputs.prev_version }}
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: composite
|
|
||||||
steps:
|
|
||||||
- name: Build comment body
|
|
||||||
id: build
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
VERSION_FILE="${{ inputs.version_file }}"
|
|
||||||
REPO="${{ github.repository }}"
|
|
||||||
|
|
||||||
if [[ -z "$VERSION_FILE" ]]; then
|
|
||||||
echo "::error::version_file input is required" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PREV_JSON=$(git show HEAD^1:"$VERSION_FILE" 2>/dev/null || true)
|
|
||||||
if [[ -z "$PREV_JSON" ]]; then
|
|
||||||
echo "::error::Unable to read $VERSION_FILE from parent commit" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
PREV_VERSION=$(printf '%s' "$PREV_JSON" | node -pe "const data = JSON.parse(require('fs').readFileSync(0, 'utf8')); if (!data.version) { process.exit(1); } data.version")
|
|
||||||
if [[ -z "$PREV_VERSION" ]]; then
|
|
||||||
echo "::error::Unable to determine previous version from $VERSION_FILE" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
NEW_VERSION=$(node -pe "const fs=require('fs');const data=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));if(!data.version){process.exit(1);}data.version" "$VERSION_FILE")
|
|
||||||
if [[ -z "$NEW_VERSION" ]]; then
|
|
||||||
echo "::error::Unable to determine current version from $VERSION_FILE" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
MARKER='release-summary'
|
|
||||||
MESSAGE='Publish jobs finished successfully:'
|
|
||||||
LINKS_VALUE=''
|
|
||||||
|
|
||||||
case "$VERSION_FILE" in
|
|
||||||
package.json)
|
|
||||||
LINKS_VALUE=$(printf '%s\n%s' \
|
|
||||||
'PyPI|https://pypi.org/project/comfyui-frontend-package/{{version}}/' \
|
|
||||||
'npm types|https://www.npmjs.com/package/@comfyorg/comfyui-frontend-types/v/{{version}}')
|
|
||||||
;;
|
|
||||||
apps/desktop-ui/package.json)
|
|
||||||
MARKER='desktop-release-summary'
|
|
||||||
LINKS_VALUE='npm desktop UI|https://www.npmjs.com/package/@comfyorg/desktop-ui/v/{{version}}'
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
DIFF_PREFIX='v'
|
|
||||||
DIFF_LABEL='Diff'
|
|
||||||
DIFF_URL="https://github.com/${REPO}/compare/${DIFF_PREFIX}${PREV_VERSION}...${DIFF_PREFIX}${NEW_VERSION}"
|
|
||||||
COMMENT_FILE=$(mktemp)
|
|
||||||
|
|
||||||
{
|
|
||||||
echo "<!--$MARKER:$DIFF_PREFIX$NEW_VERSION-->"
|
|
||||||
echo "$MESSAGE"
|
|
||||||
echo ""
|
|
||||||
echo "- $DIFF_LABEL: [\`$DIFF_PREFIX$PREV_VERSION...$DIFF_PREFIX$NEW_VERSION\`]($DIFF_URL)"
|
|
||||||
|
|
||||||
while IFS= read -r RAW_LINE; do
|
|
||||||
LINE=$(echo "$RAW_LINE" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
||||||
[[ -z "$LINE" ]] && continue
|
|
||||||
if [[ "$LINE" != *"|"* ]]; then
|
|
||||||
echo "::warning::Skipping malformed link entry: $LINE" >&2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
LABEL=${LINE%%|*}
|
|
||||||
URL_TEMPLATE=${LINE#*|}
|
|
||||||
URL=${URL_TEMPLATE//\{\{version\}\}/$NEW_VERSION}
|
|
||||||
URL=${URL//\{\{prev_version\}\}/$PREV_VERSION}
|
|
||||||
echo "- $LABEL: [\`$NEW_VERSION\`]($URL)"
|
|
||||||
done <<< "$LINKS_VALUE"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
} > "$COMMENT_FILE"
|
|
||||||
|
|
||||||
{
|
|
||||||
echo "body<<COMMENT_BODY_END_MARKER"
|
|
||||||
cat "$COMMENT_FILE"
|
|
||||||
echo "COMMENT_BODY_END_MARKER"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
echo "prev_version=$PREV_VERSION" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "marker_search=<!--$MARKER:" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Find existing comment
|
|
||||||
id: find
|
|
||||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad
|
|
||||||
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
|
|
||||||
with:
|
|
||||||
issue-number: ${{ inputs.issue-number || github.event.pull_request.number }}
|
|
||||||
comment-id: ${{ steps.find.outputs.comment-id }}
|
|
||||||
body: ${{ steps.build.outputs.body }}
|
|
||||||
edit-mode: replace
|
|
||||||
55
.github/actions/setup-comfyui-server/action.yml
vendored
55
.github/actions/setup-comfyui-server/action.yml
vendored
@@ -1,55 +0,0 @@
|
|||||||
name: Setup ComfyUI Server
|
|
||||||
description: 'Setup ComfyUI server for continuous integration (with ComfyUI_devtools node installed)'
|
|
||||||
inputs:
|
|
||||||
extra_server_params:
|
|
||||||
description: 'Additional parameters to pass to ComfyUI server'
|
|
||||||
required: false
|
|
||||||
default: ''
|
|
||||||
launch_server:
|
|
||||||
description: 'Whether to launch the server after setup'
|
|
||||||
required: false
|
|
||||||
default: 'false'
|
|
||||||
runs:
|
|
||||||
using: 'composite'
|
|
||||||
steps:
|
|
||||||
# Note: this workflow assume frontend repo is checked out and is built in ../dist
|
|
||||||
|
|
||||||
# Checkout ComfyUI repo, install the dev_tools node and start server
|
|
||||||
- name: Checkout ComfyUI
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
repository: 'comfyanonymous/ComfyUI'
|
|
||||||
path: 'ComfyUI'
|
|
||||||
|
|
||||||
- name: Install ComfyUI_devtools from frontend repo
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools
|
|
||||||
if ! cp -r ./tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/; then
|
|
||||||
echo "::error::Failed to copy ComfyUI_devtools from ./tools/devtools/"
|
|
||||||
echo "::error::This action assumes the ComfyUI_frontend repository is checked out in the current working directory."
|
|
||||||
echo "::error::Please ensure you have run 'actions/checkout@v5' before calling this action."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
|
|
||||||
- name: Install Python requirements
|
|
||||||
shell: bash
|
|
||||||
working-directory: ComfyUI
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
|
||||||
pip install -r requirements.txt
|
|
||||||
pip install wait-for-it
|
|
||||||
|
|
||||||
- name: Start ComfyUI server
|
|
||||||
if: ${{ inputs.launch_server == 'true' }}
|
|
||||||
shell: bash
|
|
||||||
working-directory: ComfyUI
|
|
||||||
run: |
|
|
||||||
python main.py --cpu --multi-user --front-end-root ../dist ${{ inputs.extra_server_params }} &
|
|
||||||
wait-for-it --service 127.0.0.1:8188 -t 600
|
|
||||||
45
.github/actions/setup-frontend/action.yml
vendored
45
.github/actions/setup-frontend/action.yml
vendored
@@ -1,45 +0,0 @@
|
|||||||
name: Setup ComfyUI Frontend
|
|
||||||
description: 'Install nodejs/pnpm/dependencies and optionally build ComfyUI_frontend'
|
|
||||||
inputs:
|
|
||||||
include_build_step:
|
|
||||||
description: 'Include the build step to build the frontend. Set to true for workflows that need a built frontend'
|
|
||||||
required: false
|
|
||||||
default: 'false'
|
|
||||||
runs:
|
|
||||||
using: 'composite'
|
|
||||||
steps:
|
|
||||||
# Note: this workflow assume frontend repo is checked out in the root of the workspace
|
|
||||||
|
|
||||||
# Install pnpm, Node.js, build frontend
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 'lts/*'
|
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: './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
|
|
||||||
|
|
||||||
- name: Build ComfyUI_frontend
|
|
||||||
if: ${{ inputs.include_build_step == 'true' }}
|
|
||||||
shell: bash
|
|
||||||
run: pnpm build
|
|
||||||
28
.github/actions/setup-playwright/action.yml
vendored
28
.github/actions/setup-playwright/action.yml
vendored
@@ -1,28 +0,0 @@
|
|||||||
name: Setup Playwright
|
|
||||||
description: Cache and install Playwright browsers with dependencies
|
|
||||||
runs:
|
|
||||||
using: composite
|
|
||||||
steps:
|
|
||||||
- name: Detect Playwright version
|
|
||||||
id: detect-version
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --json | jq --raw-output '.[0].devDependencies["@playwright/test"].version')
|
|
||||||
echo "playwright-version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Cache Playwright Browsers
|
|
||||||
uses: actions/cache@v4
|
|
||||||
id: cache-playwright-browsers
|
|
||||||
with:
|
|
||||||
path: '~/.cache/ms-playwright'
|
|
||||||
key: ${{ runner.os }}-playwright-browsers-${{ steps.detect-version.outputs.playwright-version }}
|
|
||||||
|
|
||||||
- name: Install Playwright Browsers
|
|
||||||
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
|
|
||||||
shell: bash
|
|
||||||
run: pnpm exec playwright install chromium --with-deps
|
|
||||||
|
|
||||||
- name: Install Playwright Browsers (operating system dependencies)
|
|
||||||
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
|
|
||||||
shell: bash
|
|
||||||
run: pnpm exec playwright install-deps
|
|
||||||
21
.github/workflows/README.md
vendored
21
.github/workflows/README.md
vendored
@@ -1,21 +0,0 @@
|
|||||||
# GitHub Workflows
|
|
||||||
|
|
||||||
## Naming Convention
|
|
||||||
|
|
||||||
Workflow files follow a consistent naming pattern: `<prefix>-<descriptive-name>.yaml`
|
|
||||||
|
|
||||||
### Category Prefixes
|
|
||||||
|
|
||||||
| Prefix | Purpose | Example |
|
|
||||||
| ---------- | ----------------------------------- | ------------------------------------ |
|
|
||||||
| `ci-` | Testing, linting, validation | `ci-tests-e2e.yaml` |
|
|
||||||
| `release-` | Version management, publishing | `release-version-bump.yaml` |
|
|
||||||
| `pr-` | PR automation (triggered by labels) | `pr-claude-review.yaml` |
|
|
||||||
| `api-` | External Api type generation | `api-update-registry-api-types.yaml` |
|
|
||||||
| `i18n-` | Internationalization updates | `i18n-update-core.yaml` |
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Each workflow file contains comments explaining its purpose, triggers, and behavior. For specific details about what each workflow does, refer to the comments at the top of each `.yaml` file.
|
|
||||||
|
|
||||||
For GitHub Actions documentation, see [Events that trigger workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows).
|
|
||||||
185
.github/workflows/backport.yaml
vendored
Normal file
185
.github/workflows/backport.yaml
vendored
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
name: Auto Backport
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [closed, labeled]
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
backport:
|
||||||
|
if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'needs-backport')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure git
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Check if backports already exist
|
||||||
|
id: check-existing
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
run: |
|
||||||
|
# Check for existing backport PRs for this PR number
|
||||||
|
EXISTING_BACKPORTS=$(gh pr list --state all --search "backport-${PR_NUMBER}-to" --json title,headRefName,baseRefName | jq -r '.[].headRefName')
|
||||||
|
|
||||||
|
if [ -z "$EXISTING_BACKPORTS" ]; then
|
||||||
|
echo "skip=false" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found existing backport PRs:"
|
||||||
|
echo "$EXISTING_BACKPORTS"
|
||||||
|
echo "skip=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "::warning::Backport PRs already exist for PR #${PR_NUMBER}, skipping to avoid duplicates"
|
||||||
|
|
||||||
|
- name: Extract version labels
|
||||||
|
if: steps.check-existing.outputs.skip != 'true'
|
||||||
|
id: versions
|
||||||
|
run: |
|
||||||
|
# Extract version labels (e.g., "1.24", "1.22")
|
||||||
|
VERSIONS=""
|
||||||
|
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
|
||||||
|
for label in $(echo "$LABELS" | jq -r '.[].name'); do
|
||||||
|
# Match version labels like "1.24" (major.minor only)
|
||||||
|
if [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
# Validate the branch exists before adding to list
|
||||||
|
if git ls-remote --exit-code origin "core/${label}" >/dev/null 2>&1; then
|
||||||
|
VERSIONS="${VERSIONS}${label} "
|
||||||
|
else
|
||||||
|
echo "::warning::Label '${label}' found but branch 'core/${label}' does not exist"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$VERSIONS" ]; then
|
||||||
|
echo "::error::No version labels found (e.g., 1.24, 1.22)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "versions=${VERSIONS}" >> $GITHUB_OUTPUT
|
||||||
|
echo "Found version labels: ${VERSIONS}"
|
||||||
|
|
||||||
|
- name: Backport commits
|
||||||
|
if: steps.check-existing.outputs.skip != 'true'
|
||||||
|
id: backport
|
||||||
|
env:
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
MERGE_COMMIT: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
|
run: |
|
||||||
|
FAILED=""
|
||||||
|
SUCCESS=""
|
||||||
|
|
||||||
|
for version in ${{ steps.versions.outputs.versions }}; do
|
||||||
|
echo "::group::Backporting to core/${version}"
|
||||||
|
|
||||||
|
TARGET_BRANCH="core/${version}"
|
||||||
|
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${version}"
|
||||||
|
|
||||||
|
# Fetch target branch (fail if doesn't exist)
|
||||||
|
if ! git fetch origin "${TARGET_BRANCH}"; then
|
||||||
|
echo "::error::Target branch ${TARGET_BRANCH} does not exist"
|
||||||
|
FAILED="${FAILED}${version}:branch-missing "
|
||||||
|
echo "::endgroup::"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create backport branch
|
||||||
|
git checkout -b "${BACKPORT_BRANCH}" "origin/${TARGET_BRANCH}"
|
||||||
|
|
||||||
|
# Try cherry-pick
|
||||||
|
if git cherry-pick "${MERGE_COMMIT}"; then
|
||||||
|
git push origin "${BACKPORT_BRANCH}"
|
||||||
|
SUCCESS="${SUCCESS}${version}:${BACKPORT_BRANCH} "
|
||||||
|
echo "Successfully created backport branch: ${BACKPORT_BRANCH}"
|
||||||
|
# Return to main (keep the branch, we need it for PR)
|
||||||
|
git checkout main
|
||||||
|
else
|
||||||
|
# Get conflict info
|
||||||
|
CONFLICTS=$(git diff --name-only --diff-filter=U | tr '\n' ',')
|
||||||
|
git cherry-pick --abort
|
||||||
|
|
||||||
|
echo "::error::Cherry-pick failed due to conflicts"
|
||||||
|
FAILED="${FAILED}${version}:conflicts:${CONFLICTS} "
|
||||||
|
|
||||||
|
# Clean up the failed branch
|
||||||
|
git checkout main
|
||||||
|
git branch -D "${BACKPORT_BRANCH}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "::endgroup::"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "success=${SUCCESS}" >> $GITHUB_OUTPUT
|
||||||
|
echo "failed=${FAILED}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
if [ -n "${FAILED}" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create PR for each successful backport
|
||||||
|
if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||||
|
run: |
|
||||||
|
for backport in ${{ steps.backport.outputs.success }}; do
|
||||||
|
IFS=':' read -r version branch <<< "${backport}"
|
||||||
|
|
||||||
|
if PR_URL=$(gh pr create \
|
||||||
|
--base "core/${version}" \
|
||||||
|
--head "${branch}" \
|
||||||
|
--title "[backport ${version}] ${PR_TITLE}" \
|
||||||
|
--body "Backport of #${PR_NUMBER} to \`core/${version}\`"$'\n\n'"Automatically created by backport workflow." \
|
||||||
|
--label "backport" 2>&1); then
|
||||||
|
|
||||||
|
# Extract PR number from URL
|
||||||
|
PR_NUM=$(echo "${PR_URL}" | grep -o '[0-9]*$')
|
||||||
|
|
||||||
|
if [ -n "${PR_NUM}" ]; then
|
||||||
|
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Successfully backported to #${PR_NUM}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "::error::Failed to create PR for ${version}: ${PR_URL}"
|
||||||
|
# Still try to comment on the original PR about the failure
|
||||||
|
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport branch created but PR creation failed for \`core/${version}\`. Please create the PR manually from branch \`${branch}\`"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Comment on failures
|
||||||
|
if: steps.check-existing.outputs.skip != 'true' && failure() && steps.backport.outputs.failed
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||||
|
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
||||||
|
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
||||||
|
|
||||||
|
for failure in ${{ steps.backport.outputs.failed }}; do
|
||||||
|
IFS=':' read -r version reason conflicts <<< "${failure}"
|
||||||
|
|
||||||
|
if [ "${reason}" = "branch-missing" ]; then
|
||||||
|
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport failed: Branch \`core/${version}\` does not exist"
|
||||||
|
|
||||||
|
elif [ "${reason}" = "conflicts" ]; then
|
||||||
|
# Convert comma-separated conflicts back to newlines for display
|
||||||
|
CONFLICTS_LIST=$(echo "${conflicts}" | tr ',' '\n' | sed 's/^/- /')
|
||||||
|
|
||||||
|
COMMENT_BODY="@${PR_AUTHOR} Backport to \`core/${version}\` failed: Merge conflicts detected."$'\n\n'"Please manually cherry-pick commit \`${MERGE_COMMIT}\` to the \`core/${version}\` branch."$'\n\n'"<details><summary>Conflicting files</summary>"$'\n\n'"${CONFLICTS_LIST}"$'\n\n'"</details>"
|
||||||
|
gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
57
.github/workflows/chromatic.yaml
vendored
Normal file
57
.github/workflows/chromatic.yaml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: 'Chromatic'
|
||||||
|
|
||||||
|
# - [Automate Chromatic with GitHub Actions • Chromatic docs]( https://www.chromatic.com/docs/github-actions/ )
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: # Allow manual triggering
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
chromatic-deployment:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Only run for PRs from version-bump-* branches or manual triggers
|
||||||
|
if: github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'version-bump-')
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Required for Chromatic baseline
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.cache
|
||||||
|
storybook-static
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
key: storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', '*.config.*', '.storybook/**/*') }}
|
||||||
|
restore-keys: |
|
||||||
|
storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
|
||||||
|
storybook-cache-${{ runner.os }}-
|
||||||
|
storybook-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build Storybook and run Chromatic
|
||||||
|
id: chromatic
|
||||||
|
uses: chromaui/action@latest
|
||||||
|
with:
|
||||||
|
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
|
buildScriptName: build-storybook
|
||||||
|
autoAcceptChanges: 'main' # Auto-accept changes on main branch
|
||||||
|
exitOnceUploaded: true # Don't wait for UI tests to complete
|
||||||
|
|
||||||
18
.github/workflows/ci-json-validation.yaml
vendored
18
.github/workflows/ci-json-validation.yaml
vendored
@@ -1,18 +0,0 @@
|
|||||||
name: "CI: JSON Validation"
|
|
||||||
description: "Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '**/*.json'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
json-lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- name: Validate JSON syntax
|
|
||||||
run: ./scripts/cicd/check-json.sh
|
|
||||||
27
.github/workflows/ci-python-validation.yaml
vendored
27
.github/workflows/ci-python-validation.yaml
vendored
@@ -1,27 +0,0 @@
|
|||||||
name: "CI: Python Validation"
|
|
||||||
description: "Validates Python code in tools/devtools directory"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'tools/devtools/**'
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- 'tools/devtools/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
syntax:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.11'
|
|
||||||
|
|
||||||
- name: Validate Python syntax
|
|
||||||
run: python3 -m compileall -q tools/devtools
|
|
||||||
52
.github/workflows/ci-size-data.yaml
vendored
52
.github/workflows/ci-size-data.yaml
vendored
@@ -1,52 +0,0 @@
|
|||||||
name: "CI: Size Data"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
collect:
|
|
||||||
if: github.repository == 'Comfy-Org/ComfyUI_frontend'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- 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: Build project
|
|
||||||
run: pnpm build
|
|
||||||
|
|
||||||
- name: Collect size data
|
|
||||||
run: node scripts/size-collect.js
|
|
||||||
|
|
||||||
- name: Save PR number & base branch
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
echo ${{ github.event.number }} > ./temp/size/number.txt
|
|
||||||
echo ${{ github.base_ref }} > ./temp/size/base.txt
|
|
||||||
|
|
||||||
- name: Upload size data
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: size-data
|
|
||||||
path: temp/size
|
|
||||||
236
.github/workflows/ci-tests-e2e.yaml
vendored
236
.github/workflows/ci-tests-e2e.yaml
vendored
@@ -1,236 +0,0 @@
|
|||||||
name: "CI: Tests E2E"
|
|
||||||
description: "End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, master, core/*, desktop/*]
|
|
||||||
pull_request:
|
|
||||||
branches-ignore:
|
|
||||||
[wip/*, draft/*, temp/*, vue-nodes-migration, sno-playwright-*]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
setup:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
# Setup Test Environment, build frontend but do not start server yet
|
|
||||||
- name: Setup ComfyUI server
|
|
||||||
uses: ./.github/actions/setup-comfyui-server
|
|
||||||
- name: Setup frontend
|
|
||||||
uses: ./.github/actions/setup-frontend
|
|
||||||
with:
|
|
||||||
include_build_step: true
|
|
||||||
- name: Setup Playwright
|
|
||||||
uses: ./.github/actions/setup-playwright # Setup Playwright and cache browsers
|
|
||||||
|
|
||||||
# Save the entire workspace as cache for later test jobs to restore
|
|
||||||
- name: Generate cache key
|
|
||||||
id: cache-key
|
|
||||||
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
|
|
||||||
- name: Save cache
|
|
||||||
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
|
|
||||||
with:
|
|
||||||
path: .
|
|
||||||
key: comfyui-setup-${{ steps.cache-key.outputs.key }}
|
|
||||||
|
|
||||||
# Sharded chromium tests
|
|
||||||
playwright-tests-chromium-sharded:
|
|
||||||
needs: setup
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
|
|
||||||
shardTotal: [8]
|
|
||||||
steps:
|
|
||||||
# download built frontend repo from setup job
|
|
||||||
- name: Wait for cache propagation
|
|
||||||
run: sleep 10
|
|
||||||
- name: Restore cached setup
|
|
||||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
|
||||||
with:
|
|
||||||
fail-on-cache-miss: true
|
|
||||||
path: .
|
|
||||||
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
|
|
||||||
|
|
||||||
# Setup Test Environment for this runner, start server, use cached built frontend ./dist from 'setup' job
|
|
||||||
- name: Setup ComfyUI server
|
|
||||||
uses: ./.github/actions/setup-comfyui-server
|
|
||||||
with:
|
|
||||||
launch_server: true
|
|
||||||
- name: Setup nodejs, pnpm, reuse built frontend
|
|
||||||
uses: ./.github/actions/setup-frontend
|
|
||||||
- name: Setup Playwright
|
|
||||||
uses: ./.github/actions/setup-playwright
|
|
||||||
|
|
||||||
# Run sharded tests and upload sharded reports
|
|
||||||
- name: Run Playwright tests (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
|
|
||||||
id: playwright
|
|
||||||
run: pnpm exec playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob
|
|
||||||
env:
|
|
||||||
PLAYWRIGHT_BLOB_OUTPUT_DIR: ./blob-report
|
|
||||||
|
|
||||||
- name: Upload blob report
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
with:
|
|
||||||
name: blob-report-chromium-${{ matrix.shardIndex }}
|
|
||||||
path: blob-report/
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
playwright-tests:
|
|
||||||
# Ideally, each shard runs test in 6 minutes, but allow up to 15 minutes
|
|
||||||
timeout-minutes: 15
|
|
||||||
needs: setup
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
browser: [chromium-2x, chromium-0.5x, mobile-chrome]
|
|
||||||
steps:
|
|
||||||
# download built frontend repo from setup job
|
|
||||||
- name: Wait for cache propagation
|
|
||||||
run: sleep 10
|
|
||||||
- name: Restore cached setup
|
|
||||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
|
||||||
with:
|
|
||||||
fail-on-cache-miss: true
|
|
||||||
path: .
|
|
||||||
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
|
|
||||||
|
|
||||||
# Setup Test Environment for this runner, start server, use cached built frontend ./dist from 'setup' job
|
|
||||||
- name: Setup ComfyUI server
|
|
||||||
uses: ./.github/actions/setup-comfyui-server
|
|
||||||
with:
|
|
||||||
launch_server: true
|
|
||||||
- name: Setup nodejs, pnpm, reuse built frontend
|
|
||||||
uses: ./.github/actions/setup-frontend
|
|
||||||
- name: Setup Playwright
|
|
||||||
uses: ./.github/actions/setup-playwright
|
|
||||||
|
|
||||||
# Run tests and upload reports
|
|
||||||
- name: Run Playwright tests (${{ matrix.browser }})
|
|
||||||
id: playwright
|
|
||||||
run: |
|
|
||||||
# Run tests with both HTML and JSON reporters
|
|
||||||
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
|
||||||
pnpm exec playwright test --project=${{ matrix.browser }} \
|
|
||||||
--reporter=list \
|
|
||||||
--reporter=html \
|
|
||||||
--reporter=json
|
|
||||||
|
|
||||||
- name: Upload Playwright report
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
name: playwright-report-${{ matrix.browser }}
|
|
||||||
path: ./playwright-report/
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
# Merge sharded test reports
|
|
||||||
merge-reports:
|
|
||||||
needs: [playwright-tests-chromium-sharded]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ !cancelled() }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
# Setup Test Environment, we only need playwright to merge reports
|
|
||||||
- name: Setup frontend
|
|
||||||
uses: ./.github/actions/setup-frontend
|
|
||||||
- name: Setup Playwright
|
|
||||||
uses: ./.github/actions/setup-playwright
|
|
||||||
|
|
||||||
- name: Download blob reports
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
path: ./all-blob-reports
|
|
||||||
pattern: blob-report-chromium-*
|
|
||||||
merge-multiple: true
|
|
||||||
|
|
||||||
- name: Merge into HTML Report
|
|
||||||
run: |
|
|
||||||
# Generate HTML report
|
|
||||||
pnpm exec playwright merge-reports --reporter=html ./all-blob-reports
|
|
||||||
# Generate JSON report separately with explicit output path
|
|
||||||
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
|
||||||
pnpm exec playwright merge-reports --reporter=json ./all-blob-reports
|
|
||||||
|
|
||||||
- name: Upload HTML report
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: playwright-report-chromium
|
|
||||||
path: ./playwright-report/
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
#### BEGIN Deployment and commenting (non-forked PRs only)
|
|
||||||
# when using pull_request event, we have permission to comment directly
|
|
||||||
# if its a forked repo, we need to use workflow_run event in a separate workflow (pr-playwright-deploy.yaml)
|
|
||||||
|
|
||||||
# Post starting comment for non-forked PRs
|
|
||||||
comment-on-pr-start:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Get start time
|
|
||||||
id: start-time
|
|
||||||
run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Post starting comment
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
|
|
||||||
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
|
||||||
"${{ github.event.pull_request.number }}" \
|
|
||||||
"${{ github.head_ref }}" \
|
|
||||||
"starting" \
|
|
||||||
"${{ steps.start-time.outputs.time }}"
|
|
||||||
|
|
||||||
# Deploy and comment for non-forked PRs only
|
|
||||||
deploy-and-comment:
|
|
||||||
needs: [playwright-tests, merge-reports]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Download all playwright reports
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
pattern: playwright-report-*
|
|
||||||
path: reports
|
|
||||||
|
|
||||||
- name: Deploy reports and comment on PR
|
|
||||||
env:
|
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
bash ./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
|
||||||
"${{ github.event.pull_request.number }}" \
|
|
||||||
"${{ github.head_ref }}" \
|
|
||||||
"completed"
|
|
||||||
#### END Deployment and commenting (non-forked PRs only)
|
|
||||||
91
.github/workflows/ci-tests-storybook-forks.yaml
vendored
91
.github/workflows/ci-tests-storybook-forks.yaml
vendored
@@ -1,91 +0,0 @@
|
|||||||
name: "CI: Tests Storybook (Deploy for Forks)"
|
|
||||||
description: "Deploys Storybook previews from forked PRs (forks can't access deployment secrets)"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ["CI: Tests Storybook"]
|
|
||||||
types: [requested, completed]
|
|
||||||
|
|
||||||
env:
|
|
||||||
DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy-and-comment-forked-pr:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: |
|
|
||||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
|
||||||
github.event.workflow_run.event == 'pull_request' &&
|
|
||||||
github.event.workflow_run.head_repository != null &&
|
|
||||||
github.event.workflow_run.repository != null &&
|
|
||||||
github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
actions: read
|
|
||||||
steps:
|
|
||||||
- name: Log workflow trigger info
|
|
||||||
run: |
|
|
||||||
echo "Repository: ${{ github.repository }}"
|
|
||||||
echo "Event: ${{ github.event.workflow_run.event }}"
|
|
||||||
echo "Head repo: ${{ github.event.workflow_run.head_repository.full_name || 'null' }}"
|
|
||||||
echo "Base repo: ${{ github.event.workflow_run.repository.full_name || 'null' }}"
|
|
||||||
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Get PR Number
|
|
||||||
id: pr
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const { data: prs } = await github.rest.pulls.list({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
state: 'open',
|
|
||||||
});
|
|
||||||
|
|
||||||
const pr = prs.find(p => p.head.sha === context.payload.workflow_run.head_sha);
|
|
||||||
|
|
||||||
if (!pr) {
|
|
||||||
console.log('No PR found for SHA:', context.payload.workflow_run.head_sha);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Found PR #${pr.number} from fork: ${context.payload.workflow_run.head_repository.full_name}`);
|
|
||||||
return pr.number;
|
|
||||||
|
|
||||||
- name: Handle Storybook Start
|
|
||||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'requested'
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh
|
|
||||||
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
|
||||||
"${{ steps.pr.outputs.result }}" \
|
|
||||||
"${{ github.event.workflow_run.head_branch }}" \
|
|
||||||
"starting" \
|
|
||||||
"$(date -u '${{ env.DATE_FORMAT }}')"
|
|
||||||
|
|
||||||
- name: Download and Deploy Storybook
|
|
||||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success'
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
|
||||||
name: storybook-static
|
|
||||||
path: storybook-static
|
|
||||||
|
|
||||||
- name: Handle Storybook Completion
|
|
||||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
|
||||||
env:
|
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
WORKFLOW_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
|
|
||||||
WORKFLOW_URL: ${{ github.event.workflow_run.html_url }}
|
|
||||||
run: |
|
|
||||||
chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh
|
|
||||||
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
|
||||||
"${{ steps.pr.outputs.result }}" \
|
|
||||||
"${{ github.event.workflow_run.head_branch }}" \
|
|
||||||
"completed"
|
|
||||||
204
.github/workflows/ci-tests-storybook.yaml
vendored
204
.github/workflows/ci-tests-storybook.yaml
vendored
@@ -1,204 +0,0 @@
|
|||||||
name: "CI: Tests Storybook"
|
|
||||||
description: "Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch: # Allow manual triggering
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# Post starting comment for non-forked PRs
|
|
||||||
comment-on-pr-start:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Post starting comment
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh
|
|
||||||
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
|
||||||
"${{ github.event.pull_request.number }}" \
|
|
||||||
"${{ github.head_ref }}" \
|
|
||||||
"starting" \
|
|
||||||
"$(date -u '+%m/%d/%Y, %I:%M:%S %p')"
|
|
||||||
|
|
||||||
# Build Storybook for all PRs (free Cloudflare deployment)
|
|
||||||
storybook-build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
outputs:
|
|
||||||
conclusion: ${{ steps.job-status.outputs.conclusion }}
|
|
||||||
workflow-url: ${{ steps.workflow-url.outputs.url }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
cache: 'pnpm'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build Storybook
|
|
||||||
run: pnpm build-storybook
|
|
||||||
|
|
||||||
- name: Set job status
|
|
||||||
id: job-status
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "conclusion=${{ job.status }}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Get workflow URL
|
|
||||||
id: workflow-url
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Upload Storybook build
|
|
||||||
if: success() && github.event.pull_request.head.repo.fork == false
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: storybook-static
|
|
||||||
path: storybook-static/
|
|
||||||
retention-days: 7
|
|
||||||
|
|
||||||
# Chromatic deployment only for version-bump-* branches or manual triggers
|
|
||||||
chromatic-deployment:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'version-bump-'))
|
|
||||||
outputs:
|
|
||||||
conclusion: ${{ steps.job-status.outputs.conclusion }}
|
|
||||||
workflow-url: ${{ steps.workflow-url.outputs.url }}
|
|
||||||
chromatic-build-url: ${{ steps.chromatic.outputs.buildUrl }}
|
|
||||||
chromatic-storybook-url: ${{ steps.chromatic.outputs.storybookUrl }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # Required for Chromatic baseline
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
cache: 'pnpm'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build Storybook and run Chromatic
|
|
||||||
id: chromatic
|
|
||||||
uses: chromaui/action@latest
|
|
||||||
with:
|
|
||||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
|
||||||
buildScriptName: build-storybook
|
|
||||||
autoAcceptChanges: 'main' # Auto-accept changes on main branch
|
|
||||||
exitOnceUploaded: true # Don't wait for UI tests to complete
|
|
||||||
onlyChanged: true # Only capture changed stories
|
|
||||||
|
|
||||||
- name: Set job status
|
|
||||||
id: job-status
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "conclusion=${{ job.status }}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Get workflow URL
|
|
||||||
id: workflow-url
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Deploy and comment for non-forked PRs only
|
|
||||||
deploy-and-comment:
|
|
||||||
needs: [storybook-build]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false && always()
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Download Storybook build
|
|
||||||
if: needs.storybook-build.outputs.conclusion == 'success'
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: storybook-static
|
|
||||||
path: storybook-static
|
|
||||||
|
|
||||||
- name: Make deployment script executable
|
|
||||||
run: chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh
|
|
||||||
|
|
||||||
- name: Deploy Storybook and comment on PR
|
|
||||||
env:
|
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
WORKFLOW_CONCLUSION: ${{ needs.storybook-build.outputs.conclusion }}
|
|
||||||
WORKFLOW_URL: ${{ needs.storybook-build.outputs.workflow-url }}
|
|
||||||
run: |
|
|
||||||
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
|
||||||
"${{ github.event.pull_request.number }}" \
|
|
||||||
"${{ github.head_ref }}" \
|
|
||||||
"completed"
|
|
||||||
|
|
||||||
# Update comment with Chromatic URLs for version-bump branches
|
|
||||||
update-comment-with-chromatic:
|
|
||||||
needs: [chromatic-deployment, deploy-and-comment]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false && startsWith(github.head_ref, 'version-bump-') && needs.chromatic-deployment.outputs.chromatic-build-url != ''
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Update comment with Chromatic URLs
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const buildUrl = '${{ needs.chromatic-deployment.outputs.chromatic-build-url }}';
|
|
||||||
const storybookUrl = '${{ needs.chromatic-deployment.outputs.chromatic-storybook-url }}';
|
|
||||||
|
|
||||||
// Find the existing Storybook comment
|
|
||||||
const { data: comments } = await github.rest.issues.listComments({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: ${{ github.event.pull_request.number }}
|
|
||||||
});
|
|
||||||
|
|
||||||
const storybookComment = comments.find(comment =>
|
|
||||||
comment.body.includes('<!-- STORYBOOK_BUILD_STATUS -->')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (storybookComment && buildUrl && storybookUrl) {
|
|
||||||
// Append Chromatic info to existing comment
|
|
||||||
const updatedBody = storybookComment.body.replace(
|
|
||||||
/---\n(.*)$/s,
|
|
||||||
`---\n### 🎨 Chromatic Visual Tests\n- 📊 [View Chromatic Build](${buildUrl})\n- 📚 [View Chromatic Storybook](${storybookUrl})\n\n$1`
|
|
||||||
);
|
|
||||||
|
|
||||||
await github.rest.issues.updateComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
comment_id: storybookComment.id,
|
|
||||||
body: updatedBody
|
|
||||||
});
|
|
||||||
}
|
|
||||||
36
.github/workflows/ci-tests-unit.yaml
vendored
36
.github/workflows/ci-tests-unit.yaml
vendored
@@ -1,36 +0,0 @@
|
|||||||
name: "CI: Tests Unit"
|
|
||||||
description: "Unit and component testing with Vitest"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, master, dev*, core/*, desktop/*]
|
|
||||||
pull_request:
|
|
||||||
branches-ignore: [wip/*, draft/*, temp/*]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- 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: Run Vitest tests
|
|
||||||
run: pnpm test:unit
|
|
||||||
33
.github/workflows/ci-yaml-validation.yaml
vendored
33
.github/workflows/ci-yaml-validation.yaml
vendored
@@ -1,33 +0,0 @@
|
|||||||
name: "CI: YAML Validation"
|
|
||||||
description: "Validates YAML syntax and style using yamllint with relaxed rules"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- '**/*.yml'
|
|
||||||
- '**/*.yaml'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '**/*.yml'
|
|
||||||
- '**/*.yaml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
yaml-lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.x'
|
|
||||||
|
|
||||||
- name: Install yamllint
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install yamllint
|
|
||||||
|
|
||||||
- name: Validate YAML syntax and style
|
|
||||||
run: ./scripts/cicd/check-yaml.sh
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
name: "PR: Claude Review"
|
name: Claude PR Review
|
||||||
description: "AI-powered code review triggered by adding the 'claude-review' label to a PR"
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -12,21 +11,48 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
claude-review:
|
wait-for-ci:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event.label.name == 'claude-review'
|
if: github.event.label.name == 'claude-review'
|
||||||
|
outputs:
|
||||||
|
should-proceed: ${{ steps.check-status.outputs.proceed }}
|
||||||
|
steps:
|
||||||
|
- name: Wait for other CI checks
|
||||||
|
uses: lewagon/wait-on-check-action@e106e5c43e8ca1edea6383a39a01c5ca495fd812
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
check-regexp: '^(lint-and-format|test|playwright-tests)'
|
||||||
|
wait-interval: 30
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Check if we should proceed
|
||||||
|
id: check-status
|
||||||
|
run: |
|
||||||
|
# Get all check runs for this commit
|
||||||
|
CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("lint-and-format|test|playwright-tests")) | {name, conclusion}')
|
||||||
|
|
||||||
|
# Check if any required checks failed
|
||||||
|
if echo "$CHECK_RUNS" | grep -q '"conclusion": "failure"'; then
|
||||||
|
echo "Some CI checks failed - skipping Claude review"
|
||||||
|
echo "proceed=false" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "All CI checks passed - proceeding with Claude review"
|
||||||
|
echo "proceed=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
claude-review:
|
||||||
|
needs: wait-for-ci
|
||||||
|
if: needs.wait-for-ci.outputs.should-proceed == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: refs/pull/${{ github.event.pull_request.number }}/head
|
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -48,10 +74,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
label_trigger: "claude-review"
|
label_trigger: "claude-review"
|
||||||
prompt: |
|
prompt: |
|
||||||
Read the file .claude/commands/comprehensive-pr-review.md and follow ALL the instructions exactly.
|
Read the file .claude/commands/comprehensive-pr-review.md and follow ALL the instructions exactly.
|
||||||
|
|
||||||
CRITICAL: You must post individual inline comments using the gh api commands shown in the file.
|
CRITICAL: You must post individual inline comments using the gh api commands shown in the file.
|
||||||
DO NOT create a summary comment.
|
DO NOT create a summary comment.
|
||||||
Each issue must be posted as a separate inline comment on the specific line of code.
|
Each issue must be posted as a separate inline comment on the specific line of code.
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
claude_args: "--max-turns 256 --allowedTools 'Bash(git:*),Bash(gh api:*),Bash(gh pr:*),Bash(gh repo:*),Bash(jq:*),Bash(echo:*),Read,Write,Edit,Glob,Grep,WebFetch'"
|
claude_args: "--max-turns 256 --allowedTools 'Bash(git:*),Bash(gh api:*),Bash(gh pr:*),Bash(gh repo:*),Bash(jq:*),Bash(echo:*),Read,Write,Edit,Glob,Grep,WebFetch'"
|
||||||
@@ -60,10 +86,4 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||||
REPOSITORY: ${{ github.repository }}
|
REPOSITORY: ${{ github.repository }}
|
||||||
|
|
||||||
- name: Remove claude-review label
|
|
||||||
if: always()
|
|
||||||
run: gh pr edit ${{ github.event.pull_request.number }} --remove-label "claude-review"
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
175
.github/workflows/create-release-candidate-branch.yaml
vendored
Normal file
175
.github/workflows/create-release-candidate-branch.yaml
vendored
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
name: Create Release Branch
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'package.json'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-release-branch:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >
|
||||||
|
github.event.pull_request.merged == true &&
|
||||||
|
contains(github.event.pull_request.labels.*.name, 'Release')
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 'lts/*'
|
||||||
|
|
||||||
|
- name: Check version bump type
|
||||||
|
id: check_version
|
||||||
|
run: |
|
||||||
|
# Get current version from main
|
||||||
|
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||||||
|
# Remove 'v' prefix if present (shouldn't be in package.json, but defensive)
|
||||||
|
CURRENT_VERSION=${CURRENT_VERSION#v}
|
||||||
|
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Validate version format
|
||||||
|
if ! [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
||||||
|
echo "ERROR: Invalid version format: $CURRENT_VERSION"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract major and minor versions
|
||||||
|
MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1)
|
||||||
|
MINOR=$(echo $CURRENT_VERSION | cut -d. -f2)
|
||||||
|
PATCH=$(echo $CURRENT_VERSION | cut -d. -f3 | cut -d- -f1)
|
||||||
|
|
||||||
|
echo "major=$MAJOR" >> $GITHUB_OUTPUT
|
||||||
|
echo "minor=$MINOR" >> $GITHUB_OUTPUT
|
||||||
|
echo "patch=$PATCH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Get previous version from the commit before the merge
|
||||||
|
git checkout HEAD^1
|
||||||
|
PREV_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")
|
||||||
|
# Remove 'v' prefix if present
|
||||||
|
PREV_VERSION=${PREV_VERSION#v}
|
||||||
|
|
||||||
|
# Validate previous version format
|
||||||
|
if ! [[ "$PREV_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
||||||
|
echo "WARNING: Invalid previous version format: $PREV_VERSION, using 0.0.0"
|
||||||
|
PREV_VERSION="0.0.0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
PREV_MINOR=$(echo $PREV_VERSION | cut -d. -f2)
|
||||||
|
|
||||||
|
echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "prev_minor=$PREV_MINOR" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Get previous major version for comparison
|
||||||
|
PREV_MAJOR=$(echo $PREV_VERSION | cut -d. -f1)
|
||||||
|
|
||||||
|
# Check if current version is a pre-release
|
||||||
|
if [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+- ]]; then
|
||||||
|
IS_PRERELEASE=true
|
||||||
|
else
|
||||||
|
IS_PRERELEASE=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if this was a minor version bump or major version bump
|
||||||
|
# But skip if it's a pre-release version
|
||||||
|
if [[ "$IS_PRERELEASE" == "true" ]]; then
|
||||||
|
echo "is_minor_bump=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "reason=prerelease version" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "$MAJOR" -gt "$PREV_MAJOR" && "$MINOR" == "0" && "$PATCH" == "0" ]]; then
|
||||||
|
# Major version bump (e.g., 1.99.x → 2.0.0)
|
||||||
|
echo "is_minor_bump=true" >> $GITHUB_OUTPUT
|
||||||
|
BRANCH_NAME="core/${PREV_MAJOR}.${PREV_MINOR}"
|
||||||
|
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "$MAJOR" == "$PREV_MAJOR" && "$MINOR" -gt "$PREV_MINOR" && "$PATCH" == "0" ]]; then
|
||||||
|
# Minor version bump (e.g., 1.23.x → 1.24.0)
|
||||||
|
echo "is_minor_bump=true" >> $GITHUB_OUTPUT
|
||||||
|
BRANCH_NAME="core/${MAJOR}.${PREV_MINOR}"
|
||||||
|
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "is_minor_bump=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Return to main branch
|
||||||
|
git checkout main
|
||||||
|
|
||||||
|
- name: Create release branch
|
||||||
|
if: steps.check_version.outputs.is_minor_bump == 'true'
|
||||||
|
run: |
|
||||||
|
BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}"
|
||||||
|
|
||||||
|
# Check if branch already exists
|
||||||
|
if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then
|
||||||
|
echo "⚠️ Branch $BRANCH_NAME already exists, skipping creation"
|
||||||
|
echo "branch_exists=true" >> $GITHUB_ENV
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "branch_exists=false" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create branch from the commit BEFORE the version bump
|
||||||
|
# This ensures the release branch has the previous minor version
|
||||||
|
git checkout -b "$BRANCH_NAME" HEAD^1
|
||||||
|
|
||||||
|
# Push the new branch
|
||||||
|
git push origin "$BRANCH_NAME"
|
||||||
|
|
||||||
|
echo "✅ Created release branch: $BRANCH_NAME"
|
||||||
|
echo "This branch is now in feature freeze and will only receive:"
|
||||||
|
echo "- Bug fixes"
|
||||||
|
echo "- Critical security patches"
|
||||||
|
echo "- Documentation updates"
|
||||||
|
|
||||||
|
|
||||||
|
- name: Post summary
|
||||||
|
if: steps.check_version.outputs.is_minor_bump == 'true'
|
||||||
|
run: |
|
||||||
|
BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}"
|
||||||
|
PREV_VERSION="${{ steps.check_version.outputs.prev_version }}"
|
||||||
|
CURRENT_VERSION="${{ steps.check_version.outputs.current_version }}"
|
||||||
|
|
||||||
|
if [[ "${{ env.branch_exists }}" == "true" ]]; then
|
||||||
|
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||||
|
## 🌿 Release Branch Already Exists
|
||||||
|
|
||||||
|
The release branch for the previous minor version already exists:
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||||
|
## 🌿 Release Branch Created
|
||||||
|
|
||||||
|
A new release branch has been created for the previous minor version:
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||||
|
|
||||||
|
- **Branch**: \`$BRANCH_NAME\`
|
||||||
|
- **Version**: \`$PREV_VERSION\` (feature frozen)
|
||||||
|
- **Main branch**: \`$CURRENT_VERSION\` (active development)
|
||||||
|
|
||||||
|
### Branch Policy
|
||||||
|
|
||||||
|
The \`$BRANCH_NAME\` branch is now in **feature freeze** and will only accept:
|
||||||
|
- 🐛 Bug fixes
|
||||||
|
- 🔒 Security patches
|
||||||
|
- 📚 Documentation updates
|
||||||
|
|
||||||
|
All new features should continue to be developed against \`main\`.
|
||||||
|
|
||||||
|
### Backporting Changes
|
||||||
|
|
||||||
|
To backport a fix to this release branch:
|
||||||
|
1. Create your fix on \`main\` first
|
||||||
|
2. Cherry-pick to \`$BRANCH_NAME\`
|
||||||
|
3. Create a PR targeting \`$BRANCH_NAME\`
|
||||||
|
4. Use the \`Release\` label on the PR
|
||||||
|
EOF
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Release PyPI Dev
|
name: Create Dev PyPI Package
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
version: ${{ steps.current_version.outputs.version }}
|
version: ${{ steps.current_version.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
@@ -25,6 +25,17 @@ jobs:
|
|||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
key: dev-release-tools-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
dev-release-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Get current version
|
- name: Get current version
|
||||||
id: current_version
|
id: current_version
|
||||||
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||||
@@ -33,7 +44,6 @@ jobs:
|
|||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
||||||
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
||||||
ENABLE_MINIFY: 'true'
|
|
||||||
USE_PROD_CONFIG: 'true'
|
USE_PROD_CONFIG: 'true'
|
||||||
run: |
|
run: |
|
||||||
pnpm install --frozen-lockfile
|
pnpm install --frozen-lockfile
|
||||||
@@ -52,7 +62,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
- name: Download dist artifact
|
- name: Download dist artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
164
.github/workflows/i18n-custom-nodes.yaml
vendored
Normal file
164
.github/workflows/i18n-custom-nodes.yaml
vendored
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
name: Update Locales for given custom node repository
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
owner:
|
||||||
|
description: 'Owner of the repository to update locales for'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
repository:
|
||||||
|
description: 'Repository to update locales for'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
fork_owner:
|
||||||
|
description: 'Owner of the forked repository'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'Comfy-Org'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-locales:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout ComfyUI
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: comfyanonymous/ComfyUI
|
||||||
|
path: ComfyUI
|
||||||
|
ref: master
|
||||||
|
- name: Checkout ComfyUI_frontend
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: Comfy-Org/ComfyUI_frontend
|
||||||
|
path: ComfyUI_frontend
|
||||||
|
- name: Checkout ComfyUI_devtools
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: Comfy-Org/ComfyUI_devtools
|
||||||
|
path: ComfyUI/custom_nodes/ComfyUI_devtools
|
||||||
|
- name: Checkout custom node repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: ${{ inputs.owner }}/${{ inputs.repository }}
|
||||||
|
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 'lts/*'
|
||||||
|
cache: 'pnpm'
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
- name: Install ComfyUI requirements
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install wait-for-it
|
||||||
|
working-directory: ComfyUI
|
||||||
|
- name: Install custom node requirements
|
||||||
|
run: |
|
||||||
|
if [ -f "requirements.txt" ]; then
|
||||||
|
pip install -r requirements.txt
|
||||||
|
fi
|
||||||
|
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||||
|
- name: Build & Install ComfyUI_frontend
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm build
|
||||||
|
rm -rf ../ComfyUI/web/*
|
||||||
|
mv dist/* ../ComfyUI/web/
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Start ComfyUI server
|
||||||
|
run: |
|
||||||
|
python main.py --cpu --multi-user &
|
||||||
|
wait-for-it --service 127.0.0.1:8188 -t 600
|
||||||
|
working-directory: ComfyUI
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: npx playwright install chromium --with-deps
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Start dev server
|
||||||
|
# Run electron dev server as it is a superset of the web dev server
|
||||||
|
# We do want electron specific UIs to be translated.
|
||||||
|
run: pnpm dev:electron &
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Capture base i18n
|
||||||
|
run: npx tsx scripts/diff-i18n capture
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Update en.json
|
||||||
|
run: pnpm collect-i18n
|
||||||
|
env:
|
||||||
|
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Update translations
|
||||||
|
run: pnpm locale
|
||||||
|
env:
|
||||||
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Diff base vs updated i18n
|
||||||
|
run: npx tsx scripts/diff-i18n diff
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Update i18n in custom node repository
|
||||||
|
run: |
|
||||||
|
LOCALE_DIR=ComfyUI/custom_nodes/${{ inputs.repository }}/locales/
|
||||||
|
install -d "$LOCALE_DIR"
|
||||||
|
cp -rf ComfyUI_frontend/temp/diff/* "$LOCALE_DIR"
|
||||||
|
- name: Check and create fork of custom node repository
|
||||||
|
run: |
|
||||||
|
# Try to fork the repository
|
||||||
|
gh repo fork ${{ inputs.owner }}/${{ inputs.repository }} --clone=false || {
|
||||||
|
echo "Fork failed - repository might already be forked"
|
||||||
|
# Exit 0 to prevent the workflow from failing
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enable workflows on the forked repository
|
||||||
|
gh api \
|
||||||
|
--method PUT \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
"/repos/${{ inputs.fork_owner }}/${{ inputs.repository }}/actions/permissions/workflow" \
|
||||||
|
-F can_approve_pull_request_reviews=true \
|
||||||
|
-F default_workflow_permissions="write" \
|
||||||
|
-F enabled=true
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'github-actions'
|
||||||
|
git config --global user.email 'github-actions@github.com'
|
||||||
|
|
||||||
|
# Create and switch to new branch
|
||||||
|
git checkout -b update-locales
|
||||||
|
|
||||||
|
# Stage and commit changes
|
||||||
|
git add -A
|
||||||
|
git commit -m "Update locales"
|
||||||
|
|
||||||
|
- name: Install SSH key For PUSH
|
||||||
|
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4
|
||||||
|
with:
|
||||||
|
# PR private key from action server
|
||||||
|
key: ${{ secrets.PR_SSH_PRIVATE_KEY }}
|
||||||
|
# github public key to confirm it's github server
|
||||||
|
known_hosts: github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
||||||
|
|
||||||
|
- name: Push changes
|
||||||
|
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||||
|
run: |
|
||||||
|
# Force push to create the branch
|
||||||
|
echo "Pushing changes to ${{ inputs.fork_owner }}/${{ inputs.repository }}"
|
||||||
|
git push -f git@github.com:${{ inputs.fork_owner }}/${{ inputs.repository }}.git update-locales
|
||||||
|
|
||||||
|
- name: Create PR
|
||||||
|
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||||
|
run: |
|
||||||
|
# Create PR using gh cli
|
||||||
|
gh pr create --title "Update locales for ${{ inputs.repository }}" --repo ${{ inputs.owner }}/${{ inputs.repository }} --head ${{ inputs.fork_owner }}:update-locales --body "Update locales for ${{ inputs.repository }}"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||||
48
.github/workflows/i18n-node-defs.yaml
vendored
Normal file
48
.github/workflows/i18n-node-defs.yaml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: Update Node Definitions Locales
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
trigger_type:
|
||||||
|
description: 'Type of trigger (manual or automatic)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: 'manual'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-locales:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: npx playwright install chromium --with-deps
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Start dev server
|
||||||
|
# Run electron dev server as it is a superset of the web dev server
|
||||||
|
# We do want electron specific UIs to be translated.
|
||||||
|
run: pnpm dev:electron &
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Update en.json
|
||||||
|
run: pnpm collect-i18n -- scripts/collect-i18n-node-defs.ts
|
||||||
|
env:
|
||||||
|
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Update translations
|
||||||
|
run: pnpm locale
|
||||||
|
env:
|
||||||
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PR_GH_TOKEN }}
|
||||||
|
commit-message: "Update locales for node definitions"
|
||||||
|
title: "Update locales for node definitions"
|
||||||
|
body: |
|
||||||
|
Automated PR to update locales for node definitions
|
||||||
|
|
||||||
|
This PR was created automatically by the frontend update workflow.
|
||||||
|
branch: update-locales-node-defs-${{ github.event.inputs.trigger_type }}-${{ github.run_id }}
|
||||||
|
base: main
|
||||||
|
labels: dependencies
|
||||||
|
path: ComfyUI_frontend
|
||||||
59
.github/workflows/i18n-update-core.yaml
vendored
59
.github/workflows/i18n-update-core.yaml
vendored
@@ -1,59 +0,0 @@
|
|||||||
name: "i18n: Update Core"
|
|
||||||
description: "Generates and updates translations for core ComfyUI components using OpenAI"
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Manual dispatch for urgent translation updates
|
|
||||||
workflow_dispatch:
|
|
||||||
# Only trigger on PRs to main/master - additional branch filtering in job condition
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-locales:
|
|
||||||
# Branch detection: Only run for manual dispatch or version-bump-* branches from main repo
|
|
||||||
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-'))
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
# Setup playwright environment
|
|
||||||
- name: Setup ComfyUI Frontend
|
|
||||||
uses: ./.github/actions/setup-frontend
|
|
||||||
with:
|
|
||||||
include_build_step: true
|
|
||||||
- name: Setup ComfyUI Server
|
|
||||||
uses: ./.github/actions/setup-comfyui-server
|
|
||||||
with:
|
|
||||||
launch_server: true
|
|
||||||
- name: Setup Playwright
|
|
||||||
uses: ./.github/actions/setup-playwright
|
|
||||||
|
|
||||||
- name: Start dev server
|
|
||||||
# Run electron dev server as it is a superset of the web dev server
|
|
||||||
# We do want electron specific UIs to be translated.
|
|
||||||
run: pnpm dev:electron &
|
|
||||||
|
|
||||||
# Update locales, collect new strings and update translations using OpenAI, then commit changes
|
|
||||||
- name: Update en.json
|
|
||||||
run: pnpm collect-i18n
|
|
||||||
env:
|
|
||||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
|
||||||
- name: Update translations
|
|
||||||
run: pnpm locale
|
|
||||||
env:
|
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
||||||
- name: Commit updated locales
|
|
||||||
run: |
|
|
||||||
git config --global user.name 'github-actions'
|
|
||||||
git config --global user.email 'github-actions@github.com'
|
|
||||||
git fetch origin ${{ github.head_ref }}
|
|
||||||
# Stash any local changes before checkout
|
|
||||||
git stash -u
|
|
||||||
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
|
|
||||||
# Apply the stashed changes if any
|
|
||||||
git stash pop || true
|
|
||||||
git add src/locales/
|
|
||||||
git diff --staged --quiet || git commit -m "Update locales"
|
|
||||||
git push origin HEAD:${{ github.head_ref }}
|
|
||||||
136
.github/workflows/i18n-update-custom-nodes.yaml
vendored
136
.github/workflows/i18n-update-custom-nodes.yaml
vendored
@@ -1,136 +0,0 @@
|
|||||||
name: i18n Update Custom Nodes
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
owner:
|
|
||||||
description: 'Owner of the repository to update locales for'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
repository:
|
|
||||||
description: 'Repository to update locales for'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
fork_owner:
|
|
||||||
description: 'Owner of the forked repository'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: 'Comfy-Org'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-locales:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
# Setup playwright environment with custom node repository
|
|
||||||
- name: Setup ComfyUI Server (without launching)
|
|
||||||
uses: ./.github/actions/setup-comfyui-server
|
|
||||||
- name: Setup frontend
|
|
||||||
uses: ./.github/actions/setup-frontend
|
|
||||||
with:
|
|
||||||
include_build_step: 'true'
|
|
||||||
- name: Setup Playwright
|
|
||||||
uses: ./.github/actions/setup-playwright
|
|
||||||
|
|
||||||
# Install the custom node repository
|
|
||||||
- name: Checkout custom node repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
repository: ${{ inputs.owner }}/${{ inputs.repository }}
|
|
||||||
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
|
|
||||||
- name: Install custom node Python requirements
|
|
||||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
|
||||||
run: |
|
|
||||||
if [ -f "requirements.txt" ]; then
|
|
||||||
pip install -r requirements.txt
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start ComfyUI Server
|
|
||||||
- name: Start ComfyUI Server
|
|
||||||
shell: bash
|
|
||||||
working-directory: ComfyUI
|
|
||||||
run: |
|
|
||||||
python main.py --cpu --multi-user --front-end-root ../dist --custom-node-path ../ComfyUI/custom_nodes/${{ inputs.repository }} &
|
|
||||||
wait-for-it --service
|
|
||||||
|
|
||||||
- name: Start dev server
|
|
||||||
# Run electron dev server as it is a superset of the web dev server
|
|
||||||
# We do want electron specific UIs to be translated.
|
|
||||||
run: pnpm dev:electron &
|
|
||||||
|
|
||||||
- name: Capture base i18n
|
|
||||||
run: pnpm exec tsx scripts/diff-i18n capture
|
|
||||||
- name: Update en.json
|
|
||||||
run: pnpm collect-i18n
|
|
||||||
env:
|
|
||||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
|
||||||
- name: Update translations
|
|
||||||
run: pnpm locale
|
|
||||||
env:
|
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
||||||
- name: Diff base vs updated i18n
|
|
||||||
run: pnpm exec tsx scripts/diff-i18n diff
|
|
||||||
- name: Update i18n in custom node repository
|
|
||||||
run: |
|
|
||||||
LOCALE_DIR=ComfyUI/custom_nodes/${{ inputs.repository }}/locales/
|
|
||||||
install -d "$LOCALE_DIR"
|
|
||||||
cp -rf ComfyUI_frontend/temp/diff/* "$LOCALE_DIR"
|
|
||||||
|
|
||||||
# Git ops for pushing changes and creating PR
|
|
||||||
- name: Check and create fork of custom node repository
|
|
||||||
run: |
|
|
||||||
# Try to fork the repository
|
|
||||||
gh repo fork ${{ inputs.owner }}/${{ inputs.repository }} --clone=false || {
|
|
||||||
echo "Fork failed - repository might already be forked"
|
|
||||||
# Exit 0 to prevent the workflow from failing
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Enable workflows on the forked repository
|
|
||||||
gh api \
|
|
||||||
--method PUT \
|
|
||||||
-H "Accept: application/vnd.github+json" \
|
|
||||||
"/repos/${{ inputs.fork_owner }}/${{ inputs.repository }}/actions/permissions/workflow" \
|
|
||||||
-F can_approve_pull_request_reviews=true \
|
|
||||||
-F default_workflow_permissions="write" \
|
|
||||||
-F enabled=true
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
|
||||||
|
|
||||||
- name: Commit changes
|
|
||||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
|
||||||
run: |
|
|
||||||
git config --global user.name 'github-actions'
|
|
||||||
git config --global user.email 'github-actions@github.com'
|
|
||||||
|
|
||||||
# Create and switch to new branch
|
|
||||||
git checkout -b update-locales
|
|
||||||
|
|
||||||
# Stage and commit changes
|
|
||||||
git add -A
|
|
||||||
git commit -m "Update locales"
|
|
||||||
|
|
||||||
- name: Install SSH key For PUSH
|
|
||||||
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4
|
|
||||||
with:
|
|
||||||
# PR private key from action server
|
|
||||||
key: ${{ secrets.PR_SSH_PRIVATE_KEY }}
|
|
||||||
# github public key to confirm it's github server
|
|
||||||
known_hosts: github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
|
||||||
|
|
||||||
- name: Push changes
|
|
||||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
|
||||||
run: |
|
|
||||||
# Force push to create the branch
|
|
||||||
echo "Pushing changes to ${{ inputs.fork_owner }}/${{ inputs.repository }}"
|
|
||||||
git push -f git@github.com:${{ inputs.fork_owner }}/${{ inputs.repository }}.git update-locales
|
|
||||||
|
|
||||||
- name: Create PR
|
|
||||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
|
||||||
run: |
|
|
||||||
# Create PR using gh cli
|
|
||||||
gh pr create --title "Update locales for ${{ inputs.repository }}" --repo ${{ inputs.owner }}/${{ inputs.repository }} --head ${{ inputs.fork_owner }}:update-locales --body "Update locales for ${{ inputs.repository }}"
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
|
||||||
54
.github/workflows/i18n-update-nodes.yaml
vendored
54
.github/workflows/i18n-update-nodes.yaml
vendored
@@ -1,54 +0,0 @@
|
|||||||
name: i18n Update Nodes
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
trigger_type:
|
|
||||||
description: 'Type of trigger (manual or automatic)'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: 'manual'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-locales:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
# Setup playwright environment
|
|
||||||
- name: Setup ComfyUI Server (and start)
|
|
||||||
uses: ./.github/actions/setup-comfyui-server
|
|
||||||
with:
|
|
||||||
launch_server: true
|
|
||||||
- name: Setup frontend
|
|
||||||
uses: ./.github/actions/setup-frontend
|
|
||||||
with:
|
|
||||||
include_build_step: true
|
|
||||||
- name: Setup Playwright
|
|
||||||
uses: ./.github/actions/setup-playwright
|
|
||||||
|
|
||||||
- name: Start dev server
|
|
||||||
# Run electron dev server as it is a superset of the web dev server
|
|
||||||
# We do want electron specific UIs to be translated.
|
|
||||||
run: pnpm dev:electron &
|
|
||||||
- name: Update en.json
|
|
||||||
run: pnpm collect-i18n -- scripts/collect-i18n-node-defs.ts
|
|
||||||
env:
|
|
||||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
|
||||||
- name: Update translations
|
|
||||||
run: pnpm locale
|
|
||||||
env:
|
|
||||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
||||||
- name: Create Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
|
||||||
commit-message: "Update locales for node definitions"
|
|
||||||
title: "Update locales for node definitions"
|
|
||||||
body: |
|
|
||||||
Automated PR to update locales for node definitions
|
|
||||||
|
|
||||||
This PR was created automatically by the frontend update workflow.
|
|
||||||
branch: update-locales-node-defs-${{ github.event.inputs.trigger_type }}-${{ github.run_id }}
|
|
||||||
base: main
|
|
||||||
labels: dependencies
|
|
||||||
66
.github/workflows/i18n.yaml
vendored
Normal file
66
.github/workflows/i18n.yaml
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
name: Update Locales
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Manual dispatch for urgent translation updates
|
||||||
|
workflow_dispatch:
|
||||||
|
# Only trigger on PRs to main/master - additional branch filtering in job condition
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-locales:
|
||||||
|
# Branch detection: Only run for manual dispatch or version-bump-* branches from main repo
|
||||||
|
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-'))
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
ComfyUI_frontend/.cache
|
||||||
|
ComfyUI_frontend/.cache
|
||||||
|
key: i18n-tools-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
i18n-tools-cache-${{ runner.os }}-
|
||||||
|
- name: Cache Playwright browsers
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cache/ms-playwright
|
||||||
|
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
playwright-browsers-${{ runner.os }}-
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: npx playwright install chromium --with-deps
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Start dev server
|
||||||
|
# Run electron dev server as it is a superset of the web dev server
|
||||||
|
# We do want electron specific UIs to be translated.
|
||||||
|
run: pnpm dev:electron &
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Update en.json
|
||||||
|
run: pnpm collect-i18n
|
||||||
|
env:
|
||||||
|
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Update translations
|
||||||
|
run: pnpm locale
|
||||||
|
env:
|
||||||
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Commit updated locales
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'github-actions'
|
||||||
|
git config --global user.email 'github-actions@github.com'
|
||||||
|
git fetch origin ${{ github.head_ref }}
|
||||||
|
# Stash any local changes before checkout
|
||||||
|
git stash -u
|
||||||
|
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
|
||||||
|
# Apply the stashed changes if any
|
||||||
|
git stash pop || true
|
||||||
|
git add src/locales/
|
||||||
|
git diff --staged --quiet || git commit -m "Update locales [skip ci]"
|
||||||
|
git push origin HEAD:${{ github.head_ref }}
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
name: "CI: Lint Format"
|
name: Lint and Format
|
||||||
description: "Linting and code formatting validation for pull requests"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches-ignore: [wip/*, draft/*, temp/*]
|
branches-ignore: [wip/*, draft/*, temp/*]
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
@@ -18,9 +13,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout PR
|
- name: Checkout PR
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ !github.event.pull_request.head.repo.fork && github.head_ref || github.ref }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -33,15 +30,27 @@ jobs:
|
|||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.cache
|
||||||
|
.eslintcache
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
.prettierCache
|
||||||
|
.knip-cache
|
||||||
|
key: lint-format-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js,mts}', '*.config.*', '.eslintrc.*', '.prettierrc.*', 'tsconfig.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
lint-format-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
|
||||||
|
lint-format-cache-${{ runner.os }}-
|
||||||
|
ci-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run ESLint with auto-fix
|
- name: Run ESLint with auto-fix
|
||||||
run: pnpm lint:fix
|
run: pnpm lint:fix
|
||||||
|
|
||||||
- name: Run Stylelint with auto-fix
|
|
||||||
run: pnpm stylelint:fix
|
|
||||||
|
|
||||||
- name: Run Prettier with auto-format
|
- name: Run Prettier with auto-format
|
||||||
run: pnpm format
|
run: pnpm format
|
||||||
|
|
||||||
@@ -51,7 +60,7 @@ jobs:
|
|||||||
if [ -n "$(git status --porcelain)" ]; then
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
echo "changed=true" >> $GITHUB_OUTPUT
|
echo "changed=true" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
echo "changed=false" >> $GITHUB_OUTPUT
|
echo "changed=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
@@ -60,13 +69,12 @@ jobs:
|
|||||||
git config --local user.email "action@github.com"
|
git config --local user.email "action@github.com"
|
||||||
git config --local user.name "GitHub Action"
|
git config --local user.name "GitHub Action"
|
||||||
git add .
|
git add .
|
||||||
git commit -m "[automated] Apply ESLint and Prettier fixes"
|
git commit -m "[auto-fix] Apply ESLint and Prettier fixes"
|
||||||
git push
|
git push
|
||||||
|
|
||||||
- name: Final validation
|
- name: Final validation
|
||||||
run: |
|
run: |
|
||||||
pnpm lint
|
pnpm lint
|
||||||
pnpm stylelint
|
|
||||||
pnpm format:check
|
pnpm format:check
|
||||||
pnpm knip
|
pnpm knip
|
||||||
|
|
||||||
@@ -94,4 +102,4 @@ jobs:
|
|||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
body: '## ⚠️ Linting/Formatting Issues Found\n\nThis PR has linting or formatting issues that need to be fixed.\n\n**Since this PR is from a fork, auto-fix cannot be applied automatically.**\n\n### Option 1: Set up pre-commit hooks (recommended)\nRun this once to automatically format code on every commit:\n```bash\npnpm prepare\n```\n\n### Option 2: Fix manually\nRun these commands and push the changes:\n```bash\npnpm lint:fix\npnpm format\n```\n\nSee [CONTRIBUTING.md](https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/CONTRIBUTING.md#git-pre-commit-hooks) for more details.'
|
body: '## ⚠️ Linting/Formatting Issues Found\n\nThis PR has linting or formatting issues that need to be fixed.\n\n**Since this PR is from a fork, auto-fix cannot be applied automatically.**\n\n### Option 1: Set up pre-commit hooks (recommended)\nRun this once to automatically format code on every commit:\n```bash\npnpm prepare\n```\n\n### Option 2: Fix manually\nRun these commands and push the changes:\n```bash\npnpm lint:fix\npnpm format\n```\n\nSee [CONTRIBUTING.md](https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/CONTRIBUTING.md#git-pre-commit-hooks) for more details.'
|
||||||
})
|
})
|
||||||
414
.github/workflows/pr-backport.yaml
vendored
414
.github/workflows/pr-backport.yaml
vendored
@@ -1,414 +0,0 @@
|
|||||||
name: PR Backport
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [closed, labeled]
|
|
||||||
branches: [main]
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
pr_number:
|
|
||||||
description: 'PR number to backport'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
force_rerun:
|
|
||||||
description: 'Force rerun even if backports exist'
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
backport:
|
|
||||||
if: >
|
|
||||||
(github.event_name == 'pull_request_target' &&
|
|
||||||
github.event.pull_request.merged == true &&
|
|
||||||
contains(github.event.pull_request.labels.*.name, 'needs-backport')) ||
|
|
||||||
github.event_name == 'workflow_dispatch'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Validate inputs for manual triggers
|
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
run: |
|
|
||||||
# Validate PR number format
|
|
||||||
if ! [[ "${{ inputs.pr_number }}" =~ ^[0-9]+$ ]]; then
|
|
||||||
echo "::error::Invalid PR number format. Must be a positive integer."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Validate PR exists and is merged
|
|
||||||
if ! gh pr view "${{ inputs.pr_number }}" --json merged >/dev/null 2>&1; then
|
|
||||||
echo "::error::PR #${{ inputs.pr_number }} not found or inaccessible."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
MERGED=$(gh pr view "${{ inputs.pr_number }}" --json merged --jq '.merged')
|
|
||||||
if [ "$MERGED" != "true" ]; then
|
|
||||||
echo "::error::PR #${{ inputs.pr_number }} is not merged. Only merged PRs can be backported."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Validate PR has needs-backport label
|
|
||||||
if ! gh pr view "${{ inputs.pr_number }}" --json labels --jq '.labels[].name' | grep -q "needs-backport"; then
|
|
||||||
echo "::error::PR #${{ inputs.pr_number }} does not have 'needs-backport' label."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Configure git
|
|
||||||
run: |
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
|
|
||||||
- name: Collect backport targets
|
|
||||||
id: targets
|
|
||||||
run: |
|
|
||||||
TARGETS=()
|
|
||||||
declare -A SEEN=()
|
|
||||||
|
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
||||||
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
|
|
||||||
else
|
|
||||||
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
|
|
||||||
LABELS=$(echo "$LABELS" | jq -r '.[].name')
|
|
||||||
fi
|
|
||||||
|
|
||||||
add_target() {
|
|
||||||
local label="$1"
|
|
||||||
local target="$2"
|
|
||||||
|
|
||||||
if [ -z "$target" ]; then
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
target=$(echo "$target" | xargs)
|
|
||||||
|
|
||||||
if [ -z "$target" ] || [ -n "${SEEN[$target]}" ]; then
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if git ls-remote --exit-code origin "$target" >/dev/null 2>&1; then
|
|
||||||
TARGETS+=("$target")
|
|
||||||
SEEN["$target"]=1
|
|
||||||
else
|
|
||||||
echo "::warning::Label '${label}' references missing branch '${target}'"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
while IFS= read -r label; do
|
|
||||||
[ -z "$label" ] && continue
|
|
||||||
|
|
||||||
if [[ "$label" =~ ^branch:(.+)$ ]]; then
|
|
||||||
add_target "$label" "${BASH_REMATCH[1]}"
|
|
||||||
elif [[ "$label" =~ ^backport:(.+)$ ]]; then
|
|
||||||
add_target "$label" "${BASH_REMATCH[1]}"
|
|
||||||
elif [[ "$label" =~ ^core\/([0-9]+)\.([0-9]+)$ ]]; then
|
|
||||||
SAFE_MAJOR="${BASH_REMATCH[1]}"
|
|
||||||
SAFE_MINOR="${BASH_REMATCH[2]}"
|
|
||||||
add_target "$label" "core/${SAFE_MAJOR}.${SAFE_MINOR}"
|
|
||||||
elif [[ "$label" =~ ^cloud\/([0-9]+)\.([0-9]+)$ ]]; then
|
|
||||||
SAFE_MAJOR="${BASH_REMATCH[1]}"
|
|
||||||
SAFE_MINOR="${BASH_REMATCH[2]}"
|
|
||||||
add_target "$label" "cloud/${SAFE_MAJOR}.${SAFE_MINOR}"
|
|
||||||
elif [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then
|
|
||||||
add_target "$label" "core/${label}"
|
|
||||||
fi
|
|
||||||
done <<< "$LABELS"
|
|
||||||
|
|
||||||
if [ "${#TARGETS[@]}" -eq 0 ]; then
|
|
||||||
echo "::error::No backport targets found (use labels like '1.24' or 'branch:release/hotfix')"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "targets=${TARGETS[*]}" >> $GITHUB_OUTPUT
|
|
||||||
echo "Found backport targets: ${TARGETS[*]}"
|
|
||||||
|
|
||||||
- name: Filter already backported targets
|
|
||||||
id: filter-targets
|
|
||||||
env:
|
|
||||||
EVENT_NAME: ${{ github.event_name }}
|
|
||||||
FORCE_RERUN_INPUT: >-
|
|
||||||
${{ github.event_name == 'workflow_dispatch' && inputs.force_rerun
|
|
||||||
|| 'false' }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
PR_NUMBER: >-
|
|
||||||
${{ github.event_name == 'workflow_dispatch' && inputs.pr_number
|
|
||||||
|| github.event.pull_request.number }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
REQUESTED_TARGETS="${{ steps.targets.outputs.targets }}"
|
|
||||||
if [ -z "$REQUESTED_TARGETS" ]; then
|
|
||||||
echo "skip=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "pending-targets=" >> $GITHUB_OUTPUT
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
FORCE_RERUN=false
|
|
||||||
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$FORCE_RERUN_INPUT" = "true" ]; then
|
|
||||||
FORCE_RERUN=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
mapfile -t EXISTING_BRANCHES < <(
|
|
||||||
git ls-remote --heads origin "backport-${PR_NUMBER}-to-*" || true
|
|
||||||
)
|
|
||||||
|
|
||||||
PENDING=()
|
|
||||||
SKIPPED=()
|
|
||||||
REUSED=()
|
|
||||||
|
|
||||||
for target in $REQUESTED_TARGETS; do
|
|
||||||
SAFE_TARGET=$(echo "$target" | tr '/' '-')
|
|
||||||
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}"
|
|
||||||
|
|
||||||
if [ "$FORCE_RERUN" = true ]; then
|
|
||||||
PENDING+=("$target")
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if printf '%s\n' "${EXISTING_BRANCHES[@]:-}" |
|
|
||||||
grep -Fq "refs/heads/${BACKPORT_BRANCH}"; then
|
|
||||||
OPEN_PR=$(
|
|
||||||
gh pr list \
|
|
||||||
--state open \
|
|
||||||
--head "${BACKPORT_BRANCH}" \
|
|
||||||
--json number \
|
|
||||||
--jq 'if length > 0 then .[0].number else "" end'
|
|
||||||
)
|
|
||||||
if [ -n "$OPEN_PR" ]; then
|
|
||||||
SKIPPED+=("${target} (PR #${OPEN_PR})")
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
REUSED+=("$BACKPORT_BRANCH")
|
|
||||||
fi
|
|
||||||
|
|
||||||
PENDING+=("$target")
|
|
||||||
done
|
|
||||||
|
|
||||||
SKIPPED_JOINED="${SKIPPED[*]:-}"
|
|
||||||
PENDING_JOINED="${PENDING[*]:-}"
|
|
||||||
|
|
||||||
echo "already-exists=${SKIPPED_JOINED}" >> $GITHUB_OUTPUT
|
|
||||||
echo "pending-targets=${PENDING_JOINED}" >> $GITHUB_OUTPUT
|
|
||||||
echo "reused-branches=${REUSED[*]:-}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
if [ -z "$PENDING_JOINED" ]; then
|
|
||||||
echo "skip=true" >> $GITHUB_OUTPUT
|
|
||||||
if [ -n "$SKIPPED_JOINED" ]; then
|
|
||||||
echo "::warning::Backport branches exist: ${SKIPPED_JOINED}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "skip=false" >> $GITHUB_OUTPUT
|
|
||||||
if [ -n "$SKIPPED_JOINED" ]; then
|
|
||||||
echo "::notice::Skipping backport targets: ${SKIPPED_JOINED}"
|
|
||||||
fi
|
|
||||||
if [ "${#REUSED[@]}" -gt 0 ]; then
|
|
||||||
echo "::notice::Reusing backport branches: ${REUSED[*]}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Backport commits
|
|
||||||
if: steps.filter-targets.outputs.skip != 'true'
|
|
||||||
id: backport
|
|
||||||
env:
|
|
||||||
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
|
|
||||||
run: |
|
|
||||||
FAILED=""
|
|
||||||
SUCCESS=""
|
|
||||||
|
|
||||||
CREATED_BRANCHES_FILE="$(
|
|
||||||
mktemp "$RUNNER_TEMP/backport-branches-XXXXXX"
|
|
||||||
)"
|
|
||||||
echo "CREATED_BRANCHES_FILE=$CREATED_BRANCHES_FILE" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
# Get PR data for manual triggers
|
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
||||||
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,mergeCommit)
|
|
||||||
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
|
||||||
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
|
|
||||||
else
|
|
||||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
|
||||||
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
for target in ${{ steps.filter-targets.outputs.pending-targets }}; do
|
|
||||||
TARGET_BRANCH="${target}"
|
|
||||||
SAFE_TARGET=$(echo "$TARGET_BRANCH" | tr '/' '-')
|
|
||||||
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}"
|
|
||||||
REMOTE_BACKPORT_EXISTS=false
|
|
||||||
|
|
||||||
if git ls-remote --exit-code origin "${BACKPORT_BRANCH}" >/dev/null 2>&1; then
|
|
||||||
REMOTE_BACKPORT_EXISTS=true
|
|
||||||
echo "::notice::Updating existing branch ${BACKPORT_BRANCH}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "::group::Backporting to ${TARGET_BRANCH}"
|
|
||||||
|
|
||||||
# Fetch target branch (fail if doesn't exist)
|
|
||||||
if ! git fetch origin "${TARGET_BRANCH}"; then
|
|
||||||
echo "::error::Target branch ${TARGET_BRANCH} does not exist"
|
|
||||||
FAILED="${FAILED}${TARGET_BRANCH}:branch-missing "
|
|
||||||
echo "::endgroup::"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if commit already exists on target branch
|
|
||||||
if git branch -r --contains "${MERGE_COMMIT}" | grep -q "origin/${TARGET_BRANCH}"; then
|
|
||||||
echo "::notice::Commit ${MERGE_COMMIT} already exists on ${TARGET_BRANCH}, skipping backport"
|
|
||||||
FAILED="${FAILED}${TARGET_BRANCH}:already-exists "
|
|
||||||
echo "::endgroup::"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create backport branch
|
|
||||||
git checkout -b "${BACKPORT_BRANCH}" "origin/${TARGET_BRANCH}"
|
|
||||||
|
|
||||||
# Try cherry-pick
|
|
||||||
if git cherry-pick "${MERGE_COMMIT}"; then
|
|
||||||
if [ "$REMOTE_BACKPORT_EXISTS" = true ]; then
|
|
||||||
git push --force-with-lease origin "${BACKPORT_BRANCH}"
|
|
||||||
else
|
|
||||||
git push origin "${BACKPORT_BRANCH}"
|
|
||||||
fi
|
|
||||||
echo "${BACKPORT_BRANCH}" >> "$CREATED_BRANCHES_FILE"
|
|
||||||
SUCCESS="${SUCCESS}${TARGET_BRANCH}:${BACKPORT_BRANCH} "
|
|
||||||
echo "Successfully created backport branch: ${BACKPORT_BRANCH}"
|
|
||||||
# Return to main (keep the branch, we need it for PR)
|
|
||||||
git checkout main
|
|
||||||
else
|
|
||||||
# Get conflict info
|
|
||||||
CONFLICTS=$(git diff --name-only --diff-filter=U | tr '\n' ',')
|
|
||||||
git cherry-pick --abort
|
|
||||||
|
|
||||||
echo "::error::Cherry-pick failed due to conflicts"
|
|
||||||
FAILED="${FAILED}${TARGET_BRANCH}:conflicts:${CONFLICTS} "
|
|
||||||
|
|
||||||
# Clean up the failed branch
|
|
||||||
git checkout main
|
|
||||||
git branch -D "${BACKPORT_BRANCH}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "::endgroup::"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "success=${SUCCESS}" >> $GITHUB_OUTPUT
|
|
||||||
echo "failed=${FAILED}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
if [ -s "$CREATED_BRANCHES_FILE" ]; then
|
|
||||||
CREATED_LIST=$(paste -sd' ' "$CREATED_BRANCHES_FILE")
|
|
||||||
echo "created-branches=${CREATED_LIST}" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "created-branches=" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${FAILED}" ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create PR for each successful backport
|
|
||||||
if: steps.filter-targets.outputs.skip != 'true' && steps.backport.outputs.success
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
|
||||||
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
|
|
||||||
run: |
|
|
||||||
# Get PR data for manual triggers
|
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
||||||
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,author)
|
|
||||||
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
|
||||||
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
|
|
||||||
else
|
|
||||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
|
||||||
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
for backport in ${{ steps.backport.outputs.success }}; do
|
|
||||||
IFS=':' read -r target branch <<< "${backport}"
|
|
||||||
|
|
||||||
if PR_URL=$(gh pr create \
|
|
||||||
--base "${target}" \
|
|
||||||
--head "${branch}" \
|
|
||||||
--title "[backport ${target}] ${PR_TITLE}" \
|
|
||||||
--body "Backport of #${PR_NUMBER} to \`${target}\`"$'\n\n'"Automatically created by backport workflow." \
|
|
||||||
--label "backport" 2>&1); then
|
|
||||||
|
|
||||||
# Extract PR number from URL
|
|
||||||
PR_NUM=$(echo "${PR_URL}" | grep -o '[0-9]*$')
|
|
||||||
|
|
||||||
if [ -n "${PR_NUM}" ]; then
|
|
||||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Successfully backported to #${PR_NUM}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "::error::Failed to create PR for ${target}: ${PR_URL}"
|
|
||||||
# Still try to comment on the original PR about the failure
|
|
||||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport branch created but PR creation failed for \`${target}\`. Please create the PR manually from branch \`${branch}\`"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Comment on failures
|
|
||||||
if: steps.filter-targets.outputs.skip != 'true' && failure() && steps.backport.outputs.failed
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
||||||
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json author,mergeCommit)
|
|
||||||
PR_NUMBER="${{ inputs.pr_number }}"
|
|
||||||
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
|
|
||||||
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
|
|
||||||
else
|
|
||||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
||||||
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
|
||||||
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
for failure in ${{ steps.backport.outputs.failed }}; do
|
|
||||||
IFS=':' read -r target reason conflicts <<< "${failure}"
|
|
||||||
|
|
||||||
if [ "${reason}" = "branch-missing" ]; then
|
|
||||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport failed: Branch \`${target}\` does not exist"
|
|
||||||
|
|
||||||
elif [ "${reason}" = "already-exists" ]; then
|
|
||||||
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Commit \`${MERGE_COMMIT}\` already exists on branch \`${target}\`. No backport needed."
|
|
||||||
|
|
||||||
elif [ "${reason}" = "conflicts" ]; then
|
|
||||||
# Convert comma-separated conflicts back to newlines for display
|
|
||||||
CONFLICTS_LIST=$(echo "${conflicts}" | tr ',' '\n' | sed 's/^/- /')
|
|
||||||
|
|
||||||
COMMENT_BODY="@${PR_AUTHOR} Backport to \`${target}\` failed: Merge conflicts detected."$'\n\n'"Please manually cherry-pick commit \`${MERGE_COMMIT}\` to the \`${target}\` branch."$'\n\n'"<details><summary>Conflicting files</summary>"$'\n\n'"${CONFLICTS_LIST}"$'\n\n'"</details>"
|
|
||||||
gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Cleanup stranded backport branches
|
|
||||||
if: steps.filter-targets.outputs.skip != 'true' && failure()
|
|
||||||
run: |
|
|
||||||
FILE="${CREATED_BRANCHES_FILE:-}"
|
|
||||||
|
|
||||||
if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
|
|
||||||
echo "No backport branches recorded for cleanup"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
while IFS= read -r branch; do
|
|
||||||
[ -z "$branch" ] && continue
|
|
||||||
printf 'Deleting branch %s\n' "${branch}"
|
|
||||||
if ! git push origin --delete "$branch"; then
|
|
||||||
echo "::warning::Failed to delete ${branch}"
|
|
||||||
fi
|
|
||||||
done < "$FILE"
|
|
||||||
|
|
||||||
|
|
||||||
- name: Remove needs-backport label
|
|
||||||
if: steps.filter-targets.outputs.skip != 'true' && success()
|
|
||||||
run: gh pr edit ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} --remove-label "needs-backport"
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
name: "CI: Tests E2E (Deploy for Forks)"
|
name: PR Playwright Deploy (Forks)
|
||||||
description: "Deploys test results from forked PRs (forks can't access deployment secrets)"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: ["CI: Tests E2E"]
|
workflows: ["Tests CI"]
|
||||||
types: [requested, completed]
|
types: [requested, completed]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -13,7 +12,7 @@ jobs:
|
|||||||
deploy-and-comment-forked-pr:
|
deploy-and-comment-forked-pr:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: |
|
if: |
|
||||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
||||||
github.event.workflow_run.event == 'pull_request' &&
|
github.event.workflow_run.event == 'pull_request' &&
|
||||||
github.event.workflow_run.head_repository != null &&
|
github.event.workflow_run.head_repository != null &&
|
||||||
github.event.workflow_run.repository != null &&
|
github.event.workflow_run.repository != null &&
|
||||||
@@ -31,7 +30,7 @@ jobs:
|
|||||||
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
|
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get PR Number
|
- name: Get PR Number
|
||||||
id: pr
|
id: pr
|
||||||
@@ -43,14 +42,14 @@ jobs:
|
|||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
state: 'open',
|
state: 'open',
|
||||||
});
|
});
|
||||||
|
|
||||||
const pr = prs.find(p => p.head.sha === context.payload.workflow_run.head_sha);
|
const pr = prs.find(p => p.head.sha === context.payload.workflow_run.head_sha);
|
||||||
|
|
||||||
if (!pr) {
|
if (!pr) {
|
||||||
console.log('No PR found for SHA:', context.payload.workflow_run.head_sha);
|
console.log('No PR found for SHA:', context.payload.workflow_run.head_sha);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Found PR #${pr.number} from fork: ${context.payload.workflow_run.head_repository.full_name}`);
|
console.log(`Found PR #${pr.number} from fork: ${context.payload.workflow_run.head_repository.full_name}`);
|
||||||
return pr.number;
|
return pr.number;
|
||||||
|
|
||||||
@@ -74,7 +73,7 @@ jobs:
|
|||||||
run-id: ${{ github.event.workflow_run.id }}
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
pattern: playwright-report-*
|
pattern: playwright-report-*
|
||||||
path: reports
|
path: reports
|
||||||
|
|
||||||
- name: Handle Test Completion
|
- name: Handle Test Completion
|
||||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
||||||
env:
|
env:
|
||||||
@@ -85,9 +84,9 @@ jobs:
|
|||||||
# Rename merged report if exists
|
# Rename merged report if exists
|
||||||
[ -d "reports/playwright-report-chromium-merged" ] && \
|
[ -d "reports/playwright-report-chromium-merged" ] && \
|
||||||
mv reports/playwright-report-chromium-merged reports/playwright-report-chromium
|
mv reports/playwright-report-chromium-merged reports/playwright-report-chromium
|
||||||
|
|
||||||
chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
|
chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
|
||||||
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
||||||
"${{ steps.pr.outputs.result }}" \
|
"${{ steps.pr.outputs.result }}" \
|
||||||
"${{ github.event.workflow_run.head_branch }}" \
|
"${{ github.event.workflow_run.head_branch }}" \
|
||||||
"completed"
|
"completed"
|
||||||
104
.github/workflows/pr-size-report.yaml
vendored
104
.github/workflows/pr-size-report.yaml
vendored
@@ -1,104 +0,0 @@
|
|||||||
name: "PR: Size Report"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ['CI: Size Data']
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
pr_number:
|
|
||||||
description: 'PR number to report on'
|
|
||||||
required: true
|
|
||||||
type: number
|
|
||||||
run_id:
|
|
||||||
description: 'Size data workflow run ID'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
comment:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: >
|
|
||||||
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
|
||||||
(
|
|
||||||
(github.event_name == 'workflow_run' &&
|
|
||||||
github.event.workflow_run.event == 'pull_request' &&
|
|
||||||
github.event.workflow_run.conclusion == 'success') ||
|
|
||||||
github.event_name == 'workflow_dispatch'
|
|
||||||
)
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- 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: Download size data
|
|
||||||
uses: dawidd6/action-download-artifact@v11
|
|
||||||
with:
|
|
||||||
name: size-data
|
|
||||||
run_id: ${{ github.event_name == 'workflow_dispatch' && inputs.run_id || github.event.workflow_run.id }}
|
|
||||||
path: temp/size
|
|
||||||
|
|
||||||
- name: Set PR number
|
|
||||||
id: pr-number
|
|
||||||
run: |
|
|
||||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
|
||||||
echo "content=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "content=$(cat temp/size/number.txt)" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Set base branch
|
|
||||||
id: pr-base
|
|
||||||
run: |
|
|
||||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
|
||||||
echo "content=main" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "content=$(cat temp/size/base.txt)" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Download previous size data
|
|
||||||
uses: dawidd6/action-download-artifact@v11
|
|
||||||
with:
|
|
||||||
branch: ${{ steps.pr-base.outputs.content }}
|
|
||||||
workflow: ci-size-data.yaml
|
|
||||||
event: push
|
|
||||||
name: size-data
|
|
||||||
path: temp/size-prev
|
|
||||||
if_no_artifact_found: warn
|
|
||||||
|
|
||||||
- name: Generate size report
|
|
||||||
run: node scripts/size-report.js > size-report.md
|
|
||||||
|
|
||||||
- name: Read size report
|
|
||||||
id: size-report
|
|
||||||
uses: juliangruber/read-file-action@v1
|
|
||||||
with:
|
|
||||||
path: ./size-report.md
|
|
||||||
|
|
||||||
- name: Create or update PR comment
|
|
||||||
uses: actions-cool/maintain-one-comment@v3
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
number: ${{ steps.pr-number.outputs.content }}
|
|
||||||
body: |
|
|
||||||
${{ steps.size-report.outputs.content }}
|
|
||||||
<!-- COMFYUI_FRONTEND_SIZE -->
|
|
||||||
body-include: '<!-- COMFYUI_FRONTEND_SIZE -->'
|
|
||||||
126
.github/workflows/pr-storybook-comment.yaml
vendored
Normal file
126
.github/workflows/pr-storybook-comment.yaml
vendored
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
name: PR Storybook Comment
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ['Chromatic']
|
||||||
|
types: [requested, completed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment-storybook:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >-
|
||||||
|
github.repository == 'Comfy-Org/ComfyUI_frontend'
|
||||||
|
&& github.event.workflow_run.event == 'pull_request'
|
||||||
|
&& startsWith(github.event.workflow_run.head_branch, 'version-bump-')
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
actions: read
|
||||||
|
steps:
|
||||||
|
- name: Get PR number
|
||||||
|
id: pr
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { data: pullRequests } = await github.rest.pulls.list({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
state: 'open',
|
||||||
|
head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pullRequests.length === 0) {
|
||||||
|
console.log('No open PR found for this branch');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pullRequests[0].number;
|
||||||
|
|
||||||
|
- name: Log when no PR found
|
||||||
|
if: steps.pr.outputs.result == 'null'
|
||||||
|
run: |
|
||||||
|
echo "⚠️ No open PR found for branch: ${{ github.event.workflow_run.head_branch }}"
|
||||||
|
echo "Workflow run ID: ${{ github.event.workflow_run.id }}"
|
||||||
|
echo "Repository: ${{ github.event.workflow_run.repository.full_name }}"
|
||||||
|
echo "Event: ${{ github.event.workflow_run.event }}"
|
||||||
|
|
||||||
|
- name: Get workflow run details
|
||||||
|
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
||||||
|
id: workflow-run
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const run = await github.rest.actions.getWorkflowRun({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
run_id: context.payload.workflow_run.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
conclusion: run.data.conclusion,
|
||||||
|
html_url: run.data.html_url
|
||||||
|
};
|
||||||
|
|
||||||
|
- name: Get completion time
|
||||||
|
id: completion-time
|
||||||
|
run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Comment PR - Storybook Started
|
||||||
|
if: steps.pr.outputs.result != 'null' && github.event.action == 'requested'
|
||||||
|
uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0
|
||||||
|
with:
|
||||||
|
issue-number: ${{ steps.pr.outputs.result }}
|
||||||
|
body-includes: '<!-- STORYBOOK_BUILD_STATUS -->'
|
||||||
|
comment-author: 'github-actions[bot]'
|
||||||
|
edit-mode: replace
|
||||||
|
body: |
|
||||||
|
<!-- STORYBOOK_BUILD_STATUS -->
|
||||||
|
## 🎨 Storybook Build Status
|
||||||
|
|
||||||
|
<img alt='comfy-loading-gif' src='https://github.com/user-attachments/assets/755c86ee-e445-4ea8-bc2c-cca85df48686' width='14px' height='14px' style='vertical-align: middle; margin-right: 4px;' /> **Build is starting...**
|
||||||
|
|
||||||
|
⏰ Started at: ${{ steps.completion-time.outputs.time }} UTC
|
||||||
|
|
||||||
|
### 🚀 Building Storybook
|
||||||
|
- 📦 Installing dependencies...
|
||||||
|
- 🔧 Building Storybook components...
|
||||||
|
- 🎨 Running Chromatic visual tests...
|
||||||
|
|
||||||
|
---
|
||||||
|
⏱️ Please wait while the Storybook build is in progress...
|
||||||
|
|
||||||
|
- name: Comment PR - Storybook Complete
|
||||||
|
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
||||||
|
uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0
|
||||||
|
with:
|
||||||
|
issue-number: ${{ steps.pr.outputs.result }}
|
||||||
|
body-includes: '<!-- STORYBOOK_BUILD_STATUS -->'
|
||||||
|
comment-author: 'github-actions[bot]'
|
||||||
|
edit-mode: replace
|
||||||
|
body: |
|
||||||
|
<!-- STORYBOOK_BUILD_STATUS -->
|
||||||
|
## 🎨 Storybook Build Status
|
||||||
|
|
||||||
|
${{
|
||||||
|
fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && '✅'
|
||||||
|
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && '⏭️'
|
||||||
|
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && '🚫'
|
||||||
|
|| '❌'
|
||||||
|
}} **${{
|
||||||
|
fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && 'Build completed successfully!'
|
||||||
|
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && 'Build skipped.'
|
||||||
|
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && 'Build cancelled.'
|
||||||
|
|| 'Build failed!'
|
||||||
|
}}**
|
||||||
|
|
||||||
|
⏰ Completed at: ${{ steps.completion-time.outputs.time }} UTC
|
||||||
|
|
||||||
|
### 🔗 Links
|
||||||
|
- [📊 View Workflow Run](${{ fromJSON(steps.workflow-run.outputs.result).html_url }})
|
||||||
|
|
||||||
|
---
|
||||||
|
${{
|
||||||
|
fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && '🎉 Your Storybook is ready for review!'
|
||||||
|
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && 'ℹ️ Chromatic was skipped for this PR.'
|
||||||
|
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && 'ℹ️ The Chromatic run was cancelled.'
|
||||||
|
|| '⚠️ Please check the workflow logs for error details.'
|
||||||
|
}}
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
# Setting test expectation screenshots for Playwright
|
|
||||||
name: "PR: Update Playwright Expectations"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [labeled]
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
setup:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: >
|
|
||||||
( github.event_name == 'pull_request' && github.event.label.name == 'New Browser Test Expectations' ) ||
|
|
||||||
( github.event.issue.pull_request &&
|
|
||||||
github.event_name == 'issue_comment' &&
|
|
||||||
(
|
|
||||||
github.event.comment.author_association == 'OWNER' ||
|
|
||||||
github.event.comment.author_association == 'MEMBER' ||
|
|
||||||
github.event.comment.author_association == 'COLLABORATOR'
|
|
||||||
) &&
|
|
||||||
startsWith(github.event.comment.body, '/update-playwright') )
|
|
||||||
outputs:
|
|
||||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
|
||||||
pr-number: ${{ steps.pr-info.outputs.pr-number }}
|
|
||||||
branch: ${{ steps.pr-info.outputs.branch }}
|
|
||||||
comment-id: ${{ steps.find-update-comment.outputs.comment-id }}
|
|
||||||
steps:
|
|
||||||
- name: Get PR info
|
|
||||||
id: pr-info
|
|
||||||
run: |
|
|
||||||
echo "pr-number=${{ github.event.number || github.event.issue.number }}" >> $GITHUB_OUTPUT
|
|
||||||
echo "branch=$(gh pr view ${{ github.event.number || github.event.issue.number }} --repo ${{ github.repository }} --json headRefName --jq '.headRefName')" >> $GITHUB_OUTPUT
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Find Update Comment
|
|
||||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad
|
|
||||||
id: "find-update-comment"
|
|
||||||
with:
|
|
||||||
issue-number: ${{ steps.pr-info.outputs.pr-number }}
|
|
||||||
comment-author: "github-actions[bot]"
|
|
||||||
body-includes: "Updating Playwright Expectations"
|
|
||||||
|
|
||||||
- name: Add Starting Reaction
|
|
||||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
|
|
||||||
with:
|
|
||||||
comment-id: ${{ steps.find-update-comment.outputs.comment-id }}
|
|
||||||
issue-number: ${{ steps.pr-info.outputs.pr-number }}
|
|
||||||
body: |
|
|
||||||
Updating Playwright Expectations
|
|
||||||
edit-mode: replace
|
|
||||||
reactions: eyes
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
ref: ${{ steps.pr-info.outputs.branch }}
|
|
||||||
- name: Setup frontend
|
|
||||||
uses: ./.github/actions/setup-frontend
|
|
||||||
with:
|
|
||||||
include_build_step: true
|
|
||||||
# Save expensive build artifacts (Python env, built frontend, node_modules)
|
|
||||||
# Source code will be checked out fresh in sharded jobs
|
|
||||||
- name: Generate cache key
|
|
||||||
id: cache-key
|
|
||||||
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
|
|
||||||
- name: Save cache
|
|
||||||
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
ComfyUI
|
|
||||||
dist
|
|
||||||
key: comfyui-setup-${{ steps.cache-key.outputs.key }}
|
|
||||||
|
|
||||||
# Sharded snapshot updates
|
|
||||||
update-snapshots-sharded:
|
|
||||||
needs: setup
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
shardIndex: [1, 2, 3, 4]
|
|
||||||
shardTotal: [4]
|
|
||||||
steps:
|
|
||||||
# Checkout source code fresh (not cached)
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
ref: ${{ needs.setup.outputs.branch }}
|
|
||||||
|
|
||||||
# Restore expensive build artifacts from setup job
|
|
||||||
- name: Restore cached artifacts
|
|
||||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
|
||||||
with:
|
|
||||||
fail-on-cache-miss: true
|
|
||||||
path: |
|
|
||||||
ComfyUI
|
|
||||||
dist
|
|
||||||
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
|
|
||||||
|
|
||||||
- name: Setup ComfyUI server (from cache)
|
|
||||||
uses: ./.github/actions/setup-comfyui-server
|
|
||||||
with:
|
|
||||||
launch_server: true
|
|
||||||
|
|
||||||
- name: Setup nodejs, pnpm, reuse built frontend
|
|
||||||
uses: ./.github/actions/setup-frontend
|
|
||||||
|
|
||||||
- name: Setup Playwright
|
|
||||||
uses: ./.github/actions/setup-playwright
|
|
||||||
|
|
||||||
# Run sharded tests with snapshot updates
|
|
||||||
- name: Update snapshots (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
|
|
||||||
id: playwright-tests
|
|
||||||
run: pnpm exec playwright test --update-snapshots --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
# Identify and stage only changed snapshot files
|
|
||||||
- name: Stage changed snapshot files
|
|
||||||
id: changed-snapshots
|
|
||||||
run: |
|
|
||||||
echo "=========================================="
|
|
||||||
echo "STAGING CHANGED SNAPSHOTS (Shard ${{ matrix.shardIndex }})"
|
|
||||||
echo "=========================================="
|
|
||||||
|
|
||||||
# Get list of changed snapshot files
|
|
||||||
changed_files=$(git diff --name-only browser_tests/ 2>/dev/null | grep -E '\-snapshots/' || echo "")
|
|
||||||
|
|
||||||
if [ -z "$changed_files" ]; then
|
|
||||||
echo "No snapshot changes in this shard"
|
|
||||||
echo "has-changes=false" >> $GITHUB_OUTPUT
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✓ Found changed files:"
|
|
||||||
echo "$changed_files"
|
|
||||||
file_count=$(echo "$changed_files" | wc -l)
|
|
||||||
echo "Count: $file_count"
|
|
||||||
echo "has-changes=true" >> $GITHUB_OUTPUT
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Create staging directory
|
|
||||||
mkdir -p /tmp/changed_snapshots_shard
|
|
||||||
|
|
||||||
# Copy only changed files, preserving directory structure
|
|
||||||
# Strip 'browser_tests/' prefix to avoid double nesting
|
|
||||||
echo "Copying changed files to staging directory..."
|
|
||||||
while IFS= read -r file; do
|
|
||||||
# Remove 'browser_tests/' prefix
|
|
||||||
file_without_prefix="${file#browser_tests/}"
|
|
||||||
# Create parent directories
|
|
||||||
mkdir -p "/tmp/changed_snapshots_shard/$(dirname "$file_without_prefix")"
|
|
||||||
# Copy file
|
|
||||||
cp "$file" "/tmp/changed_snapshots_shard/$file_without_prefix"
|
|
||||||
echo " → $file_without_prefix"
|
|
||||||
done <<< "$changed_files"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Staged files for upload:"
|
|
||||||
find /tmp/changed_snapshots_shard -type f
|
|
||||||
|
|
||||||
# Upload ONLY the changed files from this shard
|
|
||||||
- name: Upload changed snapshots
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: steps.changed-snapshots.outputs.has-changes == 'true'
|
|
||||||
with:
|
|
||||||
name: snapshots-shard-${{ matrix.shardIndex }}
|
|
||||||
path: /tmp/changed_snapshots_shard/
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
- name: Upload test report
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
name: playwright-report-shard-${{ matrix.shardIndex }}
|
|
||||||
path: ./playwright-report/
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
# Merge snapshots and commit
|
|
||||||
merge-and-commit:
|
|
||||||
needs: [setup, update-snapshots-sharded]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
ref: ${{ needs.setup.outputs.branch }}
|
|
||||||
|
|
||||||
# Download all changed snapshot files from shards
|
|
||||||
- name: Download snapshot artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
pattern: snapshots-shard-*
|
|
||||||
path: ./downloaded-snapshots
|
|
||||||
merge-multiple: false
|
|
||||||
|
|
||||||
- name: List downloaded files
|
|
||||||
run: |
|
|
||||||
echo "=========================================="
|
|
||||||
echo "DOWNLOADED SNAPSHOT FILES"
|
|
||||||
echo "=========================================="
|
|
||||||
find ./downloaded-snapshots -type f
|
|
||||||
echo ""
|
|
||||||
echo "Total files: $(find ./downloaded-snapshots -type f | wc -l)"
|
|
||||||
|
|
||||||
# Merge only the changed files into browser_tests
|
|
||||||
- name: Merge changed snapshots
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "MERGING CHANGED SNAPSHOTS"
|
|
||||||
echo "=========================================="
|
|
||||||
|
|
||||||
# Verify target directory exists
|
|
||||||
if [ ! -d "browser_tests" ]; then
|
|
||||||
echo "::error::Target directory 'browser_tests' does not exist"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
merged_count=0
|
|
||||||
|
|
||||||
# For each shard's changed files, copy them directly
|
|
||||||
for shard_dir in ./downloaded-snapshots/snapshots-shard-*/; do
|
|
||||||
if [ ! -d "$shard_dir" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
shard_name=$(basename "$shard_dir")
|
|
||||||
file_count=$(find "$shard_dir" -type f | wc -l)
|
|
||||||
|
|
||||||
if [ "$file_count" -eq 0 ]; then
|
|
||||||
echo " $shard_name: no files"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Processing $shard_name ($file_count file(s))..."
|
|
||||||
|
|
||||||
# Copy files directly, preserving directory structure
|
|
||||||
# Since files are already in correct structure (no browser_tests/ prefix), just copy them all
|
|
||||||
cp -v -r "$shard_dir"* browser_tests/ 2>&1 | sed 's/^/ /'
|
|
||||||
|
|
||||||
merged_count=$((merged_count + 1))
|
|
||||||
echo " ✓ Merged"
|
|
||||||
echo ""
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "MERGE COMPLETE"
|
|
||||||
echo "=========================================="
|
|
||||||
echo "Shards merged: $merged_count"
|
|
||||||
|
|
||||||
- name: Show changes
|
|
||||||
run: |
|
|
||||||
echo "=========================================="
|
|
||||||
echo "CHANGES SUMMARY"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
echo "Changed files in browser_tests:"
|
|
||||||
git diff --name-only browser_tests/ | head -20 || echo "No changes"
|
|
||||||
echo ""
|
|
||||||
echo "Total changes:"
|
|
||||||
git diff --name-only browser_tests/ | wc -l || echo "0"
|
|
||||||
|
|
||||||
- name: Commit updated expectations
|
|
||||||
id: commit
|
|
||||||
run: |
|
|
||||||
git config --global user.name 'github-actions'
|
|
||||||
git config --global user.email 'github-actions@github.com'
|
|
||||||
|
|
||||||
if git diff --quiet browser_tests/; then
|
|
||||||
echo "No changes to commit"
|
|
||||||
echo "has-changes=false" >> $GITHUB_OUTPUT
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "COMMITTING CHANGES"
|
|
||||||
echo "=========================================="
|
|
||||||
|
|
||||||
echo "has-changes=true" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
git add browser_tests/
|
|
||||||
git commit -m "[automated] Update test expectations"
|
|
||||||
|
|
||||||
echo "Pushing to ${{ needs.setup.outputs.branch }}..."
|
|
||||||
git push origin ${{ needs.setup.outputs.branch }}
|
|
||||||
|
|
||||||
echo "✓ Commit and push successful"
|
|
||||||
|
|
||||||
- name: Add Done Reaction
|
|
||||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9
|
|
||||||
if: github.event_name == 'issue_comment' && steps.commit.outputs.has-changes == 'true'
|
|
||||||
with:
|
|
||||||
comment-id: ${{ needs.setup.outputs.comment-id }}
|
|
||||||
issue-number: ${{ needs.setup.outputs.pr-number }}
|
|
||||||
reactions: +1
|
|
||||||
reactions-edit-mode: replace
|
|
||||||
|
|
||||||
- name: Remove New Browser Test Expectations label
|
|
||||||
if: always() && github.event_name == 'pull_request'
|
|
||||||
run: gh pr edit ${{ needs.setup.outputs.pr-number }} --remove-label "New Browser Test Expectations"
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
---
|
|
||||||
name: Publish Desktop UI on PR Merge
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: ['closed']
|
|
||||||
branches: [main, core/*]
|
|
||||||
paths:
|
|
||||||
- 'apps/desktop-ui/package.json'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
resolve:
|
|
||||||
name: Resolve Version and Dist Tag
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: >
|
|
||||||
github.event.pull_request.merged == true &&
|
|
||||||
contains(github.event.pull_request.labels.*.name, 'Release')
|
|
||||||
outputs:
|
|
||||||
version: ${{ steps.get_version.outputs.version }}
|
|
||||||
dist_tag: ${{ steps.dist.outputs.dist_tag }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v5
|
|
||||||
with:
|
|
||||||
node-version: '24.x'
|
|
||||||
|
|
||||||
- name: Read desktop-ui version
|
|
||||||
id: get_version
|
|
||||||
run: |
|
|
||||||
VERSION=$(node -p "require('./apps/desktop-ui/package.json').version")
|
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Determine dist-tag
|
|
||||||
id: dist
|
|
||||||
env:
|
|
||||||
VERSION: ${{ steps.get_version.outputs.version }}
|
|
||||||
run: |
|
|
||||||
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+- ]]; then
|
|
||||||
echo "dist_tag=next" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "dist_tag=latest" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
publish:
|
|
||||||
name: Publish Desktop UI to npm
|
|
||||||
needs: resolve
|
|
||||||
uses: ./.github/workflows/publish-desktop-ui.yaml
|
|
||||||
with:
|
|
||||||
version: ${{ needs.resolve.outputs.version }}
|
|
||||||
dist_tag: ${{ needs.resolve.outputs.dist_tag }}
|
|
||||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
|
||||||
secrets:
|
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
comment_desktop_publish:
|
|
||||||
name: Comment Desktop Publish Summary
|
|
||||||
needs:
|
|
||||||
- resolve
|
|
||||||
- publish
|
|
||||||
if: success()
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout merge commit
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Post desktop release summary comment
|
|
||||||
uses: ./.github/actions/comment-release-links
|
|
||||||
with:
|
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
|
||||||
version_file: apps/desktop-ui/package.json
|
|
||||||
192
.github/workflows/publish-desktop-ui.yaml
vendored
192
.github/workflows/publish-desktop-ui.yaml
vendored
@@ -1,192 +0,0 @@
|
|||||||
name: Publish Desktop UI
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: 'Version to publish (e.g., 1.2.3)'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
dist_tag:
|
|
||||||
description: 'npm dist-tag to use'
|
|
||||||
required: true
|
|
||||||
default: latest
|
|
||||||
type: string
|
|
||||||
ref:
|
|
||||||
description: 'Git ref to checkout (commit SHA, tag, or branch)'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
dist_tag:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: latest
|
|
||||||
ref:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
secrets:
|
|
||||||
NPM_TOKEN:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: publish-desktop-ui-${{ github.workflow }}-${{ inputs.version }}-${{ inputs.dist_tag }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish_desktop_ui:
|
|
||||||
name: Publish @comfyorg/desktop-ui
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
env:
|
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
|
||||||
ENABLE_MINIFY: 'true'
|
|
||||||
steps:
|
|
||||||
- name: Validate inputs
|
|
||||||
env:
|
|
||||||
VERSION: ${{ inputs.version }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
SEMVER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$'
|
|
||||||
if [[ ! "$VERSION" =~ $SEMVER_REGEX ]]; then
|
|
||||||
echo "::error title=Invalid version::Version '$VERSION' must follow semantic versioning (x.y.z[-suffix][+build])" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Determine ref to checkout
|
|
||||||
id: resolve_ref
|
|
||||||
env:
|
|
||||||
REF: ${{ inputs.ref }}
|
|
||||||
VERSION: ${{ inputs.version }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
if [ -n "$REF" ]; then
|
|
||||||
if ! git check-ref-format --allow-onelevel "$REF"; then
|
|
||||||
echo "::error title=Invalid ref::Ref '$REF' fails git check-ref-format validation." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "ref=$REF" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "ref=refs/tags/v$VERSION" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
ref: ${{ steps.resolve_ref.outputs.ref }}
|
|
||||||
fetch-depth: 1
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v5
|
|
||||||
with:
|
|
||||||
node-version: '24.x'
|
|
||||||
cache: 'pnpm'
|
|
||||||
registry-url: https://registry.npmjs.org
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile --ignore-scripts
|
|
||||||
|
|
||||||
- name: Build Desktop UI
|
|
||||||
run: pnpm build:desktop
|
|
||||||
|
|
||||||
- name: Prepare npm package
|
|
||||||
id: pkg
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
APP_PKG=apps/desktop-ui/package.json
|
|
||||||
ROOT_PKG=package.json
|
|
||||||
|
|
||||||
NAME=$(jq -r .name "$APP_PKG")
|
|
||||||
APP_VERSION=$(jq -r .version "$APP_PKG")
|
|
||||||
ROOT_LICENSE=$(jq -r .license "$ROOT_PKG")
|
|
||||||
REPO=$(jq -r .repository "$ROOT_PKG")
|
|
||||||
|
|
||||||
if [ -z "$NAME" ] || [ "$NAME" = "null" ]; then
|
|
||||||
echo "::error title=Missing name::apps/desktop-ui/package.json is missing 'name'" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
INPUT_VERSION="${{ inputs.version }}"
|
|
||||||
if [ "$APP_VERSION" != "$INPUT_VERSION" ]; then
|
|
||||||
echo "::error title=Version mismatch::apps/desktop-ui version $APP_VERSION does not match input $INPUT_VERSION" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -d apps/desktop-ui/dist ]; then
|
|
||||||
echo "::error title=Missing build::apps/desktop-ui/dist not found. Did build succeed?" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PUBLISH_DIR=apps/desktop-ui/.npm-publish
|
|
||||||
rm -rf "$PUBLISH_DIR"
|
|
||||||
mkdir -p "$PUBLISH_DIR"
|
|
||||||
cp -R apps/desktop-ui/dist "$PUBLISH_DIR/dist"
|
|
||||||
|
|
||||||
INPUT_VERSION="${{ inputs.version }}"
|
|
||||||
jq -n \
|
|
||||||
--arg name "$NAME" \
|
|
||||||
--arg version "$INPUT_VERSION" \
|
|
||||||
--arg description "Static assets for the ComfyUI Desktop UI" \
|
|
||||||
--arg license "$ROOT_LICENSE" \
|
|
||||||
--arg repository "$REPO" \
|
|
||||||
'{
|
|
||||||
name: $name,
|
|
||||||
version: $version,
|
|
||||||
description: $description,
|
|
||||||
license: $license,
|
|
||||||
repository: $repository,
|
|
||||||
type: "module",
|
|
||||||
private: false,
|
|
||||||
files: ["dist"],
|
|
||||||
publishConfig: { access: "public" }
|
|
||||||
}' > "$PUBLISH_DIR/package.json"
|
|
||||||
|
|
||||||
if [ -f apps/desktop-ui/README.md ]; then
|
|
||||||
cp apps/desktop-ui/README.md "$PUBLISH_DIR/README.md"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "publish_dir=$PUBLISH_DIR" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "name=$NAME" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Check if version already on npm
|
|
||||||
id: check_npm
|
|
||||||
env:
|
|
||||||
NAME: ${{ steps.pkg.outputs.name }}
|
|
||||||
VER: ${{ inputs.version }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
STATUS=0
|
|
||||||
OUTPUT=$(npm view "${NAME}@${VER}" --json 2>&1) || STATUS=$?
|
|
||||||
if [ "$STATUS" -eq 0 ]; then
|
|
||||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "::warning title=Already published::${NAME}@${VER} already exists on npm. Skipping publish."
|
|
||||||
else
|
|
||||||
if echo "$OUTPUT" | grep -q "E404"; then
|
|
||||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "::error title=Registry lookup failed::$OUTPUT" >&2
|
|
||||||
exit "$STATUS"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Publish package
|
|
||||||
if: steps.check_npm.outputs.exists == 'false'
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
DIST_TAG: ${{ inputs.dist_tag }}
|
|
||||||
run: pnpm publish --access public --tag "$DIST_TAG" --no-git-checks --ignore-scripts
|
|
||||||
working-directory: ${{ steps.pkg.outputs.publish_dir }}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Release NPM Types
|
name: Publish Frontend Types
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
281
.github/workflows/release-branch-create.yaml
vendored
281
.github/workflows/release-branch-create.yaml
vendored
@@ -1,281 +0,0 @@
|
|||||||
name: Release Branch Create
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [closed]
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- 'package.json'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
create-release-branch:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: >
|
|
||||||
github.event.pull_request.merged == true &&
|
|
||||||
contains(github.event.pull_request.labels.*.name, 'Release')
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 'lts/*'
|
|
||||||
|
|
||||||
- name: Check version bump type
|
|
||||||
id: check_version
|
|
||||||
run: |
|
|
||||||
# Get current version from main
|
|
||||||
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
|
||||||
# Remove 'v' prefix if present (shouldn't be in package.json, but defensive)
|
|
||||||
CURRENT_VERSION=${CURRENT_VERSION#v}
|
|
||||||
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Validate version format
|
|
||||||
if ! [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
|
||||||
echo "ERROR: Invalid version format: $CURRENT_VERSION"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract major and minor versions
|
|
||||||
MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1)
|
|
||||||
MINOR=$(echo $CURRENT_VERSION | cut -d. -f2)
|
|
||||||
PATCH=$(echo $CURRENT_VERSION | cut -d. -f3 | cut -d- -f1)
|
|
||||||
|
|
||||||
echo "major=$MAJOR" >> $GITHUB_OUTPUT
|
|
||||||
echo "minor=$MINOR" >> $GITHUB_OUTPUT
|
|
||||||
echo "patch=$PATCH" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Get previous version from the commit before the merge
|
|
||||||
git checkout HEAD^1
|
|
||||||
PREV_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")
|
|
||||||
# Remove 'v' prefix if present
|
|
||||||
PREV_VERSION=${PREV_VERSION#v}
|
|
||||||
|
|
||||||
# Validate previous version format
|
|
||||||
if ! [[ "$PREV_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
|
||||||
echo "WARNING: Invalid previous version format: $PREV_VERSION, using 0.0.0"
|
|
||||||
PREV_VERSION="0.0.0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
PREV_MINOR=$(echo $PREV_VERSION | cut -d. -f2)
|
|
||||||
|
|
||||||
echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT
|
|
||||||
echo "prev_minor=$PREV_MINOR" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
BASE_COMMIT=$(git rev-parse HEAD)
|
|
||||||
echo "base_commit=$BASE_COMMIT" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Get previous major version for comparison
|
|
||||||
PREV_MAJOR=$(echo $PREV_VERSION | cut -d. -f1)
|
|
||||||
|
|
||||||
# Check if current version is a pre-release
|
|
||||||
if [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+- ]]; then
|
|
||||||
IS_PRERELEASE=true
|
|
||||||
else
|
|
||||||
IS_PRERELEASE=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if this was a minor version bump or major version bump
|
|
||||||
# But skip if it's a pre-release version
|
|
||||||
if [[ "$IS_PRERELEASE" == "true" ]]; then
|
|
||||||
echo "is_minor_bump=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "reason=prerelease version" >> $GITHUB_OUTPUT
|
|
||||||
elif [[ "$MAJOR" -gt "$PREV_MAJOR" && "$MINOR" == "0" && "$PATCH" == "0" ]]; then
|
|
||||||
# Major version bump (e.g., 1.99.x → 2.0.0)
|
|
||||||
echo "is_minor_bump=true" >> $GITHUB_OUTPUT
|
|
||||||
BRANCH_BASE="${PREV_MAJOR}.${PREV_MINOR}"
|
|
||||||
echo "branch_base=$BRANCH_BASE" >> $GITHUB_OUTPUT
|
|
||||||
elif [[ "$MAJOR" == "$PREV_MAJOR" && "$MINOR" -gt "$PREV_MINOR" && "$PATCH" == "0" ]]; then
|
|
||||||
# Minor version bump (e.g., 1.23.x → 1.24.0)
|
|
||||||
echo "is_minor_bump=true" >> $GITHUB_OUTPUT
|
|
||||||
BRANCH_BASE="${MAJOR}.${PREV_MINOR}"
|
|
||||||
echo "branch_base=$BRANCH_BASE" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "is_minor_bump=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Return to main branch
|
|
||||||
git checkout main
|
|
||||||
|
|
||||||
- name: Create release branches
|
|
||||||
id: create_branches
|
|
||||||
if: steps.check_version.outputs.is_minor_bump == 'true'
|
|
||||||
run: |
|
|
||||||
BRANCH_BASE="${{ steps.check_version.outputs.branch_base }}"
|
|
||||||
PREV_VERSION="${{ steps.check_version.outputs.prev_version }}"
|
|
||||||
|
|
||||||
if [[ -z "$BRANCH_BASE" ]]; then
|
|
||||||
echo "::error::Branch base not set; unable to determine release branches"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BASE_COMMIT="${{ steps.check_version.outputs.base_commit }}"
|
|
||||||
|
|
||||||
if [[ -z "$BASE_COMMIT" ]]; then
|
|
||||||
echo "::error::Base commit not provided; cannot create release branches"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
RESULTS_FILE=$(mktemp)
|
|
||||||
trap 'rm -f "$RESULTS_FILE"' EXIT
|
|
||||||
|
|
||||||
for PREFIX in core cloud; do
|
|
||||||
BRANCH_NAME="${PREFIX}/${BRANCH_BASE}"
|
|
||||||
|
|
||||||
if git ls-remote --exit-code --heads origin \
|
|
||||||
"$BRANCH_NAME" >/dev/null 2>&1; then
|
|
||||||
echo "⚠️ Branch $BRANCH_NAME already exists"
|
|
||||||
echo "ℹ️ Skipping creation for $BRANCH_NAME"
|
|
||||||
STATUS="exists"
|
|
||||||
else
|
|
||||||
# Create branch from the commit BEFORE the version bump
|
|
||||||
if ! git push origin "$BASE_COMMIT:refs/heads/$BRANCH_NAME"; then
|
|
||||||
echo "::error::Failed to push release branch $BRANCH_NAME"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✅ Created release branch: $BRANCH_NAME"
|
|
||||||
STATUS="created"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$BRANCH_NAME|$STATUS|$PREV_VERSION" >> "$RESULTS_FILE"
|
|
||||||
done
|
|
||||||
|
|
||||||
{
|
|
||||||
echo "results<<'EOF'"
|
|
||||||
cat "$RESULTS_FILE"
|
|
||||||
echo "EOF"
|
|
||||||
} >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Ensure release labels
|
|
||||||
if: steps.check_version.outputs.is_minor_bump == 'true'
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
BRANCH_BASE="${{ steps.check_version.outputs.branch_base }}"
|
|
||||||
|
|
||||||
if [[ -z "$BRANCH_BASE" ]]; then
|
|
||||||
echo "::error::Branch base not set; unable to manage labels"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
declare -A COLORS=(
|
|
||||||
[core]="4361ee"
|
|
||||||
[cloud]="4f6ef5"
|
|
||||||
)
|
|
||||||
|
|
||||||
for PREFIX in core cloud; do
|
|
||||||
LABEL="${PREFIX}/${BRANCH_BASE}"
|
|
||||||
COLOR="${COLORS[$PREFIX]}"
|
|
||||||
DESCRIPTION="Backport PRs for ${PREFIX} ${BRANCH_BASE}"
|
|
||||||
|
|
||||||
if gh label view "$LABEL" >/dev/null 2>&1; then
|
|
||||||
gh label edit "$LABEL" \
|
|
||||||
--color "$COLOR" \
|
|
||||||
--description "$DESCRIPTION"
|
|
||||||
echo "🔄 Updated label $LABEL"
|
|
||||||
else
|
|
||||||
gh label create "$LABEL" \
|
|
||||||
--color "$COLOR" \
|
|
||||||
--description "$DESCRIPTION"
|
|
||||||
echo "✨ Created label $LABEL"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
MIN_LABELS_TO_KEEP=3
|
|
||||||
MAX_LABELS_TO_FETCH=200
|
|
||||||
|
|
||||||
for PREFIX in core cloud; do
|
|
||||||
mapfile -t LABELS < <(
|
|
||||||
gh label list \
|
|
||||||
--json name \
|
|
||||||
--limit "$MAX_LABELS_TO_FETCH" \
|
|
||||||
--jq '.[].name' |
|
|
||||||
grep -E "^${PREFIX}/[0-9]+\.[0-9]+$" |
|
|
||||||
sort -t/ -k2,2V
|
|
||||||
)
|
|
||||||
|
|
||||||
TOTAL=${#LABELS[@]}
|
|
||||||
|
|
||||||
if (( TOTAL <= MIN_LABELS_TO_KEEP )); then
|
|
||||||
echo "ℹ️ Nothing to prune for $PREFIX labels"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
REMOVE_COUNT=$((TOTAL - MIN_LABELS_TO_KEEP))
|
|
||||||
|
|
||||||
if (( REMOVE_COUNT > 1 )); then
|
|
||||||
REMOVE_COUNT=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for ((i=0; i<REMOVE_COUNT; i++)); do
|
|
||||||
OLD_LABEL="${LABELS[$i]}"
|
|
||||||
gh label delete "$OLD_LABEL" --yes
|
|
||||||
echo "🗑️ Removed old label $OLD_LABEL"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Post summary
|
|
||||||
if: always() && steps.check_version.outputs.is_minor_bump == 'true'
|
|
||||||
run: |
|
|
||||||
CURRENT_VERSION="${{ steps.check_version.outputs.current_version }}"
|
|
||||||
RESULTS="${{ steps.create_branches.outputs.results }}"
|
|
||||||
|
|
||||||
if [[ -z "$RESULTS" ]]; then
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
|
||||||
## 🌿 Release Branch Summary
|
|
||||||
|
|
||||||
Release branch creation skipped; no eligible branches were found.
|
|
||||||
EOF
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
|
||||||
## 🌿 Release Branch Summary
|
|
||||||
|
|
||||||
- **Main branch**: \`$CURRENT_VERSION\` (active development)
|
|
||||||
|
|
||||||
### Branch Status
|
|
||||||
EOF
|
|
||||||
|
|
||||||
while IFS='|' read -r BRANCH STATUS PREV_VERSION; do
|
|
||||||
if [[ "$STATUS" == "created" ]]; then
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
|
||||||
|
|
||||||
- \`$BRANCH\` created from version \`$PREV_VERSION\`
|
|
||||||
EOF
|
|
||||||
else
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
|
||||||
|
|
||||||
- \`$BRANCH\` already existed (based on version \`$PREV_VERSION\`)
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
done <<< "$RESULTS"
|
|
||||||
|
|
||||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
|
||||||
|
|
||||||
### Branch Policy
|
|
||||||
|
|
||||||
Release branches are feature-frozen and only accept:
|
|
||||||
- 🐛 Bug fixes
|
|
||||||
- 🔒 Security patches
|
|
||||||
- 📚 Documentation updates
|
|
||||||
|
|
||||||
All new features should continue to be developed against \`main\`.
|
|
||||||
|
|
||||||
### Backporting Changes
|
|
||||||
|
|
||||||
To backport a fix:
|
|
||||||
1. Create your fix on \`main\` first
|
|
||||||
2. Cherry-pick to the target release branch
|
|
||||||
3. Create a PR targeting that branch
|
|
||||||
4. Apply the matching \`core/x.y\` or \`cloud/x.y\` label
|
|
||||||
EOF
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
---
|
name: Create Release Draft
|
||||||
name: Release Draft Create
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: ['closed']
|
types: [ closed ]
|
||||||
branches: [main, core/*]
|
branches: [ main, core/* ]
|
||||||
paths:
|
paths:
|
||||||
- 'package.json'
|
- 'package.json'
|
||||||
|
|
||||||
@@ -29,11 +28,19 @@ jobs:
|
|||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.cache
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
key: release-tools-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
release-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Get current version
|
- name: Get current version
|
||||||
id: current_version
|
id: current_version
|
||||||
run: |
|
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||||
VERSION=$(node -p "require('./package.json').version")
|
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
||||||
- name: Check if prerelease
|
- name: Check if prerelease
|
||||||
id: check_prerelease
|
id: check_prerelease
|
||||||
run: |
|
run: |
|
||||||
@@ -48,7 +55,6 @@ jobs:
|
|||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
||||||
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
||||||
ENABLE_MINIFY: 'true'
|
|
||||||
USE_PROD_CONFIG: 'true'
|
USE_PROD_CONFIG: 'true'
|
||||||
run: |
|
run: |
|
||||||
pnpm install --frozen-lockfile
|
pnpm install --frozen-lockfile
|
||||||
@@ -74,8 +80,7 @@ jobs:
|
|||||||
name: dist-files
|
name: dist-files
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: >-
|
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||||
softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@@ -83,14 +88,9 @@ jobs:
|
|||||||
dist.zip
|
dist.zip
|
||||||
tag_name: v${{ needs.build.outputs.version }}
|
tag_name: v${{ needs.build.outputs.version }}
|
||||||
target_commitish: ${{ github.event.pull_request.base.ref }}
|
target_commitish: ${{ github.event.pull_request.base.ref }}
|
||||||
make_latest: >-
|
make_latest: ${{ github.event.pull_request.base.ref == 'main' && needs.build.outputs.is_prerelease == 'false' }}
|
||||||
${{ github.event.pull_request.base.ref == 'main' &&
|
draft: ${{ github.event.pull_request.base.ref != 'main' || needs.build.outputs.is_prerelease == 'true' }}
|
||||||
needs.build.outputs.is_prerelease == 'false' }}
|
prerelease: ${{ needs.build.outputs.is_prerelease == 'true' }}
|
||||||
draft: >-
|
|
||||||
${{ github.event.pull_request.base.ref != 'main' ||
|
|
||||||
needs.build.outputs.is_prerelease == 'true' }}
|
|
||||||
prerelease: >-
|
|
||||||
${{ needs.build.outputs.is_prerelease == 'true' }}
|
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|
||||||
publish_pypi:
|
publish_pypi:
|
||||||
@@ -119,41 +119,15 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
COMFYUI_FRONTEND_VERSION: ${{ needs.build.outputs.version }}
|
COMFYUI_FRONTEND_VERSION: ${{ needs.build.outputs.version }}
|
||||||
- name: Publish pypi package
|
- name: Publish pypi package
|
||||||
uses: >-
|
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
||||||
pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
|
||||||
with:
|
with:
|
||||||
password: ${{ secrets.PYPI_TOKEN }}
|
password: ${{ secrets.PYPI_TOKEN }}
|
||||||
packages-dir: comfyui_frontend_package/dist
|
packages-dir: comfyui_frontend_package/dist
|
||||||
|
|
||||||
publish_types:
|
publish_types:
|
||||||
needs: build
|
needs: build
|
||||||
uses: ./.github/workflows/release-npm-types.yaml
|
uses: ./.github/workflows/publish-frontend-types.yaml
|
||||||
with:
|
with:
|
||||||
version: ${{ needs.build.outputs.version }}
|
version: ${{ needs.build.outputs.version }}
|
||||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
comment_release_summary:
|
|
||||||
name: Comment Release Summary
|
|
||||||
needs:
|
|
||||||
- draft_release
|
|
||||||
- publish_pypi
|
|
||||||
- publish_types
|
|
||||||
if: success()
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout merge commit
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Post release summary comment
|
|
||||||
uses: ./.github/actions/comment-release-links
|
|
||||||
with:
|
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
|
||||||
version_file: package.json
|
|
||||||
49
.github/workflows/test-browser-exp.yaml
vendored
Normal file
49
.github/workflows/test-browser-exp.yaml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Setting test expectation screenshots for Playwright
|
||||||
|
name: Update Playwright Expectations
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [ labeled ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event.label.name == 'New Browser Test Expectations'
|
||||||
|
steps:
|
||||||
|
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
|
||||||
|
- name: Cache Playwright browsers
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cache/ms-playwright
|
||||||
|
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
playwright-browsers-${{ runner.os }}-
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: npx playwright install chromium --with-deps
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Run Playwright tests and update snapshots
|
||||||
|
id: playwright-tests
|
||||||
|
run: npx playwright test --update-snapshots
|
||||||
|
continue-on-error: true
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: ComfyUI_frontend/playwright-report/
|
||||||
|
retention-days: 30
|
||||||
|
- name: Debugging info
|
||||||
|
run: |
|
||||||
|
echo "Branch: ${{ github.head_ref }}"
|
||||||
|
git status
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
- name: Commit updated expectations
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'github-actions'
|
||||||
|
git config --global user.email 'github-actions@github.com'
|
||||||
|
git fetch origin ${{ github.head_ref }}
|
||||||
|
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
|
||||||
|
git add browser_tests
|
||||||
|
git commit -m "Update test expectations [skip ci]"
|
||||||
|
git push origin HEAD:${{ github.head_ref }}
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
359
.github/workflows/test-ui.yaml
vendored
Normal file
359
.github/workflows/test-ui.yaml
vendored
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
name: Tests CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master, core/*, desktop/*]
|
||||||
|
pull_request:
|
||||||
|
branches-ignore:
|
||||||
|
[wip/*, draft/*, temp/*, vue-nodes-migration, sno-playwright-*]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||||
|
playwright-version: ${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout ComfyUI
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: 'comfyanonymous/ComfyUI'
|
||||||
|
path: 'ComfyUI'
|
||||||
|
ref: master
|
||||||
|
|
||||||
|
- name: Checkout ComfyUI_frontend
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: 'Comfy-Org/ComfyUI_frontend'
|
||||||
|
path: 'ComfyUI_frontend'
|
||||||
|
|
||||||
|
- name: Checkout ComfyUI_devtools
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: 'Comfy-Org/ComfyUI_devtools'
|
||||||
|
path: 'ComfyUI/custom_nodes/ComfyUI_devtools'
|
||||||
|
ref: 'd05fd48dd787a4192e16802d4244cfcc0e2f9684'
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: 'ComfyUI_frontend/pnpm-lock.yaml'
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
ComfyUI_frontend/.cache
|
||||||
|
ComfyUI_frontend/tsconfig.tsbuildinfo
|
||||||
|
key: playwright-setup-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}-${{ hashFiles('ComfyUI_frontend/src/**/*.{ts,vue,js}', 'ComfyUI_frontend/*.config.*') }}
|
||||||
|
restore-keys: |
|
||||||
|
playwright-setup-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}-
|
||||||
|
playwright-setup-cache-${{ runner.os }}-
|
||||||
|
playwright-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Build ComfyUI_frontend
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm build
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
|
- name: Generate cache key
|
||||||
|
id: cache-key
|
||||||
|
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Playwright Version
|
||||||
|
id: playwright-version
|
||||||
|
run: |
|
||||||
|
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --json | jq --raw-output '.[0].devDependencies["@playwright/test"].version')
|
||||||
|
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
|
- name: Save cache
|
||||||
|
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
ComfyUI
|
||||||
|
ComfyUI_frontend
|
||||||
|
key: comfyui-setup-${{ steps.cache-key.outputs.key }}
|
||||||
|
|
||||||
|
# Sharded chromium tests
|
||||||
|
playwright-tests-chromium-sharded:
|
||||||
|
needs: setup
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||||
|
shardTotal: [8]
|
||||||
|
steps:
|
||||||
|
- name: Wait for cache propagation
|
||||||
|
run: sleep 10
|
||||||
|
|
||||||
|
- name: Restore cached setup
|
||||||
|
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
||||||
|
with:
|
||||||
|
fail-on-cache-miss: true
|
||||||
|
path: |
|
||||||
|
ComfyUI
|
||||||
|
ComfyUI_frontend
|
||||||
|
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install wait-for-it
|
||||||
|
working-directory: ComfyUI
|
||||||
|
|
||||||
|
|
||||||
|
- name: Cache Playwright Browsers
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: cache-playwright-browsers
|
||||||
|
with:
|
||||||
|
path: '~/.cache/ms-playwright'
|
||||||
|
key: '${{ runner.os }}-playwright-browsers-${{ needs.setup.outputs.playwright-version }}'
|
||||||
|
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
|
||||||
|
run: pnpm exec playwright install chromium --with-deps
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
|
- name: Install Playwright Browsers (operating system dependencies)
|
||||||
|
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
|
||||||
|
run: pnpm exec playwright install-deps
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
|
- name: Start ComfyUI server
|
||||||
|
run: |
|
||||||
|
python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist &
|
||||||
|
wait-for-it --service 127.0.0.1:8188 -t 600
|
||||||
|
working-directory: ComfyUI
|
||||||
|
|
||||||
|
- name: Run Playwright tests (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
|
||||||
|
id: playwright
|
||||||
|
run: npx playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
env:
|
||||||
|
PLAYWRIGHT_BLOB_OUTPUT_DIR: ../blob-report
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
with:
|
||||||
|
name: blob-report-chromium-${{ matrix.shardIndex }}
|
||||||
|
path: blob-report/
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
playwright-tests:
|
||||||
|
# Ideally, each shard runs test in 6 minutes, but allow up to 15 minutes
|
||||||
|
timeout-minutes: 15
|
||||||
|
needs: setup
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
browser: [chromium-2x, chromium-0.5x, mobile-chrome]
|
||||||
|
steps:
|
||||||
|
- name: Wait for cache propagation
|
||||||
|
run: sleep 10
|
||||||
|
|
||||||
|
- name: Restore cached setup
|
||||||
|
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
||||||
|
with:
|
||||||
|
fail-on-cache-miss: true
|
||||||
|
path: |
|
||||||
|
ComfyUI
|
||||||
|
ComfyUI_frontend
|
||||||
|
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
- name: Install requirements
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install wait-for-it
|
||||||
|
working-directory: ComfyUI
|
||||||
|
|
||||||
|
- name: Cache Playwright Browsers
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: cache-playwright-browsers
|
||||||
|
with:
|
||||||
|
path: '~/.cache/ms-playwright'
|
||||||
|
key: '${{ runner.os }}-playwright-browsers-${{ needs.setup.outputs.playwright-version }}'
|
||||||
|
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
|
||||||
|
run: pnpm exec playwright install chromium --with-deps
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
|
- name: Install Playwright Browsers (operating system dependencies)
|
||||||
|
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
|
||||||
|
run: pnpm exec playwright install-deps
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
|
- name: Start ComfyUI server
|
||||||
|
run: |
|
||||||
|
python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist &
|
||||||
|
wait-for-it --service 127.0.0.1:8188 -t 600
|
||||||
|
working-directory: ComfyUI
|
||||||
|
|
||||||
|
- name: Run Playwright tests (${{ matrix.browser }})
|
||||||
|
id: playwright
|
||||||
|
run: |
|
||||||
|
# Run tests with both HTML and JSON reporters
|
||||||
|
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
||||||
|
npx playwright test --project=${{ matrix.browser }} \
|
||||||
|
--reporter=list \
|
||||||
|
--reporter=html \
|
||||||
|
--reporter=json
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: playwright-report-${{ matrix.browser }}
|
||||||
|
path: ComfyUI_frontend/playwright-report/
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
# Merge sharded test reports
|
||||||
|
merge-reports:
|
||||||
|
needs: [playwright-tests-chromium-sharded]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout ComfyUI_frontend
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: 'Comfy-Org/ComfyUI_frontend'
|
||||||
|
path: 'ComfyUI_frontend'
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: 'ComfyUI_frontend/pnpm-lock.yaml'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
|
- name: Download blob reports
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ComfyUI_frontend/all-blob-reports
|
||||||
|
pattern: blob-report-chromium-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Merge into HTML Report
|
||||||
|
run: |
|
||||||
|
# Generate HTML report
|
||||||
|
npx playwright merge-reports --reporter=html ./all-blob-reports
|
||||||
|
# Generate JSON report separately with explicit output path
|
||||||
|
PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-report/report.json \
|
||||||
|
npx playwright merge-reports --reporter=json ./all-blob-reports
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
|
||||||
|
- name: Upload HTML report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: playwright-report-chromium
|
||||||
|
path: ComfyUI_frontend/playwright-report/
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
#### BEGIN Deployment and commenting (non-forked PRs only)
|
||||||
|
# when using pull_request event, we have permission to comment directly
|
||||||
|
# if its a forked repo, we need to use workflow_run event in a separate workflow (pr-playwright-deploy.yaml)
|
||||||
|
|
||||||
|
# Post starting comment for non-forked PRs
|
||||||
|
comment-on-pr-start:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Get start time
|
||||||
|
id: start-time
|
||||||
|
run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Post starting comment
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
|
||||||
|
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
||||||
|
"${{ github.event.pull_request.number }}" \
|
||||||
|
"${{ github.head_ref }}" \
|
||||||
|
"starting" \
|
||||||
|
"${{ steps.start-time.outputs.time }}"
|
||||||
|
|
||||||
|
# Deploy and comment for non-forked PRs only
|
||||||
|
deploy-and-comment:
|
||||||
|
needs: [playwright-tests, merge-reports]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download all playwright reports
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: playwright-report-*
|
||||||
|
path: reports
|
||||||
|
|
||||||
|
- name: Make deployment script executable
|
||||||
|
run: chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh
|
||||||
|
|
||||||
|
- name: Deploy reports and comment on PR
|
||||||
|
env:
|
||||||
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
./scripts/cicd/pr-playwright-deploy-and-comment.sh \
|
||||||
|
"${{ github.event.pull_request.number }}" \
|
||||||
|
"${{ github.head_ref }}" \
|
||||||
|
"completed"
|
||||||
|
#### END Deployment and commenting (non-forked PRs only)
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
name: 'Api: Update Electron API Types'
|
name: Update Electron Types
|
||||||
description: 'When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo'
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -13,7 +12,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -26,6 +25,15 @@ jobs:
|
|||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.cache
|
||||||
|
key: electron-types-tools-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
electron-types-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Update electron types
|
- name: Update electron types
|
||||||
run: pnpm install --workspace-root @comfyorg/comfyui-electron-types@latest
|
run: pnpm install --workspace-root @comfyorg/comfyui-electron-types@latest
|
||||||
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
name: 'Api: Update Manager API Types'
|
name: Update ComfyUI-Manager API Types
|
||||||
description: 'When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo'
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# Manual trigger
|
# Manual trigger
|
||||||
@@ -18,7 +17,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -31,11 +30,28 @@ jobs:
|
|||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.cache
|
||||||
|
key: update-manager-tools-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
update-manager-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Cache ComfyUI-Manager repository
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ComfyUI-Manager
|
||||||
|
key: comfyui-manager-repo-${{ runner.os }}-${{ github.run_id }}
|
||||||
|
restore-keys: |
|
||||||
|
comfyui-manager-repo-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Checkout ComfyUI-Manager repository
|
- name: Checkout ComfyUI-Manager repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: Comfy-Org/ComfyUI-Manager
|
repository: Comfy-Org/ComfyUI-Manager
|
||||||
path: ComfyUI-Manager
|
path: ComfyUI-Manager
|
||||||
@@ -52,7 +68,7 @@ jobs:
|
|||||||
- name: Generate Manager API types
|
- name: Generate Manager API types
|
||||||
run: |
|
run: |
|
||||||
echo "Generating TypeScript types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}..."
|
echo "Generating TypeScript types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}..."
|
||||||
pnpm dlx openapi-typescript ./ComfyUI-Manager/openapi.yaml --output ./src/types/generatedManagerTypes.ts
|
npx openapi-typescript ./ComfyUI-Manager/openapi.yaml --output ./src/types/generatedManagerTypes.ts
|
||||||
|
|
||||||
- name: Validate generated types
|
- name: Validate generated types
|
||||||
run: |
|
run: |
|
||||||
@@ -105,4 +121,4 @@ jobs:
|
|||||||
labels: Manager
|
labels: Manager
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
add-paths: |
|
add-paths: |
|
||||||
src/types/generatedManagerTypes.ts
|
src/types/generatedManagerTypes.ts
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
name: 'Api: Update Registry API Types'
|
name: Update Comfy Registry API Types
|
||||||
description: 'When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo'
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# Manual trigger
|
# Manual trigger
|
||||||
@@ -17,7 +16,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -30,11 +29,28 @@ jobs:
|
|||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.cache
|
||||||
|
key: update-registry-tools-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
update-registry-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Cache comfy-api repository
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: comfy-api
|
||||||
|
key: comfy-api-repo-${{ runner.os }}-${{ github.run_id }}
|
||||||
|
restore-keys: |
|
||||||
|
comfy-api-repo-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Checkout comfy-api repository
|
- name: Checkout comfy-api repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: Comfy-Org/comfy-api
|
repository: Comfy-Org/comfy-api
|
||||||
path: comfy-api
|
path: comfy-api
|
||||||
@@ -52,18 +68,17 @@ jobs:
|
|||||||
- name: Generate API types
|
- name: Generate API types
|
||||||
run: |
|
run: |
|
||||||
echo "Generating TypeScript types from comfy-api@${{ steps.api-info.outputs.commit }}..."
|
echo "Generating TypeScript types from comfy-api@${{ steps.api-info.outputs.commit }}..."
|
||||||
mkdir -p ./packages/registry-types/src
|
npx openapi-typescript ./comfy-api/openapi.yml --output ./src/types/comfyRegistryTypes.ts
|
||||||
pnpm dlx openapi-typescript ./comfy-api/openapi.yml --output ./packages/registry-types/src/comfyRegistryTypes.ts
|
|
||||||
|
|
||||||
- name: Validate generated types
|
- name: Validate generated types
|
||||||
run: |
|
run: |
|
||||||
if [ ! -f ./packages/registry-types/src/comfyRegistryTypes.ts ]; then
|
if [ ! -f ./src/types/comfyRegistryTypes.ts ]; then
|
||||||
echo "Error: Types file was not generated."
|
echo "Error: Types file was not generated."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if file is not empty
|
# Check if file is not empty
|
||||||
if [ ! -s ./packages/registry-types/src/comfyRegistryTypes.ts ]; then
|
if [ ! -s ./src/types/comfyRegistryTypes.ts ]; then
|
||||||
echo "Error: Generated types file is empty."
|
echo "Error: Generated types file is empty."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -71,12 +86,12 @@ jobs:
|
|||||||
- name: Lint generated types
|
- name: Lint generated types
|
||||||
run: |
|
run: |
|
||||||
echo "Linting generated Comfy Registry API types..."
|
echo "Linting generated Comfy Registry API types..."
|
||||||
pnpm lint:fix:no-cache -- ./packages/registry-types/src/comfyRegistryTypes.ts
|
pnpm lint:fix:no-cache -- ./src/types/comfyRegistryTypes.ts
|
||||||
|
|
||||||
- name: Check for changes
|
- name: Check for changes
|
||||||
id: check-changes
|
id: check-changes
|
||||||
run: |
|
run: |
|
||||||
if [[ -z $(git status --porcelain ./packages/registry-types/src/comfyRegistryTypes.ts) ]]; then
|
if [[ -z $(git status --porcelain ./src/types/comfyRegistryTypes.ts) ]]; then
|
||||||
echo "No changes to Comfy Registry API types detected."
|
echo "No changes to Comfy Registry API types detected."
|
||||||
echo "changed=false" >> $GITHUB_OUTPUT
|
echo "changed=false" >> $GITHUB_OUTPUT
|
||||||
exit 0
|
exit 0
|
||||||
@@ -106,4 +121,4 @@ jobs:
|
|||||||
labels: CNR
|
labels: CNR
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
add-paths: |
|
add-paths: |
|
||||||
packages/registry-types/src/comfyRegistryTypes.ts
|
src/types/comfyRegistryTypes.ts
|
||||||
94
.github/workflows/version-bump-desktop-ui.yaml
vendored
94
.github/workflows/version-bump-desktop-ui.yaml
vendored
@@ -1,94 +0,0 @@
|
|||||||
name: Version Bump Desktop UI
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version_type:
|
|
||||||
description: 'Version increment type'
|
|
||||||
required: true
|
|
||||||
default: 'patch'
|
|
||||||
type: 'choice'
|
|
||||||
options: [patch, minor, major, prepatch, preminor, premajor, prerelease]
|
|
||||||
pre_release:
|
|
||||||
description: Pre-release ID (suffix)
|
|
||||||
required: false
|
|
||||||
default: ''
|
|
||||||
type: string
|
|
||||||
branch:
|
|
||||||
description: 'Base branch to bump (e.g., main, core/1.29, core/1.30)'
|
|
||||||
required: true
|
|
||||||
default: 'main'
|
|
||||||
type: string
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
bump-version-desktop-ui:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.inputs.branch }}
|
|
||||||
fetch-depth: 0
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Validate branch exists
|
|
||||||
run: |
|
|
||||||
BRANCH="${{ github.event.inputs.branch }}"
|
|
||||||
if ! git show-ref --verify --quiet "refs/heads/$BRANCH" && ! git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
|
|
||||||
echo "❌ Branch '$BRANCH' does not exist"
|
|
||||||
echo ""
|
|
||||||
echo "Available core branches:"
|
|
||||||
git branch -r | grep 'origin/core/' | sed 's/.*origin\// - /' || echo " (none found)"
|
|
||||||
echo ""
|
|
||||||
echo "Main branch:"
|
|
||||||
echo " - main"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✅ Branch '$BRANCH' exists"
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v5
|
|
||||||
with:
|
|
||||||
node-version: '24.x'
|
|
||||||
cache: 'pnpm'
|
|
||||||
|
|
||||||
- name: Bump desktop-ui version
|
|
||||||
id: bump-version
|
|
||||||
env:
|
|
||||||
VERSION_TYPE: ${{ github.event.inputs.version_type }}
|
|
||||||
PRE_RELEASE: ${{ github.event.inputs.pre_release }}
|
|
||||||
run: |
|
|
||||||
pnpm -C apps/desktop-ui version "$VERSION_TYPE" --preid "$PRE_RELEASE" --no-git-tag-version
|
|
||||||
NEW_VERSION=$(node -p "require('./apps/desktop-ui/package.json').version")
|
|
||||||
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Format PR string
|
|
||||||
id: capitalised
|
|
||||||
env:
|
|
||||||
VERSION_TYPE: ${{ github.event.inputs.version_type }}
|
|
||||||
run: |
|
|
||||||
echo "capitalised=${VERSION_TYPE@u}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Create Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
|
||||||
commit-message: '[release] Increment desktop-ui to ${{ steps.bump-version.outputs.NEW_VERSION }}'
|
|
||||||
title: desktop-ui ${{ steps.bump-version.outputs.NEW_VERSION }}
|
|
||||||
body: |
|
|
||||||
${{ steps.capitalised.outputs.capitalised }} version increment for @comfyorg/desktop-ui to ${{ steps.bump-version.outputs.NEW_VERSION }}
|
|
||||||
|
|
||||||
**Base branch:** `${{ github.event.inputs.branch }}`
|
|
||||||
branch: desktop-ui-version-bump-${{ steps.bump-version.outputs.NEW_VERSION }}
|
|
||||||
base: ${{ github.event.inputs.branch }}
|
|
||||||
labels: |
|
|
||||||
Release
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
name: "Release: Version Bump"
|
name: Version Bump
|
||||||
description: "Manual workflow to increment package version with semantic versioning support"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -15,11 +14,6 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
type: string
|
type: string
|
||||||
branch:
|
|
||||||
description: 'Base branch to bump (e.g., main, core/1.29, core/1.30)'
|
|
||||||
required: true
|
|
||||||
default: 'main'
|
|
||||||
type: string
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump-version:
|
bump-version:
|
||||||
@@ -30,25 +24,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
ref: ${{ github.event.inputs.branch }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Validate branch exists
|
|
||||||
run: |
|
|
||||||
BRANCH="${{ github.event.inputs.branch }}"
|
|
||||||
if ! git show-ref --verify --quiet "refs/heads/$BRANCH" && ! git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
|
|
||||||
echo "❌ Branch '$BRANCH' does not exist"
|
|
||||||
echo ""
|
|
||||||
echo "Available core branches:"
|
|
||||||
git branch -r | grep 'origin/core/' | sed 's/.*origin\// - /' || echo " (none found)"
|
|
||||||
echo ""
|
|
||||||
echo "Main branch:"
|
|
||||||
echo " - main"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✅ Branch '$BRANCH' exists"
|
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -59,6 +35,7 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
id: bump-version
|
id: bump-version
|
||||||
@@ -81,9 +58,7 @@ jobs:
|
|||||||
title: ${{ steps.bump-version.outputs.NEW_VERSION }}
|
title: ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||||
body: |
|
body: |
|
||||||
${{ steps.capitalised.outputs.capitalised }} version increment to ${{ steps.bump-version.outputs.NEW_VERSION }}
|
${{ steps.capitalised.outputs.capitalised }} version increment to ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||||
|
|
||||||
**Base branch:** `${{ github.event.inputs.branch }}`
|
|
||||||
branch: version-bump-${{ steps.bump-version.outputs.NEW_VERSION }}
|
branch: version-bump-${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||||
base: ${{ github.event.inputs.branch }}
|
base: main
|
||||||
labels: |
|
labels: |
|
||||||
Release
|
Release
|
||||||
46
.github/workflows/vitest.yaml
vendored
Normal file
46
.github/workflows/vitest.yaml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: Vitest Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, master, dev*, core/*, desktop/* ]
|
||||||
|
pull_request:
|
||||||
|
branches-ignore: [ wip/*, draft/*, temp/* ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- 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: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.cache
|
||||||
|
coverage
|
||||||
|
.vitest-cache
|
||||||
|
key: vitest-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', 'vitest.config.*', 'tsconfig.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
vitest-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
|
||||||
|
vitest-cache-${{ runner.os }}-
|
||||||
|
test-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Run Vitest tests
|
||||||
|
run: |
|
||||||
|
pnpm test:component
|
||||||
|
pnpm test:unit
|
||||||
144
.github/workflows/weekly-docs-check.yaml
vendored
144
.github/workflows/weekly-docs-check.yaml
vendored
@@ -1,144 +0,0 @@
|
|||||||
name: "Weekly Documentation Check"
|
|
||||||
description: "Automated weekly documentation accuracy check and update via Claude"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
# Run every Monday at 9 AM UTC
|
|
||||||
- cron: '0 9 * * 1'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docs-check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 45
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: main
|
|
||||||
|
|
||||||
- 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 for analysis tools
|
|
||||||
run: |
|
|
||||||
# Check if packages are already available locally
|
|
||||||
if ! pnpm list typescript @vue/compiler-sfc >/dev/null 2>&1; then
|
|
||||||
echo "Installing TypeScript and Vue compiler globally..."
|
|
||||||
pnpm install -g typescript @vue/compiler-sfc
|
|
||||||
else
|
|
||||||
echo "TypeScript and Vue compiler already available locally"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Run Claude Documentation Review
|
|
||||||
uses: anthropics/claude-code-action@v1.0.6
|
|
||||||
with:
|
|
||||||
prompt: |
|
|
||||||
Is all documentation still 100% accurate?
|
|
||||||
|
|
||||||
INSTRUCTIONS:
|
|
||||||
1. Fact-check all documentation against the current codebase
|
|
||||||
2. Look for:
|
|
||||||
- Outdated API references
|
|
||||||
- Deprecated functions or components still documented
|
|
||||||
- Missing documentation for new features
|
|
||||||
- Incorrect code examples
|
|
||||||
- Broken internal references
|
|
||||||
- Configuration examples that no longer work
|
|
||||||
- Documentation that contradicts actual implementation
|
|
||||||
3. Update any inaccurate or outdated documentation
|
|
||||||
4. Add documentation for significant undocumented features
|
|
||||||
5. Ensure all code examples are valid and tested against current code
|
|
||||||
|
|
||||||
Focus on these key areas:
|
|
||||||
- docs/**/*.md (all documentation files)
|
|
||||||
- CLAUDE.md (project guidelines)
|
|
||||||
- README.md files throughout the repository
|
|
||||||
- .claude/commands/*.md (Claude command documentation)
|
|
||||||
|
|
||||||
Make changes directly to the documentation files as needed.
|
|
||||||
DO NOT modify any source code files unless absolutely necessary for documentation accuracy.
|
|
||||||
|
|
||||||
After making all changes, create a comprehensive PR message summary:
|
|
||||||
1. Write a detailed PR body to /tmp/pr-body-${{ github.run_id }}.md in markdown format
|
|
||||||
2. Include:
|
|
||||||
- ## Summary section with bullet points of what was changed
|
|
||||||
- ## Changes Made section with details organized by category
|
|
||||||
- ## Review Notes section with any important context
|
|
||||||
3. Be specific about which files were updated and why
|
|
||||||
4. If no changes were needed, write a brief message stating documentation is up to date
|
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
||||||
claude_args: "--max-turns 256 --allowedTools 'Bash(git status),Bash(git diff),Bash(git log),Bash(pnpm:*),Bash(npm:*),Bash(node:*),Bash(tsc:*),Bash(echo:*),Read,Write,Edit,Glob,Grep'"
|
|
||||||
continue-on-error: false
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Check for changes
|
|
||||||
id: check_changes
|
|
||||||
run: |
|
|
||||||
if git diff --quiet && git diff --cached --quiet; then
|
|
||||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "No documentation changes needed"
|
|
||||||
else
|
|
||||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "Documentation changes detected"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create default PR body if not generated
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
run: |
|
|
||||||
if [ ! -f /tmp/pr-body-${{ github.run_id }}.md ]; then
|
|
||||||
cat > /tmp/pr-body-${{ github.run_id }}.md <<'EOF'
|
|
||||||
## Automated Documentation Review
|
|
||||||
|
|
||||||
This PR contains documentation updates identified by the weekly automated review.
|
|
||||||
|
|
||||||
### Review Process
|
|
||||||
- Automated fact-checking against current codebase
|
|
||||||
- Verification of code examples and API references
|
|
||||||
- Detection of outdated or missing documentation
|
|
||||||
|
|
||||||
### What was checked
|
|
||||||
- All markdown documentation in `docs/`
|
|
||||||
- Project guidelines in `CLAUDE.md`
|
|
||||||
- README files throughout the repository
|
|
||||||
- Claude command documentation in `.claude/commands/`
|
|
||||||
|
|
||||||
**Note**: This is an automated PR. Please review all changes carefully before merging.
|
|
||||||
|
|
||||||
🤖 Generated by weekly documentation check workflow
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create or Update Pull Request
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
uses: peter-evans/create-pull-request@v7
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.PR_GH_TOKEN }}
|
|
||||||
commit-message: 'docs: weekly documentation accuracy update'
|
|
||||||
branch: docs/weekly-update
|
|
||||||
delete-branch: true
|
|
||||||
title: 'docs: Weekly Documentation Update'
|
|
||||||
body-path: /tmp/pr-body-${{ github.run_id }}.md
|
|
||||||
labels: |
|
|
||||||
documentation
|
|
||||||
automated
|
|
||||||
draft: true
|
|
||||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -15,17 +15,13 @@ yarn.lock
|
|||||||
# Cache files
|
# Cache files
|
||||||
.eslintcache
|
.eslintcache
|
||||||
.prettiercache
|
.prettiercache
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
.pnpm-store
|
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
# Claude configuration
|
# Claude configuration
|
||||||
.claude/*.local.json
|
.claude/*.local.json
|
||||||
.claude/*.local.md
|
|
||||||
.claude/*.local.txt
|
|
||||||
CLAUDE.local.md
|
CLAUDE.local.md
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
@@ -33,7 +29,6 @@ CLAUDE.local.md
|
|||||||
*.code-workspace
|
*.code-workspace
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
!.vscode/tailwind.json
|
!.vscode/tailwind.json
|
||||||
!.vscode/custom-css.json
|
|
||||||
!.vscode/settings.json.default
|
!.vscode/settings.json.default
|
||||||
!.vscode/launch.json.default
|
!.vscode/launch.json.default
|
||||||
.idea
|
.idea
|
||||||
@@ -49,7 +44,6 @@ components.d.ts
|
|||||||
tests-ui/data/*
|
tests-ui/data/*
|
||||||
tests-ui/ComfyUI_examples
|
tests-ui/ComfyUI_examples
|
||||||
tests-ui/workflows/examples
|
tests-ui/workflows/examples
|
||||||
coverage/
|
|
||||||
|
|
||||||
# Browser tests
|
# Browser tests
|
||||||
/test-results/
|
/test-results/
|
||||||
@@ -79,13 +73,13 @@ templates_repo/
|
|||||||
vite.config.mts.timestamp-*.mjs
|
vite.config.mts.timestamp-*.mjs
|
||||||
|
|
||||||
# Linux core dumps
|
# Linux core dumps
|
||||||
/core
|
./core
|
||||||
|
|
||||||
*storybook.log
|
*storybook.log
|
||||||
storybook-static
|
storybook-static
|
||||||
|
|
||||||
# MCP Servers
|
|
||||||
.playwright-mcp/*
|
|
||||||
|
|
||||||
.nx/cache
|
.nx/cache
|
||||||
.nx/workspace-data
|
.nx/workspace-data
|
||||||
@@ -93,6 +87,3 @@ storybook-static
|
|||||||
.github/instructions/nx.instructions.md
|
.github/instructions/nx.instructions.md
|
||||||
vite.config.*.timestamp*
|
vite.config.*.timestamp*
|
||||||
vitest.config.*.timestamp*
|
vitest.config.*.timestamp*
|
||||||
|
|
||||||
# Weekly docs check output
|
|
||||||
/output.txt
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
pnpm exec lint-staged
|
pnpm exec lint-staged
|
||||||
pnpm exec tsx scripts/check-unused-i18n-keys.ts
|
pnpm exec tsx scripts/check-unused-i18n-keys.ts
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Run Knip with cache via package script
|
# Run Knip with cache via package script
|
||||||
pnpm knip 1>&2
|
pnpm knip
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ module.exports = defineConfig({
|
|||||||
entry: 'src/locales/en',
|
entry: 'src/locales/en',
|
||||||
entryLocale: 'en',
|
entryLocale: 'en',
|
||||||
output: 'src/locales',
|
output: 'src/locales',
|
||||||
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr'],
|
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar'],
|
||||||
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
|
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
|
||||||
'latent' is the short form of 'latent space'.
|
'latent' is the short form of 'latent space'.
|
||||||
'mask' is in the context of image processing.
|
'mask' is in the context of image processing.
|
||||||
|
|||||||
1
.npmrc
1
.npmrc
@@ -1,2 +1 @@
|
|||||||
ignore-workspace-root-check=true
|
ignore-workspace-root-check=true
|
||||||
catalog-mode=prefer
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
||||||
"ignorePatterns": [
|
|
||||||
".i18nrc.cjs",
|
|
||||||
"components.d.ts",
|
|
||||||
"lint-staged.config.js",
|
|
||||||
"vitest.setup.ts",
|
|
||||||
"**/vite.config.*.timestamp*",
|
|
||||||
"**/vitest.config.*.timestamp*",
|
|
||||||
"packages/registry-types/src/comfyRegistryTypes.ts",
|
|
||||||
"src/extensions/core/*",
|
|
||||||
"src/scripts/*",
|
|
||||||
"src/types/generatedManagerTypes.ts",
|
|
||||||
"src/types/vue-shim.d.ts"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"no-async-promise-executor": "off",
|
|
||||||
"no-control-regex": "off",
|
|
||||||
"no-eval": "off",
|
|
||||||
"no-self-assign": "allow",
|
|
||||||
"no-unused-expressions": "off",
|
|
||||||
"no-unused-private-class-members": "off",
|
|
||||||
"no-useless-rename": "off",
|
|
||||||
"typescript/no-this-alias": "off",
|
|
||||||
"typescript/no-unnecessary-parameter-property-assignment": "off",
|
|
||||||
"typescript/no-unsafe-declaration-merging": "off",
|
|
||||||
"typescript/no-unused-vars": "off",
|
|
||||||
"unicorn/no-empty-file": "off",
|
|
||||||
"unicorn/no-new-array": "off",
|
|
||||||
"unicorn/no-single-promise-in-promise-methods": "off",
|
|
||||||
"unicorn/no-useless-fallback-in-spread": "off",
|
|
||||||
"unicorn/no-useless-spread": "off",
|
|
||||||
"typescript/await-thenable": "off",
|
|
||||||
"typescript/no-base-to-string": "off",
|
|
||||||
"typescript/no-duplicate-type-constituents": "off",
|
|
||||||
"typescript/no-for-in-array": "off",
|
|
||||||
"typescript/no-meaningless-void-operator": "off",
|
|
||||||
"typescript/no-redundant-type-constituents": "off",
|
|
||||||
"typescript/restrict-template-expressions": "off",
|
|
||||||
"typescript/unbound-method": "off",
|
|
||||||
"typescript/no-floating-promises": "error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
packages/registry-types/src/comfyRegistryTypes.ts
|
src/types/comfyRegistryTypes.ts
|
||||||
src/types/generatedManagerTypes.ts
|
src/types/generatedManagerTypes.ts
|
||||||
@@ -7,5 +7,12 @@
|
|||||||
"importOrder": ["^@core/(.*)$", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"],
|
"importOrder": ["^@core/(.*)$", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"],
|
||||||
"importOrderSeparation": true,
|
"importOrderSeparation": true,
|
||||||
"importOrderSortSpecifiers": true,
|
"importOrderSortSpecifiers": true,
|
||||||
"plugins": ["@prettier/plugin-oxc", "@trivago/prettier-plugin-sort-imports"]
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.{js,cjs,mjs,ts,cts,mts,tsx,vue}",
|
||||||
|
"options": {
|
||||||
|
"plugins": ["@trivago/prettier-plugin-sort-imports"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
- `pnpm storybook`: Start Storybook development server
|
- `pnpm storybook`: Start Storybook development server
|
||||||
- `pnpm build-storybook`: Build static Storybook
|
- `pnpm build-storybook`: Build static Storybook
|
||||||
- `pnpm test:unit`: Run unit tests (includes Storybook components)
|
- `pnpm test:component`: Run component tests (includes Storybook components)
|
||||||
|
|
||||||
## Development Workflow for Storybook
|
## Development Workflow for Storybook
|
||||||
|
|
||||||
|
|||||||
@@ -211,17 +211,18 @@ This Storybook setup includes:
|
|||||||
|
|
||||||
## Icon Usage in Storybook
|
## Icon Usage in Storybook
|
||||||
|
|
||||||
In this project, only the `<i class="icon-[lucide--folder]" />` syntax from unplugin-icons is supported in Storybook.
|
In this project, the `<i-lucide:... />` syntax from unplugin-icons is not supported in Storybook.
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Trophy, Settings } from 'lucide-vue-next'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<i class="icon-[lucide--trophy] text-neutral size-4" />
|
<Trophy :size="16" class="text-neutral" />
|
||||||
<i class="icon-[lucide--settings] text-neutral size-4" />
|
<Settings :size="16" class="text-neutral" />
|
||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -15,37 +15,26 @@ const config: StorybookConfig = {
|
|||||||
async viteFinal(config) {
|
async viteFinal(config) {
|
||||||
// Use dynamic import to avoid CJS deprecation warning
|
// Use dynamic import to avoid CJS deprecation warning
|
||||||
const { mergeConfig } = await import('vite')
|
const { mergeConfig } = await import('vite')
|
||||||
const { default: tailwindcss } = await import('@tailwindcss/vite')
|
|
||||||
|
|
||||||
// Filter out any plugins that might generate import maps
|
// Filter out any plugins that might generate import maps
|
||||||
if (config.plugins) {
|
if (config.plugins) {
|
||||||
config.plugins = config.plugins
|
config.plugins = config.plugins.filter((plugin: any) => {
|
||||||
// Type guard: ensure we have valid plugin objects with names
|
if (plugin && plugin.name && plugin.name.includes('import-map')) {
|
||||||
.filter(
|
return false
|
||||||
(plugin): plugin is NonNullable<typeof plugin> & { name: string } => {
|
}
|
||||||
return (
|
return true
|
||||||
plugin !== null &&
|
})
|
||||||
plugin !== undefined &&
|
|
||||||
typeof plugin === 'object' &&
|
|
||||||
'name' in plugin &&
|
|
||||||
typeof plugin.name === 'string'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
// Business logic: filter out import-map plugins
|
|
||||||
.filter((plugin) => !plugin.name.includes('import-map'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mergeConfig(config, {
|
return mergeConfig(config, {
|
||||||
// Replace plugins entirely to avoid inheritance issues
|
// Replace plugins entirely to avoid inheritance issues
|
||||||
plugins: [
|
plugins: [
|
||||||
// Only include plugins we explicitly need for Storybook
|
// Only include plugins we explicitly need for Storybook
|
||||||
tailwindcss(),
|
|
||||||
Icons({
|
Icons({
|
||||||
compiler: 'vue3',
|
compiler: 'vue3',
|
||||||
customCollections: {
|
customCollections: {
|
||||||
comfy: FileSystemIconLoader(
|
comfy: FileSystemIconLoader(
|
||||||
process.cwd() + '/packages/design-system/src/icons'
|
process.cwd() + '/src/assets/icons/custom'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -74,15 +63,13 @@ const config: StorybookConfig = {
|
|||||||
'@': process.cwd() + '/src'
|
'@': process.cwd() + '/src'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
esbuild: {
|
|
||||||
// Prevent minification of identifiers to preserve _sfc_main
|
|
||||||
minifyIdentifiers: false,
|
|
||||||
keepNames: true
|
|
||||||
},
|
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
// Disable tree-shaking for Storybook to prevent Vue SFC exports from being removed
|
external: () => {
|
||||||
treeshake: false,
|
// Don't externalize any modules in Storybook build
|
||||||
|
// This ensures PrimeVue and other dependencies are bundled
|
||||||
|
return false
|
||||||
|
},
|
||||||
onwarn: (warning, warn) => {
|
onwarn: (warning, warn) => {
|
||||||
// Suppress specific warnings
|
// Suppress specific warnings
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { definePreset } from '@primevue/themes'
|
import { definePreset } from '@primevue/themes'
|
||||||
import Aura from '@primevue/themes/aura'
|
import Aura from '@primevue/themes/aura'
|
||||||
import { setup } from '@storybook/vue3'
|
import { setup } from '@storybook/vue3'
|
||||||
import type { Preview, StoryContext, StoryFn } from '@storybook/vue3-vite'
|
import type { Preview } from '@storybook/vue3-vite'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import 'primeicons/primeicons.css'
|
import 'primeicons/primeicons.css'
|
||||||
import PrimeVue from 'primevue/config'
|
import PrimeVue from 'primevue/config'
|
||||||
@@ -9,9 +9,11 @@ import ConfirmationService from 'primevue/confirmationservice'
|
|||||||
import ToastService from 'primevue/toastservice'
|
import ToastService from 'primevue/toastservice'
|
||||||
import Tooltip from 'primevue/tooltip'
|
import Tooltip from 'primevue/tooltip'
|
||||||
|
|
||||||
import '@/assets/css/style.css'
|
import '../src/assets/css/style.css'
|
||||||
import { i18n } from '@/i18n'
|
import { i18n } from '../src/i18n'
|
||||||
import '@/lib/litegraph/public/css/litegraph.css'
|
import '../src/lib/litegraph/public/css/litegraph.css'
|
||||||
|
import { useWidgetStore } from '../src/stores/widgetStore'
|
||||||
|
import { useColorPaletteStore } from '../src/stores/workspace/colorPaletteStore'
|
||||||
|
|
||||||
const ComfyUIPreset = definePreset(Aura, {
|
const ComfyUIPreset = definePreset(Aura, {
|
||||||
semantic: {
|
semantic: {
|
||||||
@@ -23,11 +25,13 @@ const ComfyUIPreset = definePreset(Aura, {
|
|||||||
// Setup Vue app for Storybook
|
// Setup Vue app for Storybook
|
||||||
setup((app) => {
|
setup((app) => {
|
||||||
app.directive('tooltip', Tooltip)
|
app.directive('tooltip', Tooltip)
|
||||||
|
|
||||||
// Create Pinia instance
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
|
|
||||||
|
// Initialize stores
|
||||||
|
useColorPaletteStore(pinia)
|
||||||
|
useWidgetStore(pinia)
|
||||||
|
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
app.use(PrimeVue, {
|
app.use(PrimeVue, {
|
||||||
theme: {
|
theme: {
|
||||||
@@ -46,8 +50,8 @@ setup((app) => {
|
|||||||
app.use(ToastService)
|
app.use(ToastService)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Theme and dialog decorator
|
// Dark theme decorator
|
||||||
export const withTheme = (Story: StoryFn, context: StoryContext) => {
|
export const withTheme = (Story: any, context: any) => {
|
||||||
const theme = context.globals.theme || 'light'
|
const theme = context.globals.theme || 'light'
|
||||||
|
|
||||||
// Apply theme class to document root
|
// Apply theme class to document root
|
||||||
@@ -59,7 +63,7 @@ export const withTheme = (Story: StoryFn, context: StoryContext) => {
|
|||||||
document.body.classList.remove('dark-theme')
|
document.body.classList.remove('dark-theme')
|
||||||
}
|
}
|
||||||
|
|
||||||
return Story(context.args, context)
|
return Story()
|
||||||
}
|
}
|
||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.vue", "**/*.vue"],
|
|
||||||
"customSyntax": "postcss-html"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"import-notation": "string",
|
|
||||||
"font-family-no-missing-generic-family-keyword": true,
|
|
||||||
"declaration-property-value-no-unknown": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"ignoreProperties": {
|
|
||||||
"speak": ["none"],
|
|
||||||
"app-region": ["drag", "no-drag"],
|
|
||||||
"/^(width|height)$/": ["/^v-bind/"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"color-function-notation": "modern",
|
|
||||||
"shorthand-property-no-redundant-values": true,
|
|
||||||
"selector-pseudo-element-colon-notation": "double",
|
|
||||||
"no-duplicate-selectors": true,
|
|
||||||
"font-weight-notation": "numeric",
|
|
||||||
"length-zero-no-unit": true,
|
|
||||||
"color-no-invalid-hex": true,
|
|
||||||
"number-max-precision": 4,
|
|
||||||
"property-no-vendor-prefix": true,
|
|
||||||
"value-no-vendor-prefix": true,
|
|
||||||
"selector-no-vendor-prefix": true,
|
|
||||||
"media-feature-name-no-vendor-prefix": true,
|
|
||||||
"selector-max-universal": 1,
|
|
||||||
"selector-max-type": 2,
|
|
||||||
"declaration-block-no-duplicate-properties": true,
|
|
||||||
"block-no-empty": true,
|
|
||||||
"no-descending-specificity": null,
|
|
||||||
"no-duplicate-at-import-rules": true,
|
|
||||||
"at-rule-no-unknown": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"ignoreAtRules": [
|
|
||||||
"tailwind",
|
|
||||||
"apply",
|
|
||||||
"layer",
|
|
||||||
"config",
|
|
||||||
"theme",
|
|
||||||
"reference",
|
|
||||||
"plugin",
|
|
||||||
"custom-variant",
|
|
||||||
"utility"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"function-no-unknown": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"ignoreFunctions": [
|
|
||||||
"theme",
|
|
||||||
"v-bind"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"ignoreFiles": [
|
|
||||||
"node_modules/**",
|
|
||||||
"dist/**",
|
|
||||||
"playwright-report/**",
|
|
||||||
"public/**",
|
|
||||||
"src/lib/litegraph/**"
|
|
||||||
],
|
|
||||||
"files": ["**/*.css", "**/*.vue"]
|
|
||||||
}
|
|
||||||
50
.vscode/custom-css.json
vendored
50
.vscode/custom-css.json
vendored
@@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1.1,
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "app-region",
|
|
||||||
"description": "Electron-specific CSS property that defines draggable regions in custom title bar windows. Setting 'drag' marks a rectangular area as draggable for moving the window; 'no-drag' excludes areas from the draggable region.",
|
|
||||||
"values": [
|
|
||||||
{
|
|
||||||
"name": "drag",
|
|
||||||
"description": "Marks the element as draggable for moving the Electron window"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "no-drag",
|
|
||||||
"description": "Excludes the element from being used to drag the Electron window"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"name": "Electron Window Customization",
|
|
||||||
"url": "https://www.electronjs.org/docs/latest/tutorial/window-customization"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "speak",
|
|
||||||
"description": "Deprecated CSS2 aural stylesheet property for controlling screen reader speech. Use ARIA attributes instead.",
|
|
||||||
"values": [
|
|
||||||
{
|
|
||||||
"name": "auto",
|
|
||||||
"description": "Content is read aurally if element is not a block and is visible"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "never",
|
|
||||||
"description": "Content will not be read aurally"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "always",
|
|
||||||
"description": "Content will be read aurally regardless of display settings"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"name": "CSS-Tricks Reference",
|
|
||||||
"url": "https://css-tricks.com/almanac/properties/s/speak/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "obsolete"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
3
.vscode/settings.json.default
vendored
3
.vscode/settings.json.default
vendored
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"css.customData": [
|
"css.customData": [
|
||||||
".vscode/tailwind.json",
|
".vscode/tailwind.json"
|
||||||
".vscode/custom-css.json"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
36
.vscode/tailwind.json
vendored
36
.vscode/tailwind.json
vendored
@@ -7,7 +7,7 @@
|
|||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"name": "Tailwind Documentation",
|
||||||
"url": "https://tailwindcss.com/docs/functions-and-directives#import-directive"
|
"url": "https://tailwindcss.com/docs/functions-and-directives#import"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"name": "Tailwind Documentation",
|
||||||
"url": "https://tailwindcss.com/docs/functions-and-directives#theme-directive"
|
"url": "https://tailwindcss.com/docs/functions-and-directives#theme"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -27,17 +27,17 @@
|
|||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"name": "Tailwind Documentation",
|
||||||
"url": "https://tailwindcss.com/docs/theme#layers"
|
"url": "https://tailwindcss.com/docs/functions-and-directives#layer"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@apply",
|
"name": "@apply",
|
||||||
"description": "DO NOT USE. IF YOU ARE CAUGHT USING @apply YOU WILL FACE SEVERE CONSEQUENCES.",
|
"description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.",
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"name": "Tailwind Documentation",
|
||||||
"url": "https://tailwindcss.com/docs/functions-and-directives#apply-directive"
|
"url": "https://tailwindcss.com/docs/functions-and-directives#apply"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"name": "Tailwind Documentation",
|
||||||
"url": "https://tailwindcss.com/docs/functions-and-directives#config-directive"
|
"url": "https://tailwindcss.com/docs/functions-and-directives#config"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"name": "Tailwind Documentation",
|
||||||
"url": "https://tailwindcss.com/docs/functions-and-directives#reference-directive"
|
"url": "https://tailwindcss.com/docs/functions-and-directives#reference"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -67,27 +67,7 @@
|
|||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"name": "Tailwind Documentation",
|
"name": "Tailwind Documentation",
|
||||||
"url": "https://tailwindcss.com/docs/functions-and-directives#plugin-directive"
|
"url": "https://tailwindcss.com/docs/functions-and-directives#plugin"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@custom-variant",
|
|
||||||
"description": "Use the `@custom-variant` directive to add a custom variant to your project. Custom variants can be used with utilities like `hover`, `focus`, and responsive breakpoints. Use `@slot` inside the variant to indicate where the utility's styles should be inserted.",
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"name": "Tailwind Documentation",
|
|
||||||
"url": "https://tailwindcss.com/docs/adding-custom-styles#adding-custom-variants"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "@utility",
|
|
||||||
"description": "Use the `@utility` directive to add custom utilities to your project. Custom utilities work with all variants like `hover`, `focus`, and responsive variants. Use `--value()` to create functional utilities that accept arguments.",
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"name": "Tailwind Documentation",
|
|
||||||
"url": "https://tailwindcss.com/docs/adding-custom-styles#adding-custom-utilities"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
10
.yamllint
10
.yamllint
@@ -1,10 +0,0 @@
|
|||||||
extends: default
|
|
||||||
|
|
||||||
ignore: |
|
|
||||||
node_modules/
|
|
||||||
dist/
|
|
||||||
|
|
||||||
rules:
|
|
||||||
line-length: disable
|
|
||||||
document-start: disable
|
|
||||||
truthy: disable
|
|
||||||
10
AGENTS.md
10
AGENTS.md
@@ -5,14 +5,15 @@
|
|||||||
- Routing/i18n/entry: `src/router.ts`, `src/i18n.ts`, `src/main.ts`.
|
- Routing/i18n/entry: `src/router.ts`, `src/i18n.ts`, `src/main.ts`.
|
||||||
- Tests: unit/component in `tests-ui/` and `src/components/**/*.{test,spec}.ts`; E2E in `browser_tests/`.
|
- Tests: unit/component in `tests-ui/` and `src/components/**/*.{test,spec}.ts`; E2E in `browser_tests/`.
|
||||||
- Public assets: `public/`. Build output: `dist/`.
|
- Public assets: `public/`. Build output: `dist/`.
|
||||||
- Config: `vite.config.mts`, `vitest.config.ts`, `playwright.config.ts`, `eslint.config.ts`, `.prettierrc`.
|
- Config: `vite.config.mts`, `vitest.config.ts`, `playwright.config.ts`, `eslint.config.js`, `.prettierrc`.
|
||||||
|
|
||||||
## Build, Test, and Development Commands
|
## Build, Test, and Development Commands
|
||||||
- `pnpm dev`: Start Vite dev server.
|
- `pnpm dev`: Start Vite dev server.
|
||||||
- `pnpm dev:electron`: Dev server with Electron API mocks.
|
- `pnpm dev:electron`: Dev server with Electron API mocks.
|
||||||
- `pnpm build`: Type-check then production build to `dist/`.
|
- `pnpm build`: Type-check then production build to `dist/`.
|
||||||
- `pnpm preview`: Preview the production build locally.
|
- `pnpm preview`: Preview the production build locally.
|
||||||
- `pnpm test:unit`: Run Vitest unit tests.
|
- `pnpm test:unit`: Run Vitest unit tests (`tests-ui/`).
|
||||||
|
- `pnpm test:component`: Run component tests (`src/components/`).
|
||||||
- `pnpm test:browser`: Run Playwright E2E tests (`browser_tests/`).
|
- `pnpm test:browser`: Run Playwright E2E tests (`browser_tests/`).
|
||||||
- `pnpm lint` / `pnpm lint:fix`: Lint (ESLint). `pnpm format` / `format:check`: Prettier.
|
- `pnpm lint` / `pnpm lint:fix`: Lint (ESLint). `pnpm format` / `format:check`: Prettier.
|
||||||
- `pnpm typecheck`: Vue TSC type checking.
|
- `pnpm typecheck`: Vue TSC type checking.
|
||||||
@@ -30,9 +31,10 @@
|
|||||||
- Playwright: place tests in `browser_tests/`; optional tags like `@mobile`, `@2x` are respected by config.
|
- Playwright: place tests in `browser_tests/`; optional tags like `@mobile`, `@2x` are respected by config.
|
||||||
|
|
||||||
## Commit & Pull Request Guidelines
|
## Commit & Pull Request Guidelines
|
||||||
- Commits: Use `[skip ci]` for locale-only updates when appropriate.
|
- Commits: Prefer Conventional Commits (e.g., `feat(ui): add sidebar`), `refactor(litegraph): …`. Use `[skip ci]` for locale-only updates when appropriate.
|
||||||
- PRs: Include clear description, linked issues (`- Fixes #123`), and screenshots/GIFs for UI changes.
|
- PRs: Include clear description, linked issues (`Fixes #123`), and screenshots/GIFs for UI changes. Add/adjust tests and i18n strings when applicable.
|
||||||
- Quality gates: `pnpm lint`, `pnpm typecheck`, and relevant tests must pass. Keep PRs focused and small.
|
- Quality gates: `pnpm lint`, `pnpm typecheck`, and relevant tests must pass. Keep PRs focused and small.
|
||||||
|
|
||||||
## Security & Configuration Tips
|
## Security & Configuration Tips
|
||||||
- Secrets: Use `.env` (see `.env_example`); do not commit secrets.
|
- Secrets: Use `.env` (see `.env_example`); do not commit secrets.
|
||||||
|
- Backend: Dev server expects ComfyUI backend at `localhost:8188` by default; configure via `.env`.
|
||||||
|
|||||||
12
CLAUDE.md
12
CLAUDE.md
@@ -17,8 +17,8 @@ This bootstraps the monorepo with dependencies, builds, tests, and dev server ve
|
|||||||
- `pnpm typecheck`: Type checking
|
- `pnpm typecheck`: Type checking
|
||||||
- `pnpm build`: Build for production (via nx)
|
- `pnpm build`: Build for production (via nx)
|
||||||
- `pnpm lint`: Linting (via nx)
|
- `pnpm lint`: Linting (via nx)
|
||||||
- `pnpm oxlint`: Fast Rust-based linting with Oxc
|
|
||||||
- `pnpm format`: Prettier formatting
|
- `pnpm format`: Prettier formatting
|
||||||
|
- `pnpm test:component`: Run component tests with browser environment
|
||||||
- `pnpm test:unit`: Run all unit tests
|
- `pnpm test:unit`: Run all unit tests
|
||||||
- `pnpm test:browser`: Run E2E tests via Playwright
|
- `pnpm test:browser`: Run E2E tests via Playwright
|
||||||
- `pnpm test:unit -- tests-ui/tests/example.test.ts`: Run single test file
|
- `pnpm test:unit -- tests-ui/tests/example.test.ts`: Run single test file
|
||||||
@@ -63,11 +63,6 @@ Key Nx features:
|
|||||||
|
|
||||||
## Project Philosophy
|
## Project Philosophy
|
||||||
|
|
||||||
- Follow good software engineering principles
|
|
||||||
- YAGNI
|
|
||||||
- AHA
|
|
||||||
- DRY
|
|
||||||
- SOLID
|
|
||||||
- Clean, stable public APIs
|
- Clean, stable public APIs
|
||||||
- Domain-driven design
|
- Domain-driven design
|
||||||
- Thousands of users and extensions
|
- Thousands of users and extensions
|
||||||
@@ -132,5 +127,6 @@ const value = api.getServerFeature('config_name', defaultValue) // Get config
|
|||||||
- NEVER use `--no-verify` flag when committing
|
- NEVER use `--no-verify` flag when committing
|
||||||
- NEVER delete or disable tests to make them pass
|
- NEVER delete or disable tests to make them pass
|
||||||
- NEVER circumvent quality checks
|
- NEVER circumvent quality checks
|
||||||
- NEVER use `dark:` or `dark-theme:` tailwind variants. Instead use a semantic value from the `style.css` theme, e.g. `bg-node-component-surface`
|
- NEVER use `dark:` prefix - always use `dark-theme:` for dark mode styles, for example: `dark-theme:text-white dark-theme:bg-black`
|
||||||
- NEVER use `:class="[]"` to merge class names - always use `import { cn } from '@/utils/tailwindUtil'`, for example: `<div :class="cn('text-node-component-header-icon', hasError && 'text-danger')" />`
|
- NEVER use `:class="[]"` to merge class names - always use `import { cn } from '@/utils/tailwindUtil'`, for example: `<div :class="cn('bg-red-500', { 'bg-blue-500': condition })" />`
|
||||||
|
|
||||||
|
|||||||
72
CODEOWNERS
72
CODEOWNERS
@@ -1,63 +1,17 @@
|
|||||||
# Desktop/Electron
|
# Admins
|
||||||
/apps/desktop-ui/ @webfiltered
|
* @Comfy-Org/comfy_frontend_devs
|
||||||
/src/stores/electronDownloadStore.ts @webfiltered
|
|
||||||
/src/extensions/core/electronAdapter.ts @webfiltered
|
|
||||||
/vite.electron.config.mts @webfiltered
|
|
||||||
|
|
||||||
# Common UI Components
|
# Maintainers
|
||||||
/src/components/chip/ @viva-jinyi
|
*.md @Comfy-Org/comfy_maintainer
|
||||||
/src/components/card/ @viva-jinyi
|
/tests-ui/ @Comfy-Org/comfy_maintainer
|
||||||
/src/components/button/ @viva-jinyi
|
/browser_tests/ @Comfy-Org/comfy_maintainer
|
||||||
/src/components/input/ @viva-jinyi
|
/.env_example @Comfy-Org/comfy_maintainer
|
||||||
|
|
||||||
# Topbar
|
# Translations (AIGODLIKE team + shinshin86)
|
||||||
/src/components/topbar/ @pythongosssss
|
/src/locales/ @Yorha4D @KarryCharon @DorotaLuna @shinshin86 @Comfy-Org/comfy_maintainer
|
||||||
|
|
||||||
# Thumbnail
|
# Load 3D extension
|
||||||
/src/renderer/core/thumbnail/ @pythongosssss
|
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs
|
||||||
|
|
||||||
# Legacy UI
|
# Mask Editor extension
|
||||||
/scripts/ui/ @pythongosssss
|
/src/extensions/core/maskeditor.ts @brucew4yn3rp @trsommer @Comfy-Org/comfy_frontend_devs
|
||||||
|
|
||||||
# Link rendering
|
|
||||||
/src/renderer/core/canvas/links/ @benceruleanlu
|
|
||||||
|
|
||||||
# Node help system
|
|
||||||
/src/utils/nodeHelpUtil.ts @benceruleanlu
|
|
||||||
/src/stores/workspace/nodeHelpStore.ts @benceruleanlu
|
|
||||||
/src/services/nodeHelpService.ts @benceruleanlu
|
|
||||||
|
|
||||||
# Selection toolbox
|
|
||||||
/src/components/graph/selectionToolbox/ @Myestery
|
|
||||||
|
|
||||||
# Minimap
|
|
||||||
/src/renderer/extensions/minimap/ @jtydhr88
|
|
||||||
|
|
||||||
# Assets
|
|
||||||
/src/platform/assets/ @arjansingh
|
|
||||||
|
|
||||||
# Workflow Templates
|
|
||||||
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki
|
|
||||||
/src/components/templates/ @Myestery @christian-byrne @comfyui-wiki
|
|
||||||
|
|
||||||
# Mask Editor
|
|
||||||
/src/extensions/core/maskeditor.ts @trsommer @brucew4yn3rp
|
|
||||||
/src/extensions/core/maskEditorLayerFilenames.ts @trsommer @brucew4yn3rp
|
|
||||||
/src/extensions/core/maskEditorOld.ts @trsommer @brucew4yn3rp
|
|
||||||
|
|
||||||
# 3D
|
|
||||||
/src/extensions/core/load3d.ts @jtydhr88
|
|
||||||
/src/components/load3d/ @jtydhr88
|
|
||||||
|
|
||||||
# Manager
|
|
||||||
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer
|
|
||||||
|
|
||||||
# LLM Instructions (blank on purpose)
|
|
||||||
.claude/
|
|
||||||
.cursor/
|
|
||||||
.cursorrules
|
|
||||||
**/AGENTS.md
|
|
||||||
**/CLAUDE.md
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ Have another idea? Drop into Discord or open an issue, and let's chat!
|
|||||||
```
|
```
|
||||||
|
|
||||||
3. Configure environment (optional):
|
3. Configure environment (optional):
|
||||||
Create a `.env` file in the project root based on the provided [.env_example](.env_example) file.
|
Create a `.env` file in the project root based on the provided [.env.example](.env.example) file.
|
||||||
|
|
||||||
**Note about ports**: By default, the dev server expects the ComfyUI backend at `localhost:8188`. If your ComfyUI instance runs on a different port, update this in your `.env` file.
|
**Note about ports**: By default, the dev server expects the ComfyUI backend at `localhost:8188`. If your ComfyUI instance runs on a different port, update this in your `.env` file.
|
||||||
|
|
||||||
@@ -213,6 +213,12 @@ Here's how Claude Code can use the Playwright MCP server to inspect the interfac
|
|||||||
- `pnpm i` to install all dependencies
|
- `pnpm i` to install all dependencies
|
||||||
- `pnpm test:unit` to execute all unit tests
|
- `pnpm test:unit` to execute all unit tests
|
||||||
|
|
||||||
|
### Component Tests
|
||||||
|
|
||||||
|
Component tests verify Vue components in `src/components/`.
|
||||||
|
|
||||||
|
- `pnpm test:component` to execute all component tests
|
||||||
|
|
||||||
### Playwright Tests
|
### Playwright Tests
|
||||||
|
|
||||||
Playwright tests verify the whole app. See [browser_tests/README.md](browser_tests/README.md) for details.
|
Playwright tests verify the whole app. See [browser_tests/README.md](browser_tests/README.md) for details.
|
||||||
@@ -223,6 +229,7 @@ Before submitting a PR, ensure all tests pass:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm test:unit
|
pnpm test:unit
|
||||||
|
pnpm test:component
|
||||||
pnpm test:browser
|
pnpm test:browser
|
||||||
pnpm typecheck
|
pnpm typecheck
|
||||||
pnpm lint
|
pnpm lint
|
||||||
@@ -243,7 +250,7 @@ pnpm format
|
|||||||
|
|
||||||
### Styling
|
### Styling
|
||||||
- Use Tailwind CSS classes instead of custom CSS
|
- Use Tailwind CSS classes instead of custom CSS
|
||||||
- NEVER use `dark:` or `dark-theme:` tailwind variants. Instead use a semantic value from the `style.css` theme, e.g. `bg-node-component-surface`
|
- Follow the existing dark theme pattern: `dark-theme:` prefix (not `dark:`)
|
||||||
|
|
||||||
### Internationalization
|
### Internationalization
|
||||||
- All user-facing strings must use vue-i18n
|
- All user-facing strings must use vue-i18n
|
||||||
@@ -255,12 +262,12 @@ pnpm format
|
|||||||
The project supports three types of icons, all with automatic imports (no manual imports needed):
|
The project supports three types of icons, all with automatic imports (no manual imports needed):
|
||||||
|
|
||||||
1. **PrimeIcons** - Built-in PrimeVue icons using CSS classes: `<i class="pi pi-plus" />`
|
1. **PrimeIcons** - Built-in PrimeVue icons using CSS classes: `<i class="pi pi-plus" />`
|
||||||
2. **Iconify Icons** - 200,000+ icons from various libraries: `<i class="icon-[lucide--settings]" />`, `<i class="icon-[mdi--folder]" />`
|
2. **Iconify Icons** - 200,000+ icons from various libraries: `<i-lucide:settings />`, `<i-mdi:folder />`
|
||||||
3. **Custom Icons** - Your own SVG icons: `<i-comfy:workflow />`
|
3. **Custom Icons** - Your own SVG icons: `<i-comfy:workflow />`
|
||||||
|
|
||||||
Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `packages/design-system/src/icons/` and processed by `packages/design-system/src/iconCollection.ts` with automatic validation.
|
Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `src/assets/icons/custom/` and processed by `build/customIconCollection.ts` with automatic validation.
|
||||||
|
|
||||||
For detailed instructions and code examples, see [packages/design-system/src/icons/README.md](packages/design-system/src/icons/README.md).
|
For detailed instructions and code examples, see [src/assets/icons/README.md](src/assets/icons/README.md).
|
||||||
|
|
||||||
## Working with litegraph.js
|
## Working with litegraph.js
|
||||||
|
|
||||||
@@ -325,4 +332,4 @@ If you have questions about contributing:
|
|||||||
- Ask in our [Discord](https://discord.com/invite/comfyorg)
|
- Ask in our [Discord](https://discord.com/invite/comfyorg)
|
||||||
- Open a new issue for clarification
|
- Open a new issue for clarification
|
||||||
|
|
||||||
Thank you for contributing to ComfyUI Frontend!
|
Thank you for contributing to ComfyUI Frontend!
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import type { StorybookConfig } from '@storybook/vue3-vite'
|
|
||||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders'
|
|
||||||
import IconsResolver from 'unplugin-icons/resolver'
|
|
||||||
import Icons from 'unplugin-icons/vite'
|
|
||||||
import Components from 'unplugin-vue-components/vite'
|
|
||||||
import type { InlineConfig } from 'vite'
|
|
||||||
|
|
||||||
const config: StorybookConfig = {
|
|
||||||
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
|
||||||
addons: ['@storybook/addon-docs'],
|
|
||||||
framework: {
|
|
||||||
name: '@storybook/vue3-vite',
|
|
||||||
options: {}
|
|
||||||
},
|
|
||||||
staticDirs: [{ from: '../public', to: '/' }],
|
|
||||||
async viteFinal(config) {
|
|
||||||
// Use dynamic import to avoid CJS deprecation warning
|
|
||||||
const { mergeConfig } = await import('vite')
|
|
||||||
const { default: tailwindcss } = await import('@tailwindcss/vite')
|
|
||||||
|
|
||||||
// Filter out any plugins that might generate import maps
|
|
||||||
if (config.plugins) {
|
|
||||||
config.plugins = config.plugins
|
|
||||||
// Type guard: ensure we have valid plugin objects with names
|
|
||||||
.filter(
|
|
||||||
(plugin): plugin is NonNullable<typeof plugin> & { name: string } => {
|
|
||||||
return (
|
|
||||||
plugin !== null &&
|
|
||||||
plugin !== undefined &&
|
|
||||||
typeof plugin === 'object' &&
|
|
||||||
'name' in plugin &&
|
|
||||||
typeof plugin.name === 'string'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
// Business logic: filter out import-map plugins
|
|
||||||
.filter((plugin) => !plugin.name.includes('import-map'))
|
|
||||||
}
|
|
||||||
|
|
||||||
return mergeConfig(config, {
|
|
||||||
// Replace plugins entirely to avoid inheritance issues
|
|
||||||
plugins: [
|
|
||||||
// Only include plugins we explicitly need for Storybook
|
|
||||||
tailwindcss(),
|
|
||||||
Icons({
|
|
||||||
compiler: 'vue3',
|
|
||||||
customCollections: {
|
|
||||||
comfy: FileSystemIconLoader(
|
|
||||||
process.cwd() + '/../../packages/design-system/src/icons'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
Components({
|
|
||||||
dts: false, // Disable dts generation in Storybook
|
|
||||||
resolvers: [
|
|
||||||
IconsResolver({
|
|
||||||
customCollections: ['comfy']
|
|
||||||
})
|
|
||||||
],
|
|
||||||
dirs: [
|
|
||||||
process.cwd() + '/src/components',
|
|
||||||
process.cwd() + '/src/views'
|
|
||||||
],
|
|
||||||
deep: true,
|
|
||||||
extensions: ['vue'],
|
|
||||||
directoryAsNamespace: true
|
|
||||||
})
|
|
||||||
],
|
|
||||||
server: {
|
|
||||||
allowedHosts: true
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': process.cwd() + '/src',
|
|
||||||
'@frontend-locales': process.cwd() + '/../../src/locales'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
esbuild: {
|
|
||||||
// Prevent minification of identifiers to preserve _sfc_main
|
|
||||||
minifyIdentifiers: false,
|
|
||||||
keepNames: true
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
rollupOptions: {
|
|
||||||
// Disable tree-shaking for Storybook to prevent Vue SFC exports from being removed
|
|
||||||
treeshake: false,
|
|
||||||
onwarn: (warning, warn) => {
|
|
||||||
// Suppress specific warnings
|
|
||||||
if (
|
|
||||||
warning.code === 'UNUSED_EXTERNAL_IMPORT' &&
|
|
||||||
warning.message?.includes('resolveComponent')
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Suppress Storybook font asset warnings
|
|
||||||
if (
|
|
||||||
warning.code === 'UNRESOLVED_IMPORT' &&
|
|
||||||
warning.message?.includes('nunito-sans')
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
warn(warning)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chunkSizeWarningLimit: 1000
|
|
||||||
}
|
|
||||||
} satisfies InlineConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default config
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import { definePreset } from '@primevue/themes'
|
|
||||||
import Aura from '@primevue/themes/aura'
|
|
||||||
import { setup } from '@storybook/vue3'
|
|
||||||
import type { Preview, StoryContext, StoryFn } from '@storybook/vue3-vite'
|
|
||||||
import { createPinia } from 'pinia'
|
|
||||||
import 'primeicons/primeicons.css'
|
|
||||||
import PrimeVue from 'primevue/config'
|
|
||||||
import ConfirmationService from 'primevue/confirmationservice'
|
|
||||||
import ToastService from 'primevue/toastservice'
|
|
||||||
import Tooltip from 'primevue/tooltip'
|
|
||||||
|
|
||||||
import '@/assets/css/style.css'
|
|
||||||
import { i18n } from '@/i18n'
|
|
||||||
|
|
||||||
const ComfyUIPreset = definePreset(Aura, {
|
|
||||||
semantic: {
|
|
||||||
// @ts-expect-error prime type quirk
|
|
||||||
primary: Aura['primitive'].blue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setup((app) => {
|
|
||||||
app.directive('tooltip', Tooltip)
|
|
||||||
|
|
||||||
const pinia = createPinia()
|
|
||||||
|
|
||||||
app.use(pinia)
|
|
||||||
app.use(i18n)
|
|
||||||
app.use(PrimeVue, {
|
|
||||||
theme: {
|
|
||||||
preset: ComfyUIPreset,
|
|
||||||
options: {
|
|
||||||
prefix: 'p',
|
|
||||||
cssLayer: { name: 'primevue', order: 'primevue, tailwind-utilities' },
|
|
||||||
darkModeSelector: '.dark-theme, :root:has(.dark-theme)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
app.use(ConfirmationService)
|
|
||||||
app.use(ToastService)
|
|
||||||
})
|
|
||||||
|
|
||||||
export const withTheme = (Story: StoryFn, context: StoryContext) => {
|
|
||||||
const theme = context.globals.theme || 'light'
|
|
||||||
if (theme === 'dark') {
|
|
||||||
document.documentElement.classList.add('dark-theme')
|
|
||||||
document.body.classList.add('dark-theme')
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.remove('dark-theme')
|
|
||||||
document.body.classList.remove('dark-theme')
|
|
||||||
}
|
|
||||||
|
|
||||||
return Story(context.args, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
const preview: Preview = {
|
|
||||||
parameters: {
|
|
||||||
controls: {
|
|
||||||
matchers: { color: /(background|color)$/i, date: /Date$/i }
|
|
||||||
},
|
|
||||||
backgrounds: {
|
|
||||||
default: 'light',
|
|
||||||
values: [
|
|
||||||
{ name: 'light', value: '#ffffff' },
|
|
||||||
{ name: 'dark', value: '#0a0a0a' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
globalTypes: {
|
|
||||||
theme: {
|
|
||||||
name: 'Theme',
|
|
||||||
description: 'Global theme for components',
|
|
||||||
defaultValue: 'light',
|
|
||||||
toolbar: {
|
|
||||||
icon: 'circlehollow',
|
|
||||||
items: [
|
|
||||||
{ value: 'light', icon: 'sun', title: 'Light' },
|
|
||||||
{ value: 'dark', icon: 'moon', title: 'Dark' }
|
|
||||||
],
|
|
||||||
showName: true,
|
|
||||||
dynamicTitle: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
decorators: [withTheme]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default preview
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>ComfyUI</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="desktop-app"></div>
|
|
||||||
<script type="module" src="src/main.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@comfyorg/desktop-ui",
|
|
||||||
"version": "0.0.3",
|
|
||||||
"type": "module",
|
|
||||||
"nx": {
|
|
||||||
"tags": [
|
|
||||||
"scope:desktop",
|
|
||||||
"type:app"
|
|
||||||
],
|
|
||||||
"targets": {
|
|
||||||
"dev": {
|
|
||||||
"executor": "nx:run-commands",
|
|
||||||
"continuous": true,
|
|
||||||
"options": {
|
|
||||||
"cwd": "apps/desktop-ui",
|
|
||||||
"command": "vite --config vite.config.mts"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"serve": {
|
|
||||||
"executor": "nx:run-commands",
|
|
||||||
"continuous": true,
|
|
||||||
"options": {
|
|
||||||
"cwd": "apps/desktop-ui",
|
|
||||||
"command": "vite --config vite.config.mts"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"build": {
|
|
||||||
"executor": "nx:run-commands",
|
|
||||||
"cache": true,
|
|
||||||
"dependsOn": [
|
|
||||||
"^build"
|
|
||||||
],
|
|
||||||
"options": {
|
|
||||||
"cwd": "apps/desktop-ui",
|
|
||||||
"command": "vite build --config vite.config.mts"
|
|
||||||
},
|
|
||||||
"outputs": [
|
|
||||||
"{projectRoot}/dist"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"preview": {
|
|
||||||
"executor": "nx:run-commands",
|
|
||||||
"continuous": true,
|
|
||||||
"dependsOn": [
|
|
||||||
"build"
|
|
||||||
],
|
|
||||||
"options": {
|
|
||||||
"cwd": "apps/desktop-ui",
|
|
||||||
"command": "vite preview --config vite.config.mts"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"storybook": {
|
|
||||||
"executor": "nx:run-commands",
|
|
||||||
"continuous": true,
|
|
||||||
"options": {
|
|
||||||
"cwd": "apps/desktop-ui",
|
|
||||||
"command": "storybook dev -p 6007"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"build-storybook": {
|
|
||||||
"executor": "nx:run-commands",
|
|
||||||
"cache": true,
|
|
||||||
"options": {
|
|
||||||
"cwd": "apps/desktop-ui",
|
|
||||||
"command": "storybook build -o dist/storybook"
|
|
||||||
},
|
|
||||||
"outputs": [
|
|
||||||
"{projectRoot}/dist/storybook"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"executor": "nx:run-commands",
|
|
||||||
"cache": true,
|
|
||||||
"options": {
|
|
||||||
"cwd": "apps/desktop-ui",
|
|
||||||
"command": "eslint src --cache"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"typecheck": {
|
|
||||||
"executor": "nx:run-commands",
|
|
||||||
"cache": true,
|
|
||||||
"options": {
|
|
||||||
"cwd": "apps/desktop-ui",
|
|
||||||
"command": "vue-tsc --noEmit -p tsconfig.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"storybook": "storybook dev -p 6007",
|
|
||||||
"build-storybook": "storybook build -o dist/storybook"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@comfyorg/comfyui-electron-types": "0.4.73-0",
|
|
||||||
"@comfyorg/shared-frontend-utils": "workspace:*",
|
|
||||||
"@primevue/core": "catalog:",
|
|
||||||
"@primevue/themes": "catalog:",
|
|
||||||
"@vueuse/core": "catalog:",
|
|
||||||
"pinia": "catalog:",
|
|
||||||
"primeicons": "catalog:",
|
|
||||||
"primevue": "catalog:",
|
|
||||||
"vue": "catalog:",
|
|
||||||
"vue-i18n": "catalog:",
|
|
||||||
"vue-router": "catalog:"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tailwindcss/vite": "catalog:",
|
|
||||||
"@vitejs/plugin-vue": "catalog:",
|
|
||||||
"dotenv": "catalog:",
|
|
||||||
"unplugin-icons": "catalog:",
|
|
||||||
"unplugin-vue-components": "catalog:",
|
|
||||||
"vite": "catalog:",
|
|
||||||
"vite-plugin-html": "catalog:",
|
|
||||||
"vite-plugin-vue-devtools": "catalog:",
|
|
||||||
"vue-tsc": "catalog:"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<RouterView />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { RouterView } from 'vue-router'
|
|
||||||
</script>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
@import '@comfyorg/design-system/css/style.css';
|
|
||||||
|
|
||||||
#desktop-app {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
ref="rootEl"
|
|
||||||
class="relative overflow-hidden h-full w-full bg-neutral-900"
|
|
||||||
>
|
|
||||||
<div class="p-terminal rounded-none h-full w-full p-2">
|
|
||||||
<div ref="terminalEl" class="h-full terminal-host" />
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
v-tooltip.left="{
|
|
||||||
value: tooltipText,
|
|
||||||
showDelay: 300
|
|
||||||
}"
|
|
||||||
icon="pi pi-copy"
|
|
||||||
severity="secondary"
|
|
||||||
size="small"
|
|
||||||
:class="
|
|
||||||
cn('absolute top-2 right-8 transition-opacity', {
|
|
||||||
'opacity-0 pointer-events-none select-none': !isHovered
|
|
||||||
})
|
|
||||||
"
|
|
||||||
:aria-label="tooltipText"
|
|
||||||
@click="handleCopy"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useElementHover, useEventListener } from '@vueuse/core'
|
|
||||||
import type { IDisposable } from '@xterm/xterm'
|
|
||||||
import Button from 'primevue/button'
|
|
||||||
import type { Ref } from 'vue'
|
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
import { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
|
|
||||||
import { electronAPI, isElectron } from '@/utils/envUtil'
|
|
||||||
import { cn } from '@/utils/tailwindUtil'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
created: [ReturnType<typeof useTerminal>, Ref<HTMLElement | undefined>]
|
|
||||||
unmounted: []
|
|
||||||
}>()
|
|
||||||
const terminalEl = ref<HTMLElement | undefined>()
|
|
||||||
const rootEl = ref<HTMLElement | undefined>()
|
|
||||||
const hasSelection = ref(false)
|
|
||||||
|
|
||||||
const isHovered = useElementHover(rootEl)
|
|
||||||
|
|
||||||
const terminalData = useTerminal(terminalEl)
|
|
||||||
emit('created', terminalData, ref(rootEl))
|
|
||||||
|
|
||||||
const { terminal } = terminalData
|
|
||||||
let selectionDisposable: IDisposable | undefined
|
|
||||||
|
|
||||||
const tooltipText = computed(() => {
|
|
||||||
return hasSelection.value
|
|
||||||
? t('serverStart.copySelectionTooltip')
|
|
||||||
: t('serverStart.copyAllTooltip')
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleCopy = async () => {
|
|
||||||
const existingSelection = terminal.getSelection()
|
|
||||||
const shouldSelectAll = !existingSelection
|
|
||||||
if (shouldSelectAll) terminal.selectAll()
|
|
||||||
|
|
||||||
const selectedText = shouldSelectAll
|
|
||||||
? terminal.getSelection()
|
|
||||||
: existingSelection
|
|
||||||
|
|
||||||
if (selectedText) {
|
|
||||||
await navigator.clipboard.writeText(selectedText)
|
|
||||||
|
|
||||||
if (shouldSelectAll) {
|
|
||||||
terminal.clearSelection()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const showContextMenu = (event: MouseEvent) => {
|
|
||||||
event.preventDefault()
|
|
||||||
electronAPI()?.showContextMenu({ type: 'text' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isElectron()) {
|
|
||||||
useEventListener(terminalEl, 'contextmenu', showContextMenu)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
selectionDisposable = terminal.onSelectionChange(() => {
|
|
||||||
hasSelection.value = terminal.hasSelection()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
selectionDisposable?.dispose()
|
|
||||||
emit('unmounted')
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@reference '../../../../assets/css/style.css';
|
|
||||||
|
|
||||||
:deep(.p-terminal) .xterm {
|
|
||||||
@apply overflow-hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.p-terminal) .xterm-screen {
|
|
||||||
@apply bg-neutral-900 overflow-hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Select
|
|
||||||
:id="dropdownId"
|
|
||||||
v-model="selectedLocale"
|
|
||||||
:options="localeOptions"
|
|
||||||
option-label="label"
|
|
||||||
option-value="value"
|
|
||||||
:disabled="isSwitching"
|
|
||||||
:pt="dropdownPt"
|
|
||||||
:size="props.size"
|
|
||||||
class="language-selector"
|
|
||||||
@change="onLocaleChange"
|
|
||||||
>
|
|
||||||
<template #value="{ value }">
|
|
||||||
<span :class="valueClass">
|
|
||||||
<i class="pi pi-language" :class="iconClass" />
|
|
||||||
<span>{{ displayLabel(value as SupportedLocale) }}</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template #option="{ option }">
|
|
||||||
<span :class="optionClass">
|
|
||||||
<i class="pi pi-language" :class="iconClass" />
|
|
||||||
<span class="leading-none">{{ option.label }}</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</Select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import Select from 'primevue/select'
|
|
||||||
import type { SelectChangeEvent } from 'primevue/select'
|
|
||||||
import { computed, ref, watch } from 'vue'
|
|
||||||
|
|
||||||
import { i18n, loadLocale, st } from '@/i18n'
|
|
||||||
|
|
||||||
type VariantKey = 'dark' | 'light'
|
|
||||||
type SizeKey = 'small' | 'large'
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
variant?: VariantKey
|
|
||||||
size?: SizeKey
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
variant: 'dark',
|
|
||||||
size: 'small'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const dropdownId = `language-select-${Math.random().toString(36).slice(2)}`
|
|
||||||
|
|
||||||
const LOCALES = [
|
|
||||||
['en', 'English'],
|
|
||||||
['zh', '中文'],
|
|
||||||
['zh-TW', '繁體中文'],
|
|
||||||
['ru', 'Русский'],
|
|
||||||
['ja', '日本語'],
|
|
||||||
['ko', '한국어'],
|
|
||||||
['fr', 'Français'],
|
|
||||||
['es', 'Español'],
|
|
||||||
['ar', 'عربي'],
|
|
||||||
['tr', 'Türkçe']
|
|
||||||
] as const satisfies ReadonlyArray<[string, string]>
|
|
||||||
|
|
||||||
type SupportedLocale = (typeof LOCALES)[number][0]
|
|
||||||
|
|
||||||
const SIZE_PRESETS = {
|
|
||||||
large: {
|
|
||||||
wrapper: 'px-3 py-1 min-w-[7rem]',
|
|
||||||
gap: 'gap-2',
|
|
||||||
valueText: 'text-xs',
|
|
||||||
optionText: 'text-sm',
|
|
||||||
icon: 'text-sm'
|
|
||||||
},
|
|
||||||
small: {
|
|
||||||
wrapper: 'px-2 py-0.5 min-w-[5rem]',
|
|
||||||
gap: 'gap-1',
|
|
||||||
valueText: 'text-[0.65rem]',
|
|
||||||
optionText: 'text-xs',
|
|
||||||
icon: 'text-xs'
|
|
||||||
}
|
|
||||||
} as const satisfies Record<SizeKey, Record<string, string>>
|
|
||||||
|
|
||||||
const VARIANT_PRESETS = {
|
|
||||||
light: {
|
|
||||||
root: 'bg-white/80 border border-neutral-200 text-neutral-700 rounded-full shadow-sm backdrop-blur hover:border-neutral-400 transition-colors focus-visible:ring-offset-2 focus-visible:ring-offset-white',
|
|
||||||
trigger: 'text-neutral-500 hover:text-neutral-700',
|
|
||||||
item: 'text-neutral-700 bg-transparent hover:bg-neutral-100 focus-visible:outline-none',
|
|
||||||
valueText: 'text-neutral-600',
|
|
||||||
optionText: 'text-neutral-600',
|
|
||||||
icon: 'text-neutral-500'
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
root: 'bg-neutral-900/70 border border-neutral-700 text-neutral-200 rounded-full shadow-sm backdrop-blur hover:border-neutral-500 transition-colors focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-900',
|
|
||||||
trigger: 'text-neutral-400 hover:text-neutral-200',
|
|
||||||
item: 'text-neutral-200 bg-transparent hover:bg-neutral-800/80 focus-visible:outline-none',
|
|
||||||
valueText: 'text-neutral-100',
|
|
||||||
optionText: 'text-neutral-100',
|
|
||||||
icon: 'text-neutral-300'
|
|
||||||
}
|
|
||||||
} as const satisfies Record<VariantKey, Record<string, string>>
|
|
||||||
|
|
||||||
const selectedLocale = ref<string>(i18n.global.locale.value)
|
|
||||||
const isSwitching = ref(false)
|
|
||||||
|
|
||||||
const sizePreset = computed(() => SIZE_PRESETS[props.size as SizeKey])
|
|
||||||
const variantPreset = computed(
|
|
||||||
() => VARIANT_PRESETS[props.variant as VariantKey]
|
|
||||||
)
|
|
||||||
|
|
||||||
const dropdownPt = computed(() => ({
|
|
||||||
root: {
|
|
||||||
class: `${variantPreset.value.root} ${sizePreset.value.wrapper}`
|
|
||||||
},
|
|
||||||
trigger: {
|
|
||||||
class: variantPreset.value.trigger
|
|
||||||
},
|
|
||||||
item: {
|
|
||||||
class: `${variantPreset.value.item} ${sizePreset.value.optionText}`
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const valueClass = computed(() =>
|
|
||||||
[
|
|
||||||
'flex items-center font-medium uppercase tracking-wide leading-tight',
|
|
||||||
sizePreset.value.gap,
|
|
||||||
sizePreset.value.valueText,
|
|
||||||
variantPreset.value.valueText
|
|
||||||
].join(' ')
|
|
||||||
)
|
|
||||||
|
|
||||||
const optionClass = computed(() =>
|
|
||||||
[
|
|
||||||
'flex items-center leading-tight',
|
|
||||||
sizePreset.value.gap,
|
|
||||||
variantPreset.value.optionText,
|
|
||||||
sizePreset.value.optionText
|
|
||||||
].join(' ')
|
|
||||||
)
|
|
||||||
|
|
||||||
const iconClass = computed(() =>
|
|
||||||
[sizePreset.value.icon, variantPreset.value.icon].join(' ')
|
|
||||||
)
|
|
||||||
|
|
||||||
const localeOptions = computed(() =>
|
|
||||||
LOCALES.map(([value, fallback]) => ({
|
|
||||||
value,
|
|
||||||
label: st(`settings.Comfy_Locale.options.${value}`, fallback)
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
const labelLookup = computed(() =>
|
|
||||||
localeOptions.value.reduce<Record<string, string>>((acc, option) => {
|
|
||||||
acc[option.value] = option.label
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
)
|
|
||||||
|
|
||||||
function displayLabel(locale?: SupportedLocale) {
|
|
||||||
if (!locale) {
|
|
||||||
return st('settings.Comfy_Locale.name', 'Language')
|
|
||||||
}
|
|
||||||
|
|
||||||
return labelLookup.value[locale] ?? locale
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => i18n.global.locale.value,
|
|
||||||
(newLocale) => {
|
|
||||||
if (newLocale !== selectedLocale.value) {
|
|
||||||
selectedLocale.value = newLocale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
async function onLocaleChange(event: SelectChangeEvent) {
|
|
||||||
const nextLocale = event.value as SupportedLocale | undefined
|
|
||||||
|
|
||||||
if (!nextLocale || nextLocale === i18n.global.locale.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isSwitching.value = true
|
|
||||||
try {
|
|
||||||
await loadLocale(nextLocale)
|
|
||||||
i18n.global.locale.value = nextLocale
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to change locale to "${nextLocale}"`, error)
|
|
||||||
selectedLocale.value = i18n.global.locale.value
|
|
||||||
} finally {
|
|
||||||
isSwitching.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@reference '../../assets/css/style.css';
|
|
||||||
|
|
||||||
:deep(.p-dropdown-panel .p-dropdown-item) {
|
|
||||||
@apply transition-colors;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.p-dropdown) {
|
|
||||||
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-yellow/60 focus-visible:ring-offset-2;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div :class="wrapperClass">
|
|
||||||
<div class="grid grid-rows-2 gap-8">
|
|
||||||
<!-- Top container: Logo -->
|
|
||||||
<div class="flex items-end justify-center">
|
|
||||||
<img
|
|
||||||
src="/assets/images/comfy-brand-mark.svg"
|
|
||||||
:alt="t('g.logoAlt')"
|
|
||||||
class="w-60"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- Bottom container: Progress and text -->
|
|
||||||
<div class="flex flex-col items-center justify-center gap-4">
|
|
||||||
<ProgressBar
|
|
||||||
v-if="!hideProgress"
|
|
||||||
:mode="progressMode"
|
|
||||||
:value="progressPercentage ?? 0"
|
|
||||||
:show-value="false"
|
|
||||||
class="w-90 h-2 mt-8"
|
|
||||||
:pt="{ value: { class: 'bg-brand-yellow' } }"
|
|
||||||
/>
|
|
||||||
<h1 v-if="title" class="font-inter font-bold text-3xl text-neutral-300">
|
|
||||||
{{ title }}
|
|
||||||
</h1>
|
|
||||||
<p v-if="statusText" class="text-lg text-neutral-400">
|
|
||||||
{{ statusText }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import ProgressBar from 'primevue/progressbar'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
/** Props for the StartupDisplay component */
|
|
||||||
interface StartupDisplayProps {
|
|
||||||
/** Progress: 0-100 for determinate, undefined for indeterminate */
|
|
||||||
progressPercentage?: number
|
|
||||||
/** Main title text */
|
|
||||||
title?: string
|
|
||||||
/** Status text shown below the title */
|
|
||||||
statusText?: string
|
|
||||||
/** Hide the progress bar */
|
|
||||||
hideProgress?: boolean
|
|
||||||
/** Use full screen wrapper (default: true) */
|
|
||||||
fullScreen?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
progressPercentage,
|
|
||||||
title,
|
|
||||||
statusText,
|
|
||||||
hideProgress = false,
|
|
||||||
fullScreen = true
|
|
||||||
} = defineProps<StartupDisplayProps>()
|
|
||||||
|
|
||||||
const progressMode = computed(() =>
|
|
||||||
progressPercentage === undefined ? 'indeterminate' : 'determinate'
|
|
||||||
)
|
|
||||||
|
|
||||||
const wrapperClass = computed(() =>
|
|
||||||
fullScreen
|
|
||||||
? 'flex items-center justify-center min-h-screen'
|
|
||||||
: 'flex items-center justify-center'
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
<template>
|
|
||||||
<IconField class="w-full">
|
|
||||||
<InputText
|
|
||||||
v-bind="$attrs"
|
|
||||||
:model-value="internalValue"
|
|
||||||
class="w-full"
|
|
||||||
:invalid="validationState === ValidationState.INVALID"
|
|
||||||
@update:model-value="handleInput"
|
|
||||||
@blur="handleBlur"
|
|
||||||
/>
|
|
||||||
<InputIcon
|
|
||||||
:class="{
|
|
||||||
'pi pi-spin pi-spinner text-neutral-400':
|
|
||||||
validationState === ValidationState.LOADING,
|
|
||||||
'pi pi-check text-green-500 cursor-pointer':
|
|
||||||
validationState === ValidationState.VALID,
|
|
||||||
'pi pi-times text-red-500 cursor-pointer':
|
|
||||||
validationState === ValidationState.INVALID
|
|
||||||
}"
|
|
||||||
@click="validateUrl(props.modelValue)"
|
|
||||||
/>
|
|
||||||
</IconField>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { isValidUrl } from '@comfyorg/shared-frontend-utils/formatUtil'
|
|
||||||
import { checkUrlReachable } from '@comfyorg/shared-frontend-utils/networkUtil'
|
|
||||||
import IconField from 'primevue/iconfield'
|
|
||||||
import InputIcon from 'primevue/inputicon'
|
|
||||||
import InputText from 'primevue/inputtext'
|
|
||||||
import { onMounted, ref, watch } from 'vue'
|
|
||||||
|
|
||||||
import { ValidationState } from '@/utils/validationUtil'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
modelValue: string
|
|
||||||
validateUrlFn?: (url: string) => Promise<boolean>
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
'update:modelValue': [value: string]
|
|
||||||
'state-change': [state: ValidationState]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const validationState = ref<ValidationState>(ValidationState.IDLE)
|
|
||||||
|
|
||||||
const cleanInput = (value: string): string =>
|
|
||||||
value ? value.replace(/\s+/g, '') : ''
|
|
||||||
|
|
||||||
// Add internal value state
|
|
||||||
const internalValue = ref(cleanInput(props.modelValue))
|
|
||||||
|
|
||||||
// Watch for external modelValue changes
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
async (newValue: string) => {
|
|
||||||
internalValue.value = cleanInput(newValue)
|
|
||||||
await validateUrl(newValue)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(validationState, (newState) => {
|
|
||||||
emit('state-change', newState)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Validate on mount
|
|
||||||
onMounted(async () => {
|
|
||||||
await validateUrl(props.modelValue)
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleInput = (value: string | undefined) => {
|
|
||||||
// Update internal value without emitting
|
|
||||||
internalValue.value = cleanInput(value ?? '')
|
|
||||||
// Reset validation state when user types
|
|
||||||
validationState.value = ValidationState.IDLE
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBlur = async () => {
|
|
||||||
const input = cleanInput(internalValue.value)
|
|
||||||
|
|
||||||
let normalizedUrl = input
|
|
||||||
try {
|
|
||||||
const url = new URL(input)
|
|
||||||
normalizedUrl = url.toString()
|
|
||||||
} catch {
|
|
||||||
// If URL parsing fails, just use the cleaned input
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit the update only on blur
|
|
||||||
emit('update:modelValue', normalizedUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default validation implementation
|
|
||||||
const defaultValidateUrl = async (url: string): Promise<boolean> => {
|
|
||||||
if (!isValidUrl(url)) return false
|
|
||||||
try {
|
|
||||||
return await checkUrlReachable(url)
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateUrl = async (value: string) => {
|
|
||||||
if (validationState.value === ValidationState.LOADING) return
|
|
||||||
|
|
||||||
const url = cleanInput(value)
|
|
||||||
|
|
||||||
// Reset state
|
|
||||||
validationState.value = ValidationState.IDLE
|
|
||||||
|
|
||||||
// Skip validation if empty
|
|
||||||
if (!url) return
|
|
||||||
|
|
||||||
validationState.value = ValidationState.LOADING
|
|
||||||
try {
|
|
||||||
const isValid = await (props.validateUrlFn ?? defaultValidateUrl)(url)
|
|
||||||
validationState.value = isValid
|
|
||||||
? ValidationState.VALID
|
|
||||||
: ValidationState.INVALID
|
|
||||||
} catch {
|
|
||||||
validationState.value = ValidationState.INVALID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add inheritAttrs option to prevent attrs from being applied to root element
|
|
||||||
defineOptions({
|
|
||||||
inheritAttrs: false
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="mx-auto grid h-[40rem] w-full max-w-3xl grid-rows-[1fr_auto_auto_1fr] select-none"
|
|
||||||
>
|
|
||||||
<h2 class="text-center font-inter text-3xl font-bold text-neutral-100">
|
|
||||||
{{ $t('install.gpuPicker.title') }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<!-- GPU Selection buttons - takes up remaining space and centers content -->
|
|
||||||
<div class="flex flex-1 items-center justify-center gap-8">
|
|
||||||
<!-- Apple Metal / NVIDIA -->
|
|
||||||
<HardwareOption
|
|
||||||
v-if="platform === 'darwin'"
|
|
||||||
:image-path="'./assets/images/apple-mps-logo.png'"
|
|
||||||
placeholder-text="Apple Metal"
|
|
||||||
subtitle="Apple Metal"
|
|
||||||
:value="'mps'"
|
|
||||||
:selected="selected === 'mps'"
|
|
||||||
:recommended="true"
|
|
||||||
@click="pickGpu('mps')"
|
|
||||||
/>
|
|
||||||
<HardwareOption
|
|
||||||
v-else
|
|
||||||
:image-path="'./assets/images/nvidia-logo-square.jpg'"
|
|
||||||
placeholder-text="NVIDIA"
|
|
||||||
:subtitle="$t('install.gpuPicker.nvidiaSubtitle')"
|
|
||||||
:value="'nvidia'"
|
|
||||||
:selected="selected === 'nvidia'"
|
|
||||||
:recommended="true"
|
|
||||||
@click="pickGpu('nvidia')"
|
|
||||||
/>
|
|
||||||
<!-- CPU -->
|
|
||||||
<HardwareOption
|
|
||||||
placeholder-text="CPU"
|
|
||||||
:subtitle="$t('install.gpuPicker.cpuSubtitle')"
|
|
||||||
:value="'cpu'"
|
|
||||||
:selected="selected === 'cpu'"
|
|
||||||
@click="pickGpu('cpu')"
|
|
||||||
/>
|
|
||||||
<!-- Manual Install -->
|
|
||||||
<HardwareOption
|
|
||||||
placeholder-text="Manual Install"
|
|
||||||
:subtitle="$t('install.gpuPicker.manualSubtitle')"
|
|
||||||
:value="'unsupported'"
|
|
||||||
:selected="selected === 'unsupported'"
|
|
||||||
@click="pickGpu('unsupported')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-16 px-24 pt-12">
|
|
||||||
<div v-show="showRecommendedBadge" class="flex items-center gap-2">
|
|
||||||
<Tag
|
|
||||||
:value="$t('install.gpuPicker.recommended')"
|
|
||||||
class="rounded-full bg-neutral-300 px-2 py-[1px] text-sm font-bold text-neutral-900"
|
|
||||||
/>
|
|
||||||
<i class="icon-[lucide--badge-check] text-lg text-neutral-300" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="px-24 text-neutral-300">
|
|
||||||
<p v-show="descriptionText" class="leading-relaxed">
|
|
||||||
{{ descriptionText }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { TorchDeviceType } from '@comfyorg/comfyui-electron-types'
|
|
||||||
import Tag from 'primevue/tag'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
import HardwareOption from '@/components/install/HardwareOption.vue'
|
|
||||||
import { st } from '@/i18n'
|
|
||||||
import { electronAPI } from '@/utils/envUtil'
|
|
||||||
|
|
||||||
const selected = defineModel<TorchDeviceType | null>('device', {
|
|
||||||
required: true
|
|
||||||
})
|
|
||||||
|
|
||||||
const electron = electronAPI()
|
|
||||||
const platform = electron.getPlatform()
|
|
||||||
|
|
||||||
const showRecommendedBadge = computed(
|
|
||||||
() => selected.value === 'mps' || selected.value === 'nvidia'
|
|
||||||
)
|
|
||||||
|
|
||||||
const descriptionKeys = {
|
|
||||||
mps: 'appleMetal',
|
|
||||||
nvidia: 'nvidia',
|
|
||||||
cpu: 'cpu',
|
|
||||||
unsupported: 'manual'
|
|
||||||
} as const
|
|
||||||
|
|
||||||
const descriptionText = computed(() => {
|
|
||||||
const key = selected.value ? descriptionKeys[selected.value] : undefined
|
|
||||||
return st(`install.gpuPicker.${key}Description`, '')
|
|
||||||
})
|
|
||||||
|
|
||||||
const pickGpu = (value: TorchDeviceType) => {
|
|
||||||
selected.value = value
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
// eslint-disable-next-line storybook/no-renderer-packages
|
|
||||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
||||||
|
|
||||||
import HardwareOption from './HardwareOption.vue'
|
|
||||||
|
|
||||||
const meta: Meta<typeof HardwareOption> = {
|
|
||||||
title: 'Desktop/Components/HardwareOption',
|
|
||||||
component: HardwareOption,
|
|
||||||
parameters: {
|
|
||||||
layout: 'centered',
|
|
||||||
backgrounds: {
|
|
||||||
default: 'dark',
|
|
||||||
values: [{ name: 'dark', value: '#1a1a1a' }]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
argTypes: {
|
|
||||||
selected: { control: 'boolean' },
|
|
||||||
imagePath: { control: 'text' },
|
|
||||||
placeholderText: { control: 'text' },
|
|
||||||
subtitle: { control: 'text' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default meta
|
|
||||||
type Story = StoryObj<typeof meta>
|
|
||||||
|
|
||||||
export const AppleMetalSelected: Story = {
|
|
||||||
args: {
|
|
||||||
imagePath: '/assets/images/apple-mps-logo.png',
|
|
||||||
placeholderText: 'Apple Metal',
|
|
||||||
subtitle: 'Apple Metal',
|
|
||||||
value: 'mps',
|
|
||||||
selected: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AppleMetalUnselected: Story = {
|
|
||||||
args: {
|
|
||||||
imagePath: '/assets/images/apple-mps-logo.png',
|
|
||||||
placeholderText: 'Apple Metal',
|
|
||||||
subtitle: 'Apple Metal',
|
|
||||||
value: 'mps',
|
|
||||||
selected: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CPUOption: Story = {
|
|
||||||
args: {
|
|
||||||
placeholderText: 'CPU',
|
|
||||||
subtitle: 'Subtitle',
|
|
||||||
value: 'cpu',
|
|
||||||
selected: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ManualInstall: Story = {
|
|
||||||
args: {
|
|
||||||
placeholderText: 'Manual Install',
|
|
||||||
subtitle: 'Subtitle',
|
|
||||||
value: 'unsupported',
|
|
||||||
selected: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NvidiaSelected: Story = {
|
|
||||||
args: {
|
|
||||||
imagePath: '/assets/images/nvidia-logo-square.jpg',
|
|
||||||
placeholderText: 'NVIDIA',
|
|
||||||
subtitle: 'NVIDIA',
|
|
||||||
value: 'nvidia',
|
|
||||||
selected: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="relative">
|
|
||||||
<!-- Recommended Badge -->
|
|
||||||
<button
|
|
||||||
:class="
|
|
||||||
cn(
|
|
||||||
'hardware-option w-[170px] h-[190px] p-5 flex flex-col items-center rounded-3xl transition-all duration-200 bg-neutral-900/70 border-4',
|
|
||||||
selected ? 'border-solid border-brand-yellow' : 'border-transparent'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
@click="$emit('click')"
|
|
||||||
>
|
|
||||||
<!-- Icon/Logo Area - Rounded square container -->
|
|
||||||
<div
|
|
||||||
class="icon-container w-[110px] h-[110px] shrink-0 rounded-2xl bg-neutral-800 flex items-center justify-center overflow-hidden"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
v-if="imagePath"
|
|
||||||
:src="imagePath"
|
|
||||||
:alt="placeholderText"
|
|
||||||
class="w-full h-full object-cover"
|
|
||||||
style="object-position: 57% center"
|
|
||||||
draggable="false"
|
|
||||||
/>
|
|
||||||
<span v-else class="text-xl font-medium text-neutral-400">
|
|
||||||
{{ placeholderText }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Text Content -->
|
|
||||||
<div v-if="subtitle" class="text-center mt-4">
|
|
||||||
<div class="text-sm text-neutral-500">{{ subtitle }}</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { TorchDeviceType } from '@comfyorg/comfyui-electron-types'
|
|
||||||
|
|
||||||
import { cn } from '@/utils/tailwindUtil'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
imagePath?: string
|
|
||||||
placeholderText: string
|
|
||||||
subtitle?: string
|
|
||||||
value: TorchDeviceType
|
|
||||||
selected?: boolean
|
|
||||||
recommended?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
defineProps<Props>()
|
|
||||||
|
|
||||||
defineEmits<{ click: [] }>()
|
|
||||||
</script>
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="grid grid-cols-[1fr_auto_1fr] items-center gap-4">
|
|
||||||
<!-- Back button -->
|
|
||||||
<Button
|
|
||||||
v-if="currentStep !== '1'"
|
|
||||||
:label="$t('g.back')"
|
|
||||||
severity="secondary"
|
|
||||||
icon="pi pi-arrow-left"
|
|
||||||
class="font-inter rounded-lg border-0 px-6 py-2 justify-self-start"
|
|
||||||
@click="$emit('previous')"
|
|
||||||
/>
|
|
||||||
<div v-else></div>
|
|
||||||
|
|
||||||
<!-- Step indicators in center -->
|
|
||||||
<StepList class="flex justify-center items-center gap-3 select-none">
|
|
||||||
<Step value="1" :pt="stepPassthrough">
|
|
||||||
{{ $t('install.gpu') }}
|
|
||||||
</Step>
|
|
||||||
<Step value="2" :disabled="disableLocationStep" :pt="stepPassthrough">
|
|
||||||
{{ $t('install.installLocation') }}
|
|
||||||
</Step>
|
|
||||||
<Step value="3" :disabled="disableSettingsStep" :pt="stepPassthrough">
|
|
||||||
{{ $t('install.desktopSettings') }}
|
|
||||||
</Step>
|
|
||||||
</StepList>
|
|
||||||
|
|
||||||
<!-- Next/Install button -->
|
|
||||||
<Button
|
|
||||||
:label="currentStep !== '3' ? $t('g.next') : $t('g.install')"
|
|
||||||
class="px-8 py-2 bg-brand-yellow hover:bg-brand-yellow/90 font-inter rounded-lg border-0 transition-colors justify-self-end"
|
|
||||||
:pt="{
|
|
||||||
label: { class: 'text-neutral-900 font-inter font-black' }
|
|
||||||
}"
|
|
||||||
:disabled="!canProceed"
|
|
||||||
@click="currentStep !== '3' ? $emit('next') : $emit('install')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { PassThrough } from '@primevue/core'
|
|
||||||
import Button from 'primevue/button'
|
|
||||||
import Step, { type StepPassThroughOptions } from 'primevue/step'
|
|
||||||
import StepList from 'primevue/steplist'
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
/** Current step index as string ('1', '2', '3', '4') */
|
|
||||||
currentStep: string
|
|
||||||
/** Whether the user can proceed to the next step */
|
|
||||||
canProceed: boolean
|
|
||||||
/** Whether the location step should be disabled */
|
|
||||||
disableLocationStep: boolean
|
|
||||||
/** Whether the migration step should be disabled */
|
|
||||||
disableMigrationStep: boolean
|
|
||||||
/** Whether the settings step should be disabled */
|
|
||||||
disableSettingsStep: boolean
|
|
||||||
}>()
|
|
||||||
|
|
||||||
defineEmits<{
|
|
||||||
previous: []
|
|
||||||
next: []
|
|
||||||
install: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const stepPassthrough: PassThrough<StepPassThroughOptions> = {
|
|
||||||
root: { class: 'flex-none p-0 m-0' },
|
|
||||||
header: ({ context }) => ({
|
|
||||||
class: [
|
|
||||||
'h-2.5 p-0 m-0 border-0 rounded-full transition-all duration-300',
|
|
||||||
context.active
|
|
||||||
? 'bg-brand-yellow w-8 rounded-sm'
|
|
||||||
: 'bg-neutral-700 w-2.5',
|
|
||||||
context.disabled ? 'opacity-60 cursor-not-allowed' : ''
|
|
||||||
].join(' ')
|
|
||||||
}),
|
|
||||||
number: { class: 'hidden' },
|
|
||||||
title: { class: 'hidden' }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
// eslint-disable-next-line storybook/no-renderer-packages
|
|
||||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
import InstallLocationPicker from './InstallLocationPicker.vue'
|
|
||||||
|
|
||||||
const meta: Meta<typeof InstallLocationPicker> = {
|
|
||||||
title: 'Desktop/Components/InstallLocationPicker',
|
|
||||||
component: InstallLocationPicker,
|
|
||||||
parameters: {
|
|
||||||
layout: 'padded',
|
|
||||||
backgrounds: {
|
|
||||||
default: 'dark',
|
|
||||||
values: [
|
|
||||||
{ name: 'dark', value: '#0a0a0a' },
|
|
||||||
{ name: 'neutral-900', value: '#171717' },
|
|
||||||
{ name: 'neutral-950', value: '#0a0a0a' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
decorators: [
|
|
||||||
() => {
|
|
||||||
// Mock electron API
|
|
||||||
;(window as any).electronAPI = {
|
|
||||||
getSystemPaths: () =>
|
|
||||||
Promise.resolve({
|
|
||||||
defaultInstallPath: '/Users/username/ComfyUI'
|
|
||||||
}),
|
|
||||||
validateInstallPath: () =>
|
|
||||||
Promise.resolve({
|
|
||||||
isValid: true,
|
|
||||||
exists: false,
|
|
||||||
canWrite: true,
|
|
||||||
freeSpace: 100000000000,
|
|
||||||
requiredSpace: 10000000000,
|
|
||||||
isNonDefaultDrive: false
|
|
||||||
}),
|
|
||||||
validateComfyUISource: () =>
|
|
||||||
Promise.resolve({
|
|
||||||
isValid: true
|
|
||||||
}),
|
|
||||||
showDirectoryPicker: () => Promise.resolve('/Users/username/ComfyUI')
|
|
||||||
}
|
|
||||||
return { template: '<story />' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default meta
|
|
||||||
type Story = StoryObj<typeof meta>
|
|
||||||
|
|
||||||
// Default story with accordion expanded
|
|
||||||
export const Default: Story = {
|
|
||||||
render: (args) => ({
|
|
||||||
components: { InstallLocationPicker },
|
|
||||||
setup() {
|
|
||||||
const installPath = ref('/Users/username/ComfyUI')
|
|
||||||
const pathError = ref('')
|
|
||||||
const migrationSourcePath = ref('/Users/username/ComfyUI-old')
|
|
||||||
const migrationItemIds = ref<string[]>(['models', 'custom_nodes'])
|
|
||||||
|
|
||||||
return {
|
|
||||||
args,
|
|
||||||
installPath,
|
|
||||||
pathError,
|
|
||||||
migrationSourcePath,
|
|
||||||
migrationItemIds
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<div class="min-h-screen bg-neutral-950 p-8">
|
|
||||||
<InstallLocationPicker
|
|
||||||
v-model:installPath="installPath"
|
|
||||||
v-model:pathError="pathError"
|
|
||||||
v-model:migrationSourcePath="migrationSourcePath"
|
|
||||||
v-model:migrationItemIds="migrationItemIds"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Story with different background to test transparency
|
|
||||||
export const OnNeutral900: Story = {
|
|
||||||
render: (args) => ({
|
|
||||||
components: { InstallLocationPicker },
|
|
||||||
setup() {
|
|
||||||
const installPath = ref('/Users/username/ComfyUI')
|
|
||||||
const pathError = ref('')
|
|
||||||
const migrationSourcePath = ref('/Users/username/ComfyUI-old')
|
|
||||||
const migrationItemIds = ref<string[]>(['models', 'custom_nodes'])
|
|
||||||
|
|
||||||
return {
|
|
||||||
args,
|
|
||||||
installPath,
|
|
||||||
pathError,
|
|
||||||
migrationSourcePath,
|
|
||||||
migrationItemIds
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<div class="min-h-screen bg-neutral-900 p-8">
|
|
||||||
<InstallLocationPicker
|
|
||||||
v-model:installPath="installPath"
|
|
||||||
v-model:pathError="pathError"
|
|
||||||
v-model:migrationSourcePath="migrationSourcePath"
|
|
||||||
v-model:migrationItemIds="migrationItemIds"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Story with debug overlay showing background colors
|
|
||||||
export const DebugBackgrounds: Story = {
|
|
||||||
render: (args) => ({
|
|
||||||
components: { InstallLocationPicker },
|
|
||||||
setup() {
|
|
||||||
const installPath = ref('/Users/username/ComfyUI')
|
|
||||||
const pathError = ref('')
|
|
||||||
const migrationSourcePath = ref('/Users/username/ComfyUI-old')
|
|
||||||
const migrationItemIds = ref<string[]>(['models', 'custom_nodes'])
|
|
||||||
|
|
||||||
return {
|
|
||||||
args,
|
|
||||||
installPath,
|
|
||||||
pathError,
|
|
||||||
migrationSourcePath,
|
|
||||||
migrationItemIds
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<div class="min-h-screen bg-neutral-950 p-8 relative">
|
|
||||||
<div class="absolute top-4 right-4 text-white text-xs space-y-2 z-50">
|
|
||||||
<div>Parent bg: neutral-950 (#0a0a0a)</div>
|
|
||||||
<div>Accordion content: bg-transparent</div>
|
|
||||||
<div>Migration options: bg-transparent + p-4 rounded-lg</div>
|
|
||||||
</div>
|
|
||||||
<InstallLocationPicker
|
|
||||||
v-model:installPath="installPath"
|
|
||||||
v-model:pathError="pathError"
|
|
||||||
v-model:migrationSourcePath="migrationSourcePath"
|
|
||||||
v-model:migrationItemIds="migrationItemIds"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,312 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col gap-8 w-full max-w-3xl mx-auto select-none">
|
|
||||||
<!-- Installation Path Section -->
|
|
||||||
<div class="grow flex flex-col gap-6 text-neutral-300">
|
|
||||||
<h2 class="font-inter font-bold text-3xl text-neutral-100 text-center">
|
|
||||||
{{ $t('install.locationPicker.title') }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p class="text-center text-neutral-400 px-12">
|
|
||||||
{{ $t('install.locationPicker.subtitle') }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Path Input -->
|
|
||||||
<div class="flex gap-2 px-12">
|
|
||||||
<InputText
|
|
||||||
v-model="installPath"
|
|
||||||
:placeholder="$t('install.locationPicker.pathPlaceholder')"
|
|
||||||
class="flex-1 bg-neutral-800/50 border-neutral-700 text-neutral-200 placeholder:text-neutral-500"
|
|
||||||
:class="{ 'p-invalid': pathError }"
|
|
||||||
@update:model-value="validatePath"
|
|
||||||
@focus="onFocus"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
icon="pi pi-folder-open"
|
|
||||||
severity="secondary"
|
|
||||||
class="bg-neutral-700 hover:bg-neutral-600 border-0"
|
|
||||||
@click="browsePath"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Error Messages -->
|
|
||||||
<div v-if="pathError || pathExists || nonDefaultDrive" class="px-12">
|
|
||||||
<Message
|
|
||||||
v-if="pathError"
|
|
||||||
severity="error"
|
|
||||||
class="whitespace-pre-line w-full"
|
|
||||||
>
|
|
||||||
{{ pathError }}
|
|
||||||
</Message>
|
|
||||||
<Message v-if="pathExists" severity="warn" class="w-full">
|
|
||||||
{{ $t('install.pathExists') }}
|
|
||||||
</Message>
|
|
||||||
<Message v-if="nonDefaultDrive" severity="warn" class="w-full">
|
|
||||||
{{ $t('install.nonDefaultDrive') }}
|
|
||||||
</Message>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Collapsible Sections using PrimeVue Accordion -->
|
|
||||||
<Accordion
|
|
||||||
v-model:value="activeAccordionIndex"
|
|
||||||
:multiple="true"
|
|
||||||
class="location-picker-accordion"
|
|
||||||
:pt="{
|
|
||||||
root: 'bg-transparent border-0',
|
|
||||||
panel: {
|
|
||||||
root: 'border-0 mb-0'
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
root: 'border-0',
|
|
||||||
content:
|
|
||||||
'text-neutral-400 hover:text-neutral-300 px-4 py-2 flex items-center gap-3',
|
|
||||||
toggleicon: 'text-xs order-first mr-0'
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
root: 'bg-transparent border-0',
|
|
||||||
content: 'text-neutral-500 text-sm pl-11 pb-3 pt-0'
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<AccordionPanel value="0">
|
|
||||||
<AccordionHeader>
|
|
||||||
{{ $t('install.locationPicker.migrateFromExisting') }}
|
|
||||||
</AccordionHeader>
|
|
||||||
<AccordionContent>
|
|
||||||
<MigrationPicker
|
|
||||||
v-model:source-path="migrationSourcePath"
|
|
||||||
v-model:migration-item-ids="migrationItemIds"
|
|
||||||
/>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionPanel>
|
|
||||||
|
|
||||||
<AccordionPanel value="1">
|
|
||||||
<AccordionHeader>
|
|
||||||
{{ $t('install.locationPicker.chooseDownloadServers') }}
|
|
||||||
</AccordionHeader>
|
|
||||||
<AccordionContent>
|
|
||||||
<template
|
|
||||||
v-for="([item, modelValue], index) in mirrors"
|
|
||||||
:key="item.settingId + item.mirror"
|
|
||||||
>
|
|
||||||
<Divider v-if="index > 0" class="my-8" />
|
|
||||||
|
|
||||||
<MirrorItem
|
|
||||||
v-model="modelValue.value"
|
|
||||||
:item="item"
|
|
||||||
@state-change="validationStates[index] = $event"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionPanel>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { TorchDeviceType } from '@comfyorg/comfyui-electron-types'
|
|
||||||
import { TorchMirrorUrl } from '@comfyorg/comfyui-electron-types'
|
|
||||||
import { isInChina } from '@comfyorg/shared-frontend-utils/networkUtil'
|
|
||||||
import Accordion from 'primevue/accordion'
|
|
||||||
import AccordionContent from 'primevue/accordioncontent'
|
|
||||||
import AccordionHeader from 'primevue/accordionheader'
|
|
||||||
import AccordionPanel from 'primevue/accordionpanel'
|
|
||||||
import Button from 'primevue/button'
|
|
||||||
import Divider from 'primevue/divider'
|
|
||||||
import InputText from 'primevue/inputtext'
|
|
||||||
import Message from 'primevue/message'
|
|
||||||
import { type ModelRef, computed, onMounted, ref } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
import MigrationPicker from '@/components/install/MigrationPicker.vue'
|
|
||||||
import MirrorItem from '@/components/install/mirror/MirrorItem.vue'
|
|
||||||
import {
|
|
||||||
PYPI_MIRROR,
|
|
||||||
PYTHON_MIRROR,
|
|
||||||
type UVMirror
|
|
||||||
} from '@/constants/uvMirrors'
|
|
||||||
import { electronAPI } from '@/utils/envUtil'
|
|
||||||
import { ValidationState } from '@/utils/validationUtil'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const installPath = defineModel<string>('installPath', { required: true })
|
|
||||||
const pathError = defineModel<string>('pathError', { required: true })
|
|
||||||
const migrationSourcePath = defineModel<string>('migrationSourcePath')
|
|
||||||
const migrationItemIds = defineModel<string[]>('migrationItemIds')
|
|
||||||
const pythonMirror = defineModel<string>('pythonMirror', {
|
|
||||||
default: ''
|
|
||||||
})
|
|
||||||
const pypiMirror = defineModel<string>('pypiMirror', {
|
|
||||||
default: ''
|
|
||||||
})
|
|
||||||
const torchMirror = defineModel<string>('torchMirror', {
|
|
||||||
default: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const { device } = defineProps<{ device: TorchDeviceType | null }>()
|
|
||||||
|
|
||||||
const pathExists = ref(false)
|
|
||||||
const nonDefaultDrive = ref(false)
|
|
||||||
const inputTouched = ref(false)
|
|
||||||
|
|
||||||
// Accordion state - array of active panel values
|
|
||||||
const activeAccordionIndex = ref<string[] | undefined>(undefined)
|
|
||||||
|
|
||||||
const electron = electronAPI()
|
|
||||||
|
|
||||||
// Mirror configuration logic
|
|
||||||
const getTorchMirrorItem = (device: TorchDeviceType): UVMirror => {
|
|
||||||
const settingId = 'Comfy-Desktop.UV.TorchInstallMirror'
|
|
||||||
switch (device) {
|
|
||||||
case 'mps':
|
|
||||||
return {
|
|
||||||
settingId,
|
|
||||||
mirror: TorchMirrorUrl.NightlyCpu,
|
|
||||||
fallbackMirror: TorchMirrorUrl.NightlyCpu
|
|
||||||
}
|
|
||||||
case 'nvidia':
|
|
||||||
return {
|
|
||||||
settingId,
|
|
||||||
mirror: TorchMirrorUrl.Cuda,
|
|
||||||
fallbackMirror: TorchMirrorUrl.Cuda
|
|
||||||
}
|
|
||||||
case 'cpu':
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
settingId,
|
|
||||||
mirror: PYPI_MIRROR.mirror,
|
|
||||||
fallbackMirror: PYPI_MIRROR.fallbackMirror
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const userIsInChina = ref(false)
|
|
||||||
const useFallbackMirror = (mirror: UVMirror) => ({
|
|
||||||
...mirror,
|
|
||||||
mirror: mirror.fallbackMirror
|
|
||||||
})
|
|
||||||
|
|
||||||
const mirrors = computed<[UVMirror, ModelRef<string>][]>(() =>
|
|
||||||
(
|
|
||||||
[
|
|
||||||
[PYTHON_MIRROR, pythonMirror],
|
|
||||||
[PYPI_MIRROR, pypiMirror],
|
|
||||||
[getTorchMirrorItem(device ?? 'cpu'), torchMirror]
|
|
||||||
] as [UVMirror, ModelRef<string>][]
|
|
||||||
).map(([item, modelValue]) => [
|
|
||||||
userIsInChina.value ? useFallbackMirror(item) : item,
|
|
||||||
modelValue
|
|
||||||
])
|
|
||||||
)
|
|
||||||
|
|
||||||
const validationStates = ref<ValidationState[]>(
|
|
||||||
mirrors.value.map(() => ValidationState.IDLE)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get default install path on component mount
|
|
||||||
onMounted(async () => {
|
|
||||||
const paths = await electron.getSystemPaths()
|
|
||||||
installPath.value = paths.defaultInstallPath
|
|
||||||
await validatePath(paths.defaultInstallPath)
|
|
||||||
userIsInChina.value = await isInChina()
|
|
||||||
})
|
|
||||||
|
|
||||||
const validatePath = async (path: string | undefined) => {
|
|
||||||
try {
|
|
||||||
pathError.value = ''
|
|
||||||
pathExists.value = false
|
|
||||||
nonDefaultDrive.value = false
|
|
||||||
const validation = await electron.validateInstallPath(path ?? '')
|
|
||||||
|
|
||||||
// Create a pre-formatted list of errors
|
|
||||||
if (!validation.isValid) {
|
|
||||||
const errors: string[] = []
|
|
||||||
if (validation.cannotWrite) errors.push(t('install.cannotWrite'))
|
|
||||||
if (validation.freeSpace < validation.requiredSpace) {
|
|
||||||
const requiredGB = validation.requiredSpace / 1024 / 1024 / 1024
|
|
||||||
errors.push(`${t('install.insufficientFreeSpace')}: ${requiredGB} GB`)
|
|
||||||
}
|
|
||||||
if (validation.parentMissing) errors.push(t('install.parentMissing'))
|
|
||||||
if (validation.isOneDrive) errors.push(t('install.isOneDrive'))
|
|
||||||
|
|
||||||
if (validation.error)
|
|
||||||
errors.push(`${t('install.unhandledError')}: ${validation.error}`)
|
|
||||||
pathError.value = errors.join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validation.isNonDefaultDrive) nonDefaultDrive.value = true
|
|
||||||
if (validation.exists) pathExists.value = true
|
|
||||||
} catch (error) {
|
|
||||||
pathError.value = t('install.pathValidationFailed')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const browsePath = async () => {
|
|
||||||
try {
|
|
||||||
const result = await electron.showDirectoryPicker()
|
|
||||||
if (result) {
|
|
||||||
installPath.value = result
|
|
||||||
await validatePath(result)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
pathError.value = t('install.failedToSelectDirectory')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFocus = async () => {
|
|
||||||
if (!inputTouched.value) {
|
|
||||||
inputTouched.value = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Refresh validation on re-focus
|
|
||||||
await validatePath(installPath.value)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@reference '../../assets/css/style.css';
|
|
||||||
|
|
||||||
:deep(.location-picker-accordion) {
|
|
||||||
@apply px-12;
|
|
||||||
|
|
||||||
.p-accordionpanel {
|
|
||||||
@apply border-0 bg-transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-accordionheader {
|
|
||||||
@apply bg-neutral-800/50 border-0 rounded-xl mt-2 hover:bg-neutral-700/50;
|
|
||||||
transition:
|
|
||||||
background-color 0.2s ease,
|
|
||||||
border-radius 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* When panel is expanded, adjust header border radius */
|
|
||||||
.p-accordionpanel-active {
|
|
||||||
.p-accordionheader {
|
|
||||||
@apply rounded-t-xl rounded-b-none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-accordionheader-toggle-icon {
|
|
||||||
&::before {
|
|
||||||
content: '\e902';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-accordioncontent {
|
|
||||||
@apply bg-neutral-800/50 border-0 rounded-b-xl rounded-t-none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-accordioncontent-content {
|
|
||||||
@apply bg-transparent pt-3 pr-5 pb-5 pl-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Override default chevron icons to use up/down */
|
|
||||||
.p-accordionheader-toggle-icon {
|
|
||||||
&::before {
|
|
||||||
content: '\e933';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// eslint-disable-next-line storybook/no-renderer-packages
|
|
||||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
import MigrationPicker from './MigrationPicker.vue'
|
|
||||||
|
|
||||||
const meta: Meta<typeof MigrationPicker> = {
|
|
||||||
title: 'Desktop/Components/MigrationPicker',
|
|
||||||
component: MigrationPicker,
|
|
||||||
parameters: {
|
|
||||||
backgrounds: {
|
|
||||||
default: 'dark',
|
|
||||||
values: [
|
|
||||||
{ name: 'dark', value: '#0a0a0a' },
|
|
||||||
{ name: 'neutral-900', value: '#171717' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
decorators: [
|
|
||||||
() => {
|
|
||||||
;(window as any).electronAPI = {
|
|
||||||
validateComfyUISource: () => Promise.resolve({ isValid: true }),
|
|
||||||
showDirectoryPicker: () => Promise.resolve('/Users/username/ComfyUI')
|
|
||||||
}
|
|
||||||
|
|
||||||
return { template: '<story />' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default meta
|
|
||||||
type Story = StoryObj<typeof meta>
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
render: () => ({
|
|
||||||
components: { MigrationPicker },
|
|
||||||
setup() {
|
|
||||||
const sourcePath = ref('')
|
|
||||||
const migrationItemIds = ref<string[]>([])
|
|
||||||
return { sourcePath, migrationItemIds }
|
|
||||||
},
|
|
||||||
template:
|
|
||||||
'<MigrationPicker v-model:sourcePath="sourcePath" v-model:migrationItemIds="migrationItemIds" />'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col gap-4 text-neutral-400 text-sm">
|
|
||||||
<div>
|
|
||||||
<h3 class="text-lg font-medium text-neutral-100 mb-3 mt-0">
|
|
||||||
{{ $t(`settings.${normalizedSettingId}.name`) }}
|
|
||||||
</h3>
|
|
||||||
<p class="my-1">
|
|
||||||
{{ $t(`settings.${normalizedSettingId}.tooltip`) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<UrlInput
|
|
||||||
v-model="modelValue"
|
|
||||||
:validate-url-fn="
|
|
||||||
(mirror: string) =>
|
|
||||||
checkMirrorReachable(mirror + (item.validationPathSuffix ?? ''))
|
|
||||||
"
|
|
||||||
@state-change="validationState = $event"
|
|
||||||
/>
|
|
||||||
<div v-if="secondParagraph" class="mt-2">
|
|
||||||
<a href="#" @click.prevent="showDialog = true">
|
|
||||||
{{ $t('g.learnMore') }}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<Dialog
|
|
||||||
v-model:visible="showDialog"
|
|
||||||
modal
|
|
||||||
dismissable-mask
|
|
||||||
:header="$t(`settings.${normalizedSettingId}.urlFormatTitle`)"
|
|
||||||
class="select-none max-w-3xl"
|
|
||||||
>
|
|
||||||
<div class="text-neutral-300">
|
|
||||||
<p class="mt-1 whitespace-pre-wrap">{{ secondParagraph }}</p>
|
|
||||||
<div class="mt-2 break-all">
|
|
||||||
<span class="text-neutral-300 font-semibold">
|
|
||||||
{{ EXAMPLE_URL_FIRST_PART }}
|
|
||||||
</span>
|
|
||||||
<span>{{ EXAMPLE_URL_SECOND_PART }}</span>
|
|
||||||
</div>
|
|
||||||
<Divider />
|
|
||||||
<p>
|
|
||||||
{{ $t(`settings.${normalizedSettingId}.fileUrlDescription`) }}
|
|
||||||
</p>
|
|
||||||
<span class="text-neutral-300 font-semibold">
|
|
||||||
{{ FILE_URL_SCHEME }}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{{ EXAMPLE_FILE_URL }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { normalizeI18nKey } from '@comfyorg/shared-frontend-utils/formatUtil'
|
|
||||||
import Dialog from 'primevue/dialog'
|
|
||||||
import Divider from 'primevue/divider'
|
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
|
||||||
|
|
||||||
import UrlInput from '@/components/common/UrlInput.vue'
|
|
||||||
import type { UVMirror } from '@/constants/uvMirrors'
|
|
||||||
import { st } from '@/i18n'
|
|
||||||
import { checkMirrorReachable } from '@/utils/electronMirrorCheck'
|
|
||||||
import { ValidationState } from '@/utils/validationUtil'
|
|
||||||
|
|
||||||
const FILE_URL_SCHEME = 'file://'
|
|
||||||
const EXAMPLE_FILE_URL = '/C:/MyPythonInstallers/'
|
|
||||||
const EXAMPLE_URL_FIRST_PART =
|
|
||||||
'https://github.com/astral-sh/python-build-standalone/releases/download'
|
|
||||||
const EXAMPLE_URL_SECOND_PART =
|
|
||||||
'/20250902/cpython-3.12.11+20250902-x86_64-pc-windows-msvc-install_only.tar.gz'
|
|
||||||
|
|
||||||
const { item } = defineProps<{
|
|
||||||
item: UVMirror
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
'state-change': [state: ValidationState]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const modelValue = defineModel<string>('modelValue', { required: true })
|
|
||||||
const validationState = ref<ValidationState>(ValidationState.IDLE)
|
|
||||||
const showDialog = ref(false)
|
|
||||||
|
|
||||||
const normalizedSettingId = computed(() => {
|
|
||||||
return normalizeI18nKey(item.settingId)
|
|
||||||
})
|
|
||||||
|
|
||||||
const secondParagraph = computed(() =>
|
|
||||||
st(`settings.${normalizedSettingId.value}.urlDescription`, '')
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
modelValue.value = item.mirror
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(validationState, (newState) => {
|
|
||||||
emit('state-change', newState)
|
|
||||||
|
|
||||||
// Set fallback mirror if default mirror is invalid
|
|
||||||
if (
|
|
||||||
newState === ValidationState.INVALID &&
|
|
||||||
modelValue.value === item.mirror
|
|
||||||
) {
|
|
||||||
modelValue.value = item.fallbackMirror
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import { FitAddon } from '@xterm/addon-fit'
|
|
||||||
import { Terminal } from '@xterm/xterm'
|
|
||||||
import '@xterm/xterm/css/xterm.css'
|
|
||||||
import { debounce } from 'es-toolkit/compat'
|
|
||||||
import type { Ref } from 'vue'
|
|
||||||
import { markRaw, onMounted, onUnmounted } from 'vue'
|
|
||||||
|
|
||||||
export function useTerminal(element: Ref<HTMLElement | undefined>) {
|
|
||||||
const fitAddon = new FitAddon()
|
|
||||||
const terminal = markRaw(
|
|
||||||
new Terminal({
|
|
||||||
convertEol: true,
|
|
||||||
theme: { background: '#171717' }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
terminal.loadAddon(fitAddon)
|
|
||||||
|
|
||||||
terminal.attachCustomKeyEventHandler((event) => {
|
|
||||||
// Allow default browser copy/paste handling
|
|
||||||
if (
|
|
||||||
event.type === 'keydown' &&
|
|
||||||
(event.ctrlKey || event.metaKey) &&
|
|
||||||
((event.key === 'c' && terminal.hasSelection()) || event.key === 'v')
|
|
||||||
) {
|
|
||||||
// TODO: Deselect text after copy/paste; use IPC.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (element.value) {
|
|
||||||
terminal.open(element.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
terminal.dispose()
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
terminal,
|
|
||||||
useAutoSize({
|
|
||||||
root,
|
|
||||||
autoRows = true,
|
|
||||||
autoCols = true,
|
|
||||||
minCols = Number.NEGATIVE_INFINITY,
|
|
||||||
minRows = Number.NEGATIVE_INFINITY,
|
|
||||||
onResize
|
|
||||||
}: {
|
|
||||||
root: Ref<HTMLElement | undefined>
|
|
||||||
autoRows?: boolean
|
|
||||||
autoCols?: boolean
|
|
||||||
minCols?: number
|
|
||||||
minRows?: number
|
|
||||||
onResize?: () => void
|
|
||||||
}) {
|
|
||||||
const ensureValidRows = (rows: number | undefined): number => {
|
|
||||||
if (rows == null || isNaN(rows)) {
|
|
||||||
return (root.value?.clientHeight ?? 80) / 20
|
|
||||||
}
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
|
|
||||||
const ensureValidCols = (cols: number | undefined): number => {
|
|
||||||
if (cols == null || isNaN(cols)) {
|
|
||||||
// Sometimes this is NaN if so, estimate.
|
|
||||||
return (root.value?.clientWidth ?? 80) / 8
|
|
||||||
}
|
|
||||||
return cols
|
|
||||||
}
|
|
||||||
|
|
||||||
const resize = () => {
|
|
||||||
const dims = fitAddon.proposeDimensions()
|
|
||||||
// Sometimes propose returns NaN, so we may need to estimate.
|
|
||||||
terminal.resize(
|
|
||||||
Math.max(
|
|
||||||
autoCols ? ensureValidCols(dims?.cols) : terminal.cols,
|
|
||||||
minCols
|
|
||||||
),
|
|
||||||
Math.max(
|
|
||||||
autoRows ? ensureValidRows(dims?.rows) : terminal.rows,
|
|
||||||
minRows
|
|
||||||
)
|
|
||||||
)
|
|
||||||
onResize?.()
|
|
||||||
}
|
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(debounce(resize, 25))
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (root.value) {
|
|
||||||
resizeObserver.observe(root.value)
|
|
||||||
resize()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
resizeObserver.disconnect()
|
|
||||||
})
|
|
||||||
|
|
||||||
return { resize }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
export interface DialogAction {
|
|
||||||
readonly label: string
|
|
||||||
readonly action: 'openUrl' | 'close' | 'cancel'
|
|
||||||
readonly url?: string
|
|
||||||
readonly severity?: 'danger' | 'primary' | 'secondary' | 'warn'
|
|
||||||
readonly returnValue: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DesktopDialog {
|
|
||||||
readonly title: string
|
|
||||||
readonly message: string
|
|
||||||
readonly buttons: DialogAction[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DESKTOP_DIALOGS = {
|
|
||||||
/** Shown when a corrupt venv is detected. */
|
|
||||||
reinstallVenv: {
|
|
||||||
title: 'Reinstall ComfyUI (Fresh Start)?',
|
|
||||||
message: `Sorry, we can't launch ComfyUI because some installed packages aren't compatible.
|
|
||||||
|
|
||||||
Click Reinstall to restore ComfyUI and get back up and running.
|
|
||||||
|
|
||||||
Please note: if you've added custom nodes, you'll need to reinstall them after this process.`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
label: 'Learn More',
|
|
||||||
action: 'openUrl',
|
|
||||||
url: 'https://docs.comfy.org',
|
|
||||||
returnValue: 'openDocs'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Reinstall',
|
|
||||||
action: 'close',
|
|
||||||
severity: 'danger',
|
|
||||||
returnValue: 'resetVenv'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
/** A dialog that is shown when an invalid dialog ID is provided. */
|
|
||||||
invalidDialog: {
|
|
||||||
title: 'Invalid Dialog',
|
|
||||||
message: `Invalid dialog ID was provided.`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
label: 'Close',
|
|
||||||
action: 'cancel',
|
|
||||||
returnValue: 'cancel'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
} as const satisfies { [K: string]: DesktopDialog }
|
|
||||||
|
|
||||||
/** The ID of a desktop dialog. */
|
|
||||||
type DesktopDialogId = keyof typeof DESKTOP_DIALOGS
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if {@link id} is a valid dialog ID.
|
|
||||||
* @param id The string to check
|
|
||||||
* @returns `true` if the ID is a valid dialog ID, otherwise `false`
|
|
||||||
*/
|
|
||||||
function isDialogId(id: unknown): id is DesktopDialogId {
|
|
||||||
return typeof id === 'string' && id in DESKTOP_DIALOGS
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the dialog with the given ID.
|
|
||||||
* @param dialogId The ID of the dialog to get
|
|
||||||
* @returns The dialog with the given ID
|
|
||||||
*/
|
|
||||||
export function getDialog(
|
|
||||||
dialogId: string | string[]
|
|
||||||
): DesktopDialog & { id: DesktopDialogId } {
|
|
||||||
const id = isDialogId(dialogId) ? dialogId : 'invalidDialog'
|
|
||||||
return { id, ...structuredClone(DESKTOP_DIALOGS[id]) }
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
export interface UVMirror {
|
|
||||||
/**
|
|
||||||
* The setting id defined for the mirror.
|
|
||||||
*/
|
|
||||||
settingId: string
|
|
||||||
/**
|
|
||||||
* The default mirror to use.
|
|
||||||
*/
|
|
||||||
mirror: string
|
|
||||||
/**
|
|
||||||
* The fallback mirror to use.
|
|
||||||
*/
|
|
||||||
fallbackMirror: string
|
|
||||||
/**
|
|
||||||
* The path suffix to validate the mirror is reachable.
|
|
||||||
*/
|
|
||||||
validationPathSuffix?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PYTHON_MIRROR: UVMirror = {
|
|
||||||
settingId: 'Comfy-Desktop.UV.PythonInstallMirror',
|
|
||||||
mirror:
|
|
||||||
'https://github.com/astral-sh/python-build-standalone/releases/download',
|
|
||||||
fallbackMirror:
|
|
||||||
'https://python-standalone.org/mirror/astral-sh/python-build-standalone',
|
|
||||||
validationPathSuffix:
|
|
||||||
'/20250115/cpython-3.10.16+20250115-aarch64-apple-darwin-debug-full.tar.zst.sha256'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PYPI_MIRROR: UVMirror = {
|
|
||||||
settingId: 'Comfy-Desktop.UV.PypiInstallMirror',
|
|
||||||
mirror: 'https://pypi.org/simple/',
|
|
||||||
fallbackMirror: 'https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple'
|
|
||||||
}
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
// Import only English locale eagerly as the default/fallback
|
|
||||||
// ESLint cannot statically resolve dynamic imports with path aliases (@frontend-locales/*),
|
|
||||||
// but these are properly configured in tsconfig.json and resolved by Vite at build time.
|
|
||||||
// eslint-disable-next-line import-x/no-unresolved
|
|
||||||
import enCommands from '@frontend-locales/en/commands.json' with { type: 'json' }
|
|
||||||
// eslint-disable-next-line import-x/no-unresolved
|
|
||||||
import en from '@frontend-locales/en/main.json' with { type: 'json' }
|
|
||||||
// eslint-disable-next-line import-x/no-unresolved
|
|
||||||
import enNodes from '@frontend-locales/en/nodeDefs.json' with { type: 'json' }
|
|
||||||
// eslint-disable-next-line import-x/no-unresolved
|
|
||||||
import enSettings from '@frontend-locales/en/settings.json' with { type: 'json' }
|
|
||||||
import { createI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
function buildLocale<
|
|
||||||
M extends Record<string, unknown>,
|
|
||||||
N extends Record<string, unknown>,
|
|
||||||
C extends Record<string, unknown>,
|
|
||||||
S extends Record<string, unknown>
|
|
||||||
>(main: M, nodes: N, commands: C, settings: S) {
|
|
||||||
return {
|
|
||||||
...main,
|
|
||||||
nodeDefs: nodes,
|
|
||||||
commands: commands,
|
|
||||||
settings: settings
|
|
||||||
} as M & { nodeDefs: N; commands: C; settings: S }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Locale loader map - dynamically import locales only when needed
|
|
||||||
// ESLint cannot statically resolve these dynamic imports, but they are valid at build time
|
|
||||||
/* eslint-disable import-x/no-unresolved */
|
|
||||||
const localeLoaders: Record<
|
|
||||||
string,
|
|
||||||
() => Promise<{ default: Record<string, unknown> }>
|
|
||||||
> = {
|
|
||||||
ar: () => import('@frontend-locales/ar/main.json'),
|
|
||||||
es: () => import('@frontend-locales/es/main.json'),
|
|
||||||
fr: () => import('@frontend-locales/fr/main.json'),
|
|
||||||
ja: () => import('@frontend-locales/ja/main.json'),
|
|
||||||
ko: () => import('@frontend-locales/ko/main.json'),
|
|
||||||
ru: () => import('@frontend-locales/ru/main.json'),
|
|
||||||
tr: () => import('@frontend-locales/tr/main.json'),
|
|
||||||
zh: () => import('@frontend-locales/zh/main.json'),
|
|
||||||
'zh-TW': () => import('@frontend-locales/zh-TW/main.json')
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeDefsLoaders: Record<
|
|
||||||
string,
|
|
||||||
() => Promise<{ default: Record<string, unknown> }>
|
|
||||||
> = {
|
|
||||||
ar: () => import('@frontend-locales/ar/nodeDefs.json'),
|
|
||||||
es: () => import('@frontend-locales/es/nodeDefs.json'),
|
|
||||||
fr: () => import('@frontend-locales/fr/nodeDefs.json'),
|
|
||||||
ja: () => import('@frontend-locales/ja/nodeDefs.json'),
|
|
||||||
ko: () => import('@frontend-locales/ko/nodeDefs.json'),
|
|
||||||
ru: () => import('@frontend-locales/ru/nodeDefs.json'),
|
|
||||||
tr: () => import('@frontend-locales/tr/nodeDefs.json'),
|
|
||||||
zh: () => import('@frontend-locales/zh/nodeDefs.json'),
|
|
||||||
'zh-TW': () => import('@frontend-locales/zh-TW/nodeDefs.json')
|
|
||||||
}
|
|
||||||
|
|
||||||
const commandsLoaders: Record<
|
|
||||||
string,
|
|
||||||
() => Promise<{ default: Record<string, unknown> }>
|
|
||||||
> = {
|
|
||||||
ar: () => import('@frontend-locales/ar/commands.json'),
|
|
||||||
es: () => import('@frontend-locales/es/commands.json'),
|
|
||||||
fr: () => import('@frontend-locales/fr/commands.json'),
|
|
||||||
ja: () => import('@frontend-locales/ja/commands.json'),
|
|
||||||
ko: () => import('@frontend-locales/ko/commands.json'),
|
|
||||||
ru: () => import('@frontend-locales/ru/commands.json'),
|
|
||||||
tr: () => import('@frontend-locales/tr/commands.json'),
|
|
||||||
zh: () => import('@frontend-locales/zh/commands.json'),
|
|
||||||
'zh-TW': () => import('@frontend-locales/zh-TW/commands.json')
|
|
||||||
}
|
|
||||||
|
|
||||||
const settingsLoaders: Record<
|
|
||||||
string,
|
|
||||||
() => Promise<{ default: Record<string, unknown> }>
|
|
||||||
> = {
|
|
||||||
ar: () => import('@frontend-locales/ar/settings.json'),
|
|
||||||
es: () => import('@frontend-locales/es/settings.json'),
|
|
||||||
fr: () => import('@frontend-locales/fr/settings.json'),
|
|
||||||
ja: () => import('@frontend-locales/ja/settings.json'),
|
|
||||||
ko: () => import('@frontend-locales/ko/settings.json'),
|
|
||||||
ru: () => import('@frontend-locales/ru/settings.json'),
|
|
||||||
tr: () => import('@frontend-locales/tr/settings.json'),
|
|
||||||
zh: () => import('@frontend-locales/zh/settings.json'),
|
|
||||||
'zh-TW': () => import('@frontend-locales/zh-TW/settings.json')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track which locales have been loaded
|
|
||||||
const loadedLocales = new Set<string>(['en'])
|
|
||||||
|
|
||||||
// Track locales currently being loaded to prevent race conditions
|
|
||||||
const loadingLocales = new Map<string, Promise<void>>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamically load a locale and its associated files (nodeDefs, commands, settings)
|
|
||||||
*/
|
|
||||||
export async function loadLocale(locale: string): Promise<void> {
|
|
||||||
if (loadedLocales.has(locale)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If already loading, return the existing promise to prevent duplicate loads
|
|
||||||
const existingLoad = loadingLocales.get(locale)
|
|
||||||
if (existingLoad) {
|
|
||||||
return existingLoad
|
|
||||||
}
|
|
||||||
|
|
||||||
const loader = localeLoaders[locale]
|
|
||||||
const nodeDefsLoader = nodeDefsLoaders[locale]
|
|
||||||
const commandsLoader = commandsLoaders[locale]
|
|
||||||
const settingsLoader = settingsLoaders[locale]
|
|
||||||
|
|
||||||
if (!loader || !nodeDefsLoader || !commandsLoader || !settingsLoader) {
|
|
||||||
console.warn(`Locale "${locale}" is not supported`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and track the loading promise
|
|
||||||
const loadPromise = (async () => {
|
|
||||||
try {
|
|
||||||
const [main, nodes, commands, settings] = await Promise.all([
|
|
||||||
loader(),
|
|
||||||
nodeDefsLoader(),
|
|
||||||
commandsLoader(),
|
|
||||||
settingsLoader()
|
|
||||||
])
|
|
||||||
|
|
||||||
const messages = buildLocale(
|
|
||||||
main.default,
|
|
||||||
nodes.default,
|
|
||||||
commands.default,
|
|
||||||
settings.default
|
|
||||||
)
|
|
||||||
|
|
||||||
i18n.global.setLocaleMessage(locale, messages as LocaleMessages)
|
|
||||||
loadedLocales.add(locale)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to load locale "${locale}":`, error)
|
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
// Clean up the loading promise once complete
|
|
||||||
loadingLocales.delete(locale)
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
loadingLocales.set(locale, loadPromise)
|
|
||||||
return loadPromise
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only include English in the initial bundle
|
|
||||||
const messages = {
|
|
||||||
en: buildLocale(en, enNodes, enCommands, enSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type for locale messages - inferred from the English locale structure
|
|
||||||
type LocaleMessages = typeof messages.en
|
|
||||||
|
|
||||||
export const i18n = createI18n({
|
|
||||||
// Must set `false`, as Vue I18n Legacy API is for Vue 2
|
|
||||||
legacy: false,
|
|
||||||
locale: navigator.language.split('-')[0] || 'en',
|
|
||||||
fallbackLocale: 'en',
|
|
||||||
messages,
|
|
||||||
// Ignore warnings for locale options as each option is in its own language.
|
|
||||||
// e.g. "English", "中文", "Русский", "日本語", "한국어", "Français", "Español"
|
|
||||||
missingWarn: /^(?!settings\.Comfy_Locale\.options\.).+/,
|
|
||||||
fallbackWarn: /^(?!settings\.Comfy_Locale\.options\.).+/
|
|
||||||
})
|
|
||||||
|
|
||||||
/** Convenience shorthand: i18n.global */
|
|
||||||
export const { t, te } = i18n.global
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Safe translation function that returns the fallback message if the key is not found.
|
|
||||||
*
|
|
||||||
* @param key - The key to translate.
|
|
||||||
* @param fallbackMessage - The fallback message to use if the key is not found.
|
|
||||||
*/
|
|
||||||
export function st(key: string, fallbackMessage: string) {
|
|
||||||
return te(key) ? t(key) : fallbackMessage
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { definePreset } from '@primevue/themes'
|
|
||||||
import Aura from '@primevue/themes/aura'
|
|
||||||
import { createPinia } from 'pinia'
|
|
||||||
import 'primeicons/primeicons.css'
|
|
||||||
import PrimeVue from 'primevue/config'
|
|
||||||
import ConfirmationService from 'primevue/confirmationservice'
|
|
||||||
import ToastService from 'primevue/toastservice'
|
|
||||||
import Tooltip from 'primevue/tooltip'
|
|
||||||
import { createApp } from 'vue'
|
|
||||||
|
|
||||||
import App from './App.vue'
|
|
||||||
import './assets/css/style.css'
|
|
||||||
import { i18n } from './i18n'
|
|
||||||
import router from './router'
|
|
||||||
|
|
||||||
const ComfyUIPreset = definePreset(Aura, {
|
|
||||||
semantic: {
|
|
||||||
// @ts-expect-error fixme ts strict error
|
|
||||||
primary: Aura['primitive'].blue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const app = createApp(App)
|
|
||||||
const pinia = createPinia()
|
|
||||||
|
|
||||||
app.directive('tooltip', Tooltip)
|
|
||||||
app
|
|
||||||
.use(router)
|
|
||||||
.use(PrimeVue, {
|
|
||||||
theme: {
|
|
||||||
preset: ComfyUIPreset,
|
|
||||||
options: {
|
|
||||||
prefix: 'p',
|
|
||||||
cssLayer: {
|
|
||||||
name: 'primevue',
|
|
||||||
order: 'theme, base, primevue'
|
|
||||||
},
|
|
||||||
darkModeSelector: '.dark-theme, :root:has(.dark-theme)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.use(ConfirmationService)
|
|
||||||
.use(ToastService)
|
|
||||||
.use(pinia)
|
|
||||||
.use(i18n)
|
|
||||||
.mount('#desktop-app')
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import {
|
|
||||||
createRouter,
|
|
||||||
createWebHashHistory,
|
|
||||||
createWebHistory
|
|
||||||
} from 'vue-router'
|
|
||||||
|
|
||||||
import { isElectron } from '@/utils/envUtil'
|
|
||||||
import LayoutDefault from '@/views/layouts/LayoutDefault.vue'
|
|
||||||
|
|
||||||
const isFileProtocol = window.location.protocol === 'file:'
|
|
||||||
const basePath = isElectron() ? '/' : window.location.pathname
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: isFileProtocol ? createWebHashHistory() : createWebHistory(basePath),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: LayoutDefault,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
name: 'WelcomeView',
|
|
||||||
component: () => import('@/views/WelcomeView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'welcome',
|
|
||||||
name: 'WelcomeViewAlias',
|
|
||||||
component: () => import('@/views/WelcomeView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'install',
|
|
||||||
name: 'InstallView',
|
|
||||||
component: () => import('@/views/InstallView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'download-git',
|
|
||||||
name: 'DownloadGitView',
|
|
||||||
component: () => import('@/views/DownloadGitView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'desktop-start',
|
|
||||||
name: 'DesktopStartView',
|
|
||||||
component: () => import('@/views/DesktopStartView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'desktop-update',
|
|
||||||
name: 'DesktopUpdateView',
|
|
||||||
component: () => import('@/views/DesktopUpdateView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'server-start',
|
|
||||||
name: 'ServerStartView',
|
|
||||||
component: () => import('@/views/ServerStartView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'manual-configuration',
|
|
||||||
name: 'ManualConfigurationView',
|
|
||||||
component: () => import('@/views/ManualConfigurationView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'metrics-consent',
|
|
||||||
name: 'MetricsConsentView',
|
|
||||||
component: () => import('@/views/MetricsConsentView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'maintenance',
|
|
||||||
name: 'MaintenanceView',
|
|
||||||
component: () => import('@/views/MaintenanceView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'not-supported',
|
|
||||||
name: 'NotSupportedView',
|
|
||||||
component: () => import('@/views/NotSupportedView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'desktop-dialog/:dialogId',
|
|
||||||
name: 'DesktopDialogView',
|
|
||||||
component: () => import('@/views/DesktopDialogView.vue')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
scrollBehavior(_to, _from, savedPosition) {
|
|
||||||
if (savedPosition) {
|
|
||||||
return savedPosition
|
|
||||||
}
|
|
||||||
|
|
||||||
return { top: 0 }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
||||||
12
apps/desktop-ui/src/types/global.d.ts
vendored
12
apps/desktop-ui/src/types/global.d.ts
vendored
@@ -1,12 +0,0 @@
|
|||||||
declare global {
|
|
||||||
interface Navigator {
|
|
||||||
/**
|
|
||||||
* Desktop app uses windowControlsOverlay to decide if it is in a custom window.
|
|
||||||
*/
|
|
||||||
windowControlsOverlay?: {
|
|
||||||
visible: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user