diff --git a/.claude/commands/comprehensive-pr-review.md b/.claude/commands/comprehensive-pr-review.md index 84708564e..1b4047e78 100644 --- a/.claude/commands/comprehensive-pr-review.md +++ b/.claude/commands/comprehensive-pr-review.md @@ -67,9 +67,9 @@ This is critical for better file inspection: Use git locally for much faster analysis: -1. Get list of changed files: `git diff --name-only "origin/$BASE_BRANCH" > changed_files.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 "origin/$BASE_BRANCH" > file_changes.txt` +1. Get list of changed files: `git diff --name-only "$BASE_SHA" > changed_files.txt` +2. Get the full diff: `git diff "$BASE_SHA" > pr_diff.txt` +3. Get detailed file changes with status: `git diff --name-status "$BASE_SHA" > file_changes.txt` ### Step 1.5: Create Analysis Cache diff --git a/.claude/commands/create-frontend-release.md b/.claude/commands/create-frontend-release.md index de6695710..f16189d42 100644 --- a/.claude/commands/create-frontend-release.md +++ b/.claude/commands/create-frontend-release.md @@ -294,7 +294,6 @@ echo "Last stable release: $LAST_STABLE" 1. Run complete test suite: ```bash pnpm test:unit - pnpm test:component ``` 2. Run type checking: ```bash @@ -459,15 +458,15 @@ echo "Workflow triggered. Waiting for PR creation..." 3. **IMMEDIATELY CHECK**: Did release workflow trigger? ```bash sleep 10 - gh run list --workflow=release.yaml --limit=1 + gh run list --workflow=release-draft-create.yaml --limit=1 ``` -4. **For Minor/Major Version Releases**: The create-release-candidate-branch workflow will automatically: +4. **For Minor/Major Version Releases**: The release-branch-create workflow will automatically: - Create a `core/x.yy` branch for the PREVIOUS minor version - Apply branch protection rules - Document the feature freeze policy ```bash # Monitor branch creation (for minor/major releases) - gh run list --workflow=create-release-candidate-branch.yaml --limit=1 + gh run list --workflow=release-branch-create.yaml --limit=1 ``` 4. If workflow didn't trigger due to [skip ci]: ```bash @@ -478,7 +477,7 @@ echo "Workflow triggered. Waiting for PR creation..." ``` 5. If workflow triggered, monitor execution: ```bash - WORKFLOW_RUN_ID=$(gh run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[0].databaseId') + WORKFLOW_RUN_ID=$(gh run list --workflow=release-draft-create.yaml --limit=1 --json databaseId --jq '.[0].databaseId') gh run watch ${WORKFLOW_RUN_ID} ``` diff --git a/.claude/commands/create-hotfix-release.md b/.claude/commands/create-hotfix-release.md index de314309d..cc9c37ef5 100644 --- a/.claude/commands/create-hotfix-release.md +++ b/.claude/commands/create-hotfix-release.md @@ -1,30 +1,85 @@ # Create Hotfix Release -This command guides you through creating a patch/hotfix release for ComfyUI Frontend with comprehensive safety checks and human confirmations at each step. +This command creates patch/hotfix releases for ComfyUI Frontend by backporting fixes to stable core branches. It handles both automated backports (preferred) and manual cherry-picking (fallback). + +**Process Overview:** +1. **Check automated backports first** (via labels) +2. **Skip to version bump** if backports already merged +3. **Manual cherry-picking** if automation failed +4. **Create patch release** with version bump +5. **Publish GitHub release** (manually uncheck "latest") +6. **Update ComfyUI requirements.txt** via PR -Create a hotfix release by cherry-picking commits or PR commits from main to a core branch: $ARGUMENTS +Create a hotfix release by backporting commits/PRs from main to a core branch: $ARGUMENTS Expected format: Comma-separated list of commits or PR numbers Examples: -- `abc123,def456,ghi789` (commits) -- `#1234,#5678` (PRs) -- `abc123,#1234,def456` (mixed) +- `#1234,#5678` (PRs - preferred) +- `abc123,def456` (commit hashes) +- `#1234,abc123` (mixed) -If no arguments provided, the command will help identify the correct core branch and guide you through selecting commits/PRs. +If no arguments provided, the command will guide you through identifying commits/PRs to backport. ## Prerequisites -Before starting, ensure: -- You have push access to the repository -- GitHub CLI (`gh`) is authenticated -- You're on a clean working tree -- You understand the commits/PRs you're cherry-picking +- Push access to repository +- GitHub CLI (`gh`) authenticated +- Clean working tree +- Understanding of what fixes need backporting ## Hotfix Release Process -### Step 1: Identify Target Core Branch +### Step 1: Try Automated Backports First + +**Check if automated backports were attempted:** + +1. **For each PR, check existing backport labels:** + ```bash + gh pr view #1234 --json labels | jq -r '.labels[].name' + ``` + +2. **If no backport labels exist, add them now:** + ```bash + # Add backport labels (this triggers automated backports) + gh pr edit #1234 --add-label "needs-backport" + gh pr edit #1234 --add-label "1.24" # Replace with target version + ``` + +3. **Check for existing backport PRs:** + ```bash + # Check for backport PRs created by automation + PR_NUMBER=${ARGUMENTS%%,*} # Extract first PR number from arguments + PR_NUMBER=${PR_NUMBER#\#} # Remove # prefix + gh pr list --search "backport-${PR_NUMBER}-to" --json number,title,state,baseRefName + ``` + +4. **Handle existing backport scenarios:** + + **Scenario A: Automated backports already merged** + ```bash + # Check if backport PRs were merged to core branches + gh pr list --search "backport-${PR_NUMBER}-to" --state merged + ``` + - If backport PRs are merged → Skip to Step 10 (Version Bump) + - **CONFIRMATION**: Automated backports completed, proceeding to version bump? + + **Scenario B: Automated backport PRs exist but not merged** + ```bash + # Show open backport PRs that need merging + gh pr list --search "backport-${PR_NUMBER}-to" --state open + ``` + - **ACTION REQUIRED**: Merge the existing backport PRs first + - Use: `gh pr merge [PR_NUMBER] --merge` for each backport PR + - After merging, return to this command and skip to Step 10 (Version Bump) + - **CONFIRMATION**: Have you merged all backport PRs? Ready to proceed to version bump? + + **Scenario C: No automated backports or they failed** + - Continue to Step 2 for manual cherry-picking + - **CONFIRMATION**: Proceeding with manual cherry-picking because automation failed? + +### Step 2: Identify Target Core Branch 1. Fetch the current ComfyUI requirements.txt from master branch: ```bash @@ -36,7 +91,7 @@ Before starting, ensure: 5. Verify the core branch exists: `git ls-remote origin refs/heads/core/*` 6. **CONFIRMATION REQUIRED**: Is `core/X.Y` the correct target branch? -### Step 2: Parse and Validate Arguments +### Step 3: Parse and Validate Arguments 1. Parse the comma-separated list of commits/PRs 2. For each item: @@ -49,7 +104,7 @@ Before starting, ensure: - **CONFIRMATION REQUIRED**: Use merge commit or cherry-pick individual commits? 4. Validate all commit hashes exist in the repository -### Step 3: Analyze Target Changes +### Step 4: Analyze Target Changes 1. For each commit/PR to cherry-pick: - Display commit hash, author, date @@ -60,7 +115,7 @@ Before starting, ensure: 2. Identify potential conflicts by checking changed files 3. **CONFIRMATION REQUIRED**: Proceed with these commits? -### Step 4: Create Hotfix Branch +### Step 5: Create Hotfix Branch 1. Checkout the core branch (e.g., `core/1.23`) 2. Pull latest changes: `git pull origin core/X.Y` @@ -69,7 +124,7 @@ Before starting, ensure: - Example: `hotfix/1.23.4-20241120` 5. **CONFIRMATION REQUIRED**: Created branch correctly? -### Step 5: Cherry-pick Changes +### Step 6: Cherry-pick Changes For each commit: 1. Attempt cherry-pick: `git cherry-pick ` @@ -83,7 +138,7 @@ For each commit: - Run validation: `pnpm typecheck && pnpm lint` 4. **CONFIRMATION REQUIRED**: Cherry-pick successful and valid? -### Step 6: Create PR to Core Branch +### Step 7: Create PR to Core Branch 1. Push the hotfix branch: `git push origin hotfix/-` 2. Create PR using gh CLI: @@ -100,7 +155,7 @@ For each commit: - Impact assessment 5. **CONFIRMATION REQUIRED**: PR created correctly? -### Step 7: Wait for Tests +### Step 8: Wait for Tests 1. Monitor PR checks: `gh pr checks` 2. Display test results as they complete @@ -111,7 +166,7 @@ For each commit: 4. Wait for all required checks to pass 5. **CONFIRMATION REQUIRED**: All tests passing? -### Step 8: Merge Hotfix PR +### Step 9: Merge Hotfix PR 1. Verify all checks have passed 2. Check for required approvals @@ -119,7 +174,7 @@ For each commit: 4. Delete the hotfix branch 5. **CONFIRMATION REQUIRED**: PR merged successfully? -### Step 9: Create Version Bump +### Step 10: Create Version Bump 1. Checkout the core branch: `git checkout core/X.Y` 2. Pull latest changes: `git pull origin core/X.Y` @@ -131,7 +186,7 @@ For each commit: 7. Commit: `git commit -m "[release] Bump version to 1.23.5"` 8. **CONFIRMATION REQUIRED**: Version bump correct? -### Step 10: Create Release PR +### Step 11: Create Release PR 1. Push release branch: `git push origin release/1.23.5` 2. Create PR with Release label: @@ -184,14 +239,14 @@ For each commit: ``` 5. **CONFIRMATION REQUIRED**: Release PR has "Release" label? -### Step 11: Monitor Release Process +### Step 12: Monitor Release Process 1. Wait for PR checks to pass 2. **FINAL CONFIRMATION**: Ready to trigger release by merging? 3. Merge the PR: `gh pr merge --merge` 4. Monitor release workflow: ```bash - gh run list --workflow=release.yaml --limit=1 + gh run list --workflow=release-draft-create.yaml --limit=1 gh run watch ``` 5. Track progress: @@ -199,7 +254,102 @@ For each commit: - PyPI upload - pnpm types publication -### Step 12: Post-Release Verification +### Step 13: Manually Publish Draft Release + +**CRITICAL**: The release workflow creates a DRAFT release. You must manually publish it: + +1. **Go to GitHub Releases:** https://github.com/Comfy-Org/ComfyUI_frontend/releases +2. **Find the DRAFT release** (e.g., "v1.23.5 Draft") +3. **Click "Edit release"** +4. **UNCHECK "Set as the latest release"** ⚠️ **CRITICAL** + - This prevents the hotfix from showing as "latest" + - Main branch should always be "latest release" +5. **Click "Publish release"** +6. **CONFIRMATION REQUIRED**: Draft release published with "latest" unchecked? + +### Step 14: Create ComfyUI Requirements.txt Update PR + +**IMPORTANT**: Create PR to update ComfyUI's requirements.txt via fork: + +1. **Setup fork (if needed):** + ```bash + # Check if fork already exists + if gh repo view ComfyUI --json owner | jq -r '.owner.login' | grep -q "$(gh api user --jq .login)"; then + echo "Fork already exists" + else + # Fork the ComfyUI repository + gh repo fork comfyanonymous/ComfyUI --clone=false + echo "Created fork of ComfyUI" + fi + ``` + +2. **Clone fork and create branch:** + ```bash + # Clone your fork (or use existing clone) + GITHUB_USER=$(gh api user --jq .login) + if [ ! -d "ComfyUI-fork" ]; then + gh repo clone ${GITHUB_USER}/ComfyUI ComfyUI-fork + fi + + cd ComfyUI-fork + git checkout master + git pull origin master + + # Create update branch + BRANCH_NAME="update-frontend-${NEW_VERSION}" + git checkout -b ${BRANCH_NAME} + ``` + +3. **Update requirements.txt:** + ```bash + # Update the version in requirements.txt + sed -i "s/comfyui-frontend-package==[0-9].*$/comfyui-frontend-package==${NEW_VERSION}/" requirements.txt + + # Verify the change + grep "comfyui-frontend-package" requirements.txt + + # Commit the change + git add requirements.txt + git commit -m "Bump frontend to ${NEW_VERSION}" + git push origin ${BRANCH_NAME} + ``` + +4. **Create PR from fork:** + ```bash + # Create PR using gh CLI from fork + gh pr create \ + --repo comfyanonymous/ComfyUI \ + --title "Bump frontend to ${NEW_VERSION}" \ + --body "$(cat <> $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 diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 000000000..f62e91eda --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,21 @@ +# GitHub Workflows + +## Naming Convention + +Workflow files follow a consistent naming pattern: `-.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). diff --git a/.github/workflows/update-electron-types.yaml b/.github/workflows/api-update-electron-api-types.yaml similarity index 78% rename from .github/workflows/update-electron-types.yaml rename to .github/workflows/api-update-electron-api-types.yaml index 04d8cc436..90a08e02c 100644 --- a/.github/workflows/update-electron-types.yaml +++ b/.github/workflows/api-update-electron-api-types.yaml @@ -1,4 +1,5 @@ -name: Update Electron Types +name: 'Api: Update Electron API Types' +description: 'When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo' on: workflow_dispatch: @@ -12,7 +13,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 @@ -35,12 +36,12 @@ jobs: electron-types-tools-cache-${{ runner.os }}- - name: Update electron types - run: pnpm install @comfyorg/comfyui-electron-types@latest + run: pnpm install --workspace-root @comfyorg/comfyui-electron-types@latest - name: Get new version id: get-version run: | - NEW_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('./pnpm-lock.yaml')).packages['node_modules/@comfyorg/comfyui-electron-types'].version)") + NEW_VERSION=$(pnpm list @comfyorg/comfyui-electron-types --json --depth=0 | jq -r '.[0].dependencies."@comfyorg/comfyui-electron-types".version') echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT - name: Create Pull Request diff --git a/.github/workflows/update-manager-types.yaml b/.github/workflows/api-update-manager-api-types.yaml similarity index 91% rename from .github/workflows/update-manager-types.yaml rename to .github/workflows/api-update-manager-api-types.yaml index 244127dc2..5a1e8ec74 100644 --- a/.github/workflows/update-manager-types.yaml +++ b/.github/workflows/api-update-manager-api-types.yaml @@ -1,4 +1,5 @@ -name: Update ComfyUI-Manager API Types +name: 'Api: Update Manager API Types' +description: 'When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo' on: # Manual trigger @@ -17,7 +18,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 @@ -51,7 +52,7 @@ jobs: comfyui-manager-repo-${{ runner.os }}- - name: Checkout ComfyUI-Manager repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: Comfy-Org/ComfyUI-Manager path: ComfyUI-Manager @@ -68,7 +69,7 @@ jobs: - name: Generate Manager API types run: | echo "Generating TypeScript types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}..." - npx openapi-typescript ./ComfyUI-Manager/openapi.yaml --output ./src/types/generatedManagerTypes.ts + pnpm dlx openapi-typescript ./ComfyUI-Manager/openapi.yaml --output ./src/types/generatedManagerTypes.ts - name: Validate generated types run: | diff --git a/.github/workflows/update-registry-types.yaml b/.github/workflows/api-update-registry-api-types.yaml similarity index 81% rename from .github/workflows/update-registry-types.yaml rename to .github/workflows/api-update-registry-api-types.yaml index 0cd2c41da..69b4ad5b2 100644 --- a/.github/workflows/update-registry-types.yaml +++ b/.github/workflows/api-update-registry-api-types.yaml @@ -1,4 +1,5 @@ -name: Update Comfy Registry API Types +name: 'Api: Update Registry API Types' +description: 'When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo' on: # Manual trigger @@ -16,7 +17,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 @@ -50,7 +51,7 @@ jobs: comfy-api-repo-${{ runner.os }}- - name: Checkout comfy-api repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: Comfy-Org/comfy-api path: comfy-api @@ -68,17 +69,18 @@ jobs: - name: Generate API types run: | echo "Generating TypeScript types from comfy-api@${{ steps.api-info.outputs.commit }}..." - npx openapi-typescript ./comfy-api/openapi.yml --output ./src/types/comfyRegistryTypes.ts + mkdir -p ./packages/registry-types/src + pnpm dlx openapi-typescript ./comfy-api/openapi.yml --output ./packages/registry-types/src/comfyRegistryTypes.ts - name: Validate generated types run: | - if [ ! -f ./src/types/comfyRegistryTypes.ts ]; then + if [ ! -f ./packages/registry-types/src/comfyRegistryTypes.ts ]; then echo "Error: Types file was not generated." exit 1 fi # Check if file is not empty - if [ ! -s ./src/types/comfyRegistryTypes.ts ]; then + if [ ! -s ./packages/registry-types/src/comfyRegistryTypes.ts ]; then echo "Error: Generated types file is empty." exit 1 fi @@ -86,12 +88,12 @@ jobs: - name: Lint generated types run: | echo "Linting generated Comfy Registry API types..." - pnpm lint:fix:no-cache -- ./src/types/comfyRegistryTypes.ts + pnpm lint:fix:no-cache -- ./packages/registry-types/src/comfyRegistryTypes.ts - name: Check for changes id: check-changes run: | - if [[ -z $(git status --porcelain ./src/types/comfyRegistryTypes.ts) ]]; then + if [[ -z $(git status --porcelain ./packages/registry-types/src/comfyRegistryTypes.ts) ]]; then echo "No changes to Comfy Registry API types detected." echo "changed=false" >> $GITHUB_OUTPUT exit 0 @@ -121,4 +123,4 @@ jobs: labels: CNR delete-branch: true add-paths: | - src/types/comfyRegistryTypes.ts + packages/registry-types/src/comfyRegistryTypes.ts diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml deleted file mode 100644 index f9106caee..000000000 --- a/.github/workflows/backport.yaml +++ /dev/null @@ -1,165 +0,0 @@ -name: Auto Backport - -on: - pull_request_target: - types: [closed] - 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: Extract version labels - 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 - 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.backport.outputs.success - env: - GH_TOKEN: ${{ secrets.PR_GH_TOKEN }} - run: | - PR_TITLE="${{ github.event.pull_request.title }}" - PR_NUMBER="${{ github.event.pull_request.number }}" - PR_AUTHOR="${{ github.event.pull_request.user.login }}" - - 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: 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'"
Conflicting files"$'\n\n'"${CONFLICTS_LIST}"$'\n\n'"
" - gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}" - fi - done diff --git a/.github/workflows/chromatic.yaml b/.github/workflows/chromatic.yaml deleted file mode 100644 index 127186d68..000000000 --- a/.github/workflows/chromatic.yaml +++ /dev/null @@ -1,57 +0,0 @@ -name: 'Chromatic' - -# - [Automate Chromatic with GitHub Actions • Chromatic docs]( https://www.chromatic.com/docs/github-actions/ ) - -on: - workflow_dispatch: # Allow manual triggering - pull_request: - branches: [main] - -jobs: - chromatic-deployment: - runs-on: ubuntu-latest - # Only run for PRs from version-bump-* branches or manual triggers - if: github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'version-bump-') - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Required for Chromatic baseline - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - - - name: Cache tool outputs - uses: actions/cache@v4 - with: - path: | - .cache - storybook-static - tsconfig.tsbuildinfo - key: storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', '*.config.*', '.storybook/**/*') }} - restore-keys: | - storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}- - storybook-cache-${{ runner.os }}- - storybook-tools-cache-${{ runner.os }}- - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build Storybook and run Chromatic - id: chromatic - uses: chromaui/action@latest - with: - projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - buildScriptName: build-storybook - autoAcceptChanges: 'main' # Auto-accept changes on main branch - exitOnceUploaded: true # Don't wait for UI tests to complete - diff --git a/.github/workflows/ci-json-validation.yaml b/.github/workflows/ci-json-validation.yaml new file mode 100644 index 000000000..0d8c9392f --- /dev/null +++ b/.github/workflows/ci-json-validation.yaml @@ -0,0 +1,16 @@ +name: "CI: JSON Validation" +description: "Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq" + +on: + push: + branches: + - main + pull_request: + +jobs: + json-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Validate JSON syntax + run: ./scripts/cicd/check-json.sh diff --git a/.github/workflows/lint-and-format.yaml b/.github/workflows/ci-lint-format.yaml similarity index 90% rename from .github/workflows/lint-and-format.yaml rename to .github/workflows/ci-lint-format.yaml index 3b6bf1538..5c4e0011b 100644 --- a/.github/workflows/lint-and-format.yaml +++ b/.github/workflows/ci-lint-format.yaml @@ -1,9 +1,14 @@ -name: Lint and Format +name: "CI: Lint Format" +description: "Linting and code formatting validation for pull requests" on: pull_request: branches-ignore: [wip/*, draft/*, temp/*] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: write pull-requests: write @@ -13,11 +18,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: - token: ${{ secrets.GITHUB_TOKEN }} - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 + ref: ${{ !github.event.pull_request.head.repo.fork && github.head_ref || github.ref }} - name: Install pnpm uses: pnpm/action-setup@v4 @@ -69,7 +72,7 @@ jobs: git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add . - git commit -m "[auto-fix] Apply ESLint and Prettier fixes" + git commit -m "[automated] Apply ESLint and Prettier fixes" git push - name: Final validation @@ -102,4 +105,4 @@ jobs: owner: context.repo.owner, 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.' - }) \ No newline at end of file + }) diff --git a/.github/workflows/ci-python-validation.yaml b/.github/workflows/ci-python-validation.yaml new file mode 100644 index 000000000..698f467bf --- /dev/null +++ b/.github/workflows/ci-python-validation.yaml @@ -0,0 +1,27 @@ +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 diff --git a/.github/workflows/ci-tests-e2e-forks.yaml b/.github/workflows/ci-tests-e2e-forks.yaml new file mode 100644 index 000000000..5d3767f8e --- /dev/null +++ b/.github/workflows/ci-tests-e2e-forks.yaml @@ -0,0 +1,93 @@ +name: "CI: Tests E2E (Deploy for Forks)" +description: "Deploys test results from forked PRs (forks can't access deployment secrets)" + +on: + workflow_run: + workflows: ["CI: Tests E2E"] + 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 Test Start + if: steps.pr.outputs.result != 'null' && github.event.action == 'requested' + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh + ./scripts/cicd/pr-playwright-deploy-and-comment.sh \ + "${{ steps.pr.outputs.result }}" \ + "${{ github.event.workflow_run.head_branch }}" \ + "starting" \ + "$(date -u '${{ env.DATE_FORMAT }}')" + + - name: Download and Deploy Reports + if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' + uses: actions/download-artifact@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + pattern: playwright-report-* + path: reports + + - name: Handle Test 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 }} + run: | + # Rename merged report if exists + [ -d "reports/playwright-report-chromium-merged" ] && \ + mv reports/playwright-report-chromium-merged reports/playwright-report-chromium + + chmod +x scripts/cicd/pr-playwright-deploy-and-comment.sh + ./scripts/cicd/pr-playwright-deploy-and-comment.sh \ + "${{ steps.pr.outputs.result }}" \ + "${{ github.event.workflow_run.head_branch }}" \ + "completed" \ No newline at end of file diff --git a/.github/workflows/ci-tests-e2e.yaml b/.github/workflows/ci-tests-e2e.yaml new file mode 100644 index 000000000..690417a04 --- /dev/null +++ b/.github/workflows/ci-tests-e2e.yaml @@ -0,0 +1,236 @@ +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) diff --git a/.github/workflows/ci-tests-storybook-forks.yaml b/.github/workflows/ci-tests-storybook-forks.yaml new file mode 100644 index 000000000..083a85f1d --- /dev/null +++ b/.github/workflows/ci-tests-storybook-forks.yaml @@ -0,0 +1,91 @@ +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" \ No newline at end of file diff --git a/.github/workflows/ci-tests-storybook.yaml b/.github/workflows/ci-tests-storybook.yaml new file mode 100644 index 000000000..65e97ed91 --- /dev/null +++ b/.github/workflows/ci-tests-storybook.yaml @@ -0,0 +1,230 @@ +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: Cache tool outputs + uses: actions/cache@v4 + with: + path: | + .cache + storybook-static + tsconfig.tsbuildinfo + key: storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', '*.config.*', '.storybook/**/*') }} + restore-keys: | + storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}- + storybook-cache-${{ runner.os }}- + storybook-tools-cache-${{ runner.os }}- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build Storybook + run: pnpm build-storybook + + - name: Set job status + id: job-status + if: always() + run: | + echo "conclusion=${{ job.status }}" >> $GITHUB_OUTPUT + + - name: Get workflow URL + id: workflow-url + if: always() + run: | + echo "url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_OUTPUT + + - name: Upload Storybook build + if: success() && github.event.pull_request.head.repo.fork == false + uses: actions/upload-artifact@v4 + with: + name: storybook-static + path: storybook-static/ + retention-days: 7 + + # Chromatic deployment only for version-bump-* branches or manual triggers + chromatic-deployment: + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'version-bump-')) + outputs: + conclusion: ${{ steps.job-status.outputs.conclusion }} + workflow-url: ${{ steps.workflow-url.outputs.url }} + chromatic-build-url: ${{ steps.chromatic.outputs.buildUrl }} + chromatic-storybook-url: ${{ steps.chromatic.outputs.storybookUrl }} + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 0 # Required for Chromatic baseline + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Cache tool outputs + uses: actions/cache@v4 + with: + path: | + .cache + storybook-static + tsconfig.tsbuildinfo + key: storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', '*.config.*', '.storybook/**/*') }} + restore-keys: | + storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}- + storybook-cache-${{ runner.os }}- + storybook-tools-cache-${{ runner.os }}- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build Storybook and run Chromatic + id: chromatic + uses: chromaui/action@latest + with: + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + buildScriptName: build-storybook + autoAcceptChanges: 'main' # Auto-accept changes on main branch + exitOnceUploaded: true # Don't wait for UI tests to complete + onlyChanged: true # Only capture changed stories + + - name: Set job status + id: job-status + if: always() + run: | + echo "conclusion=${{ job.status }}" >> $GITHUB_OUTPUT + + - name: Get workflow URL + id: workflow-url + if: always() + run: | + echo "url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_OUTPUT + + # Deploy and comment for non-forked PRs only + deploy-and-comment: + needs: [storybook-build] + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false && always() + permissions: + pull-requests: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Download Storybook build + if: needs.storybook-build.outputs.conclusion == 'success' + uses: actions/download-artifact@v4 + with: + name: storybook-static + path: storybook-static + + - name: Make deployment script executable + run: chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh + + - name: Deploy Storybook and comment on PR + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + GITHUB_TOKEN: ${{ github.token }} + WORKFLOW_CONCLUSION: ${{ needs.storybook-build.outputs.conclusion }} + WORKFLOW_URL: ${{ needs.storybook-build.outputs.workflow-url }} + run: | + ./scripts/cicd/pr-storybook-deploy-and-comment.sh \ + "${{ github.event.pull_request.number }}" \ + "${{ github.head_ref }}" \ + "completed" + + # Update comment with Chromatic URLs for version-bump branches + update-comment-with-chromatic: + needs: [chromatic-deployment, deploy-and-comment] + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false && startsWith(github.head_ref, 'version-bump-') && needs.chromatic-deployment.outputs.chromatic-build-url != '' + permissions: + pull-requests: write + steps: + - name: Update comment with Chromatic URLs + uses: actions/github-script@v7 + with: + script: | + const buildUrl = '${{ needs.chromatic-deployment.outputs.chromatic-build-url }}'; + const storybookUrl = '${{ needs.chromatic-deployment.outputs.chromatic-storybook-url }}'; + + // Find the existing Storybook comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ github.event.pull_request.number }} + }); + + const storybookComment = comments.find(comment => + comment.body.includes('') + ); + + if (storybookComment && buildUrl && storybookUrl) { + // Append Chromatic info to existing comment + const updatedBody = storybookComment.body.replace( + /---\n(.*)$/s, + `---\n### 🎨 Chromatic Visual Tests\n- 📊 [View Chromatic Build](${buildUrl})\n- 📚 [View Chromatic Storybook](${storybookUrl})\n\n$1` + ); + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: storybookComment.id, + body: updatedBody + }); + } diff --git a/.github/workflows/ci-tests-unit.yaml b/.github/workflows/ci-tests-unit.yaml new file mode 100644 index 000000000..152f78885 --- /dev/null +++ b/.github/workflows/ci-tests-unit.yaml @@ -0,0 +1,49 @@ +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: 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:unit diff --git a/.github/workflows/i18n.yaml b/.github/workflows/i18n-update-core.yaml similarity index 61% rename from .github/workflows/i18n.yaml rename to .github/workflows/i18n-update-core.yaml index d7df815ff..8a20af4c0 100644 --- a/.github/workflows/i18n.yaml +++ b/.github/workflows/i18n-update-core.yaml @@ -1,4 +1,5 @@ -name: Update Locales +name: "i18n: Update Core" +description: "Generates and updates translations for core ComfyUI components using OpenAI" on: # Manual dispatch for urgent translation updates @@ -14,42 +15,35 @@ jobs: if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-')) runs-on: ubuntu-latest steps: - - uses: Comfy-Org/ComfyUI_frontend_setup_action@v3 - - - name: Cache tool outputs - uses: actions/cache@v4 + - name: Checkout repository + uses: actions/checkout@v5 + + # Setup playwright environment + - name: Setup ComfyUI Frontend + uses: ./.github/actions/setup-frontend 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 + include_build_step: true + - name: Setup ComfyUI Server + uses: ./.github/actions/setup-comfyui-server 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 + 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 & - working-directory: ComfyUI_frontend + + # 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 - 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' @@ -61,6 +55,5 @@ jobs: # 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 diff --staged --quiet || git commit -m "Update locales" git push origin HEAD:${{ github.head_ref }} - working-directory: ComfyUI_frontend diff --git a/.github/workflows/i18n-custom-nodes.yaml b/.github/workflows/i18n-update-custom-nodes.yaml similarity index 68% rename from .github/workflows/i18n-custom-nodes.yaml rename to .github/workflows/i18n-update-custom-nodes.yaml index a5617c196..61076f031 100644 --- a/.github/workflows/i18n-custom-nodes.yaml +++ b/.github/workflows/i18n-update-custom-nodes.yaml @@ -1,4 +1,4 @@ -name: Update Locales for given custom node repository +name: i18n Update Custom Nodes on: workflow_dispatch: @@ -21,92 +21,64 @@ jobs: update-locales: runs-on: ubuntu-latest steps: - - name: Checkout ComfyUI - uses: actions/checkout@v4 + - 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: - 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 + 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@v4 + uses: actions/checkout@v5 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 + - 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 - 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 + + # Start ComfyUI Server + - name: Start ComfyUI Server + shell: bash working-directory: ComfyUI - - name: Install Playwright Browsers - run: npx playwright install chromium --with-deps - working-directory: ComfyUI_frontend + 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 & - working-directory: ComfyUI_frontend + - name: Capture base i18n - run: npx tsx scripts/diff-i18n capture - working-directory: ComfyUI_frontend + run: pnpm exec tsx scripts/diff-i18n capture - 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 + 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 diff --git a/.github/workflows/i18n-node-defs.yaml b/.github/workflows/i18n-update-nodes.yaml similarity index 74% rename from .github/workflows/i18n-node-defs.yaml rename to .github/workflows/i18n-update-nodes.yaml index 1327db3cf..0b9f1534d 100644 --- a/.github/workflows/i18n-node-defs.yaml +++ b/.github/workflows/i18n-update-nodes.yaml @@ -1,4 +1,4 @@ -name: Update Node Definitions Locales +name: i18n Update Nodes on: workflow_dispatch: @@ -13,25 +13,32 @@ 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: 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 & - 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: @@ -45,4 +52,3 @@ jobs: branch: update-locales-node-defs-${{ github.event.inputs.trigger_type }}-${{ github.run_id }} base: main labels: dependencies - path: ComfyUI_frontend \ No newline at end of file diff --git a/.github/workflows/pr-backport.yaml b/.github/workflows/pr-backport.yaml new file mode 100644 index 000000000..1c9a29230 --- /dev/null +++ b/.github/workflows/pr-backport.yaml @@ -0,0 +1,289 @@ +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: Check if backports already exist + id: check-existing + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.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 + + # For manual triggers with force_rerun, proceed anyway + if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.force_rerun }}" = "true" ]; then + echo "skip=false" >> $GITHUB_OUTPUT + echo "::warning::Force rerun requested - existing backports will be updated" + exit 0 + fi + + echo "Found existing backport PRs:" + echo "$EXISTING_BACKPORTS" + echo "skip=true" >> $GITHUB_OUTPUT + echo "::warning::Backport PRs already exist for PR #${PR_NUMBER}, skipping to avoid duplicates" + + - name: Collect backport targets + if: steps.check-existing.outputs.skip != 'true' + 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" =~ ^[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: Backport commits + if: steps.check-existing.outputs.skip != 'true' + id: backport + env: + PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} + run: | + FAILED="" + SUCCESS="" + + # Get PR data for manual triggers + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,mergeCommit) + PR_TITLE=$(echo "$PR_DATA" | jq -r '.title') + MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid') + else + PR_TITLE="${{ github.event.pull_request.title }}" + MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}" + fi + + for target in ${{ steps.targets.outputs.targets }}; do + TARGET_BRANCH="${target}" + SAFE_TARGET=$(echo "$TARGET_BRANCH" | tr '/' '-') + BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}" + + 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 + + # 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}${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 [ -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_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.check-existing.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}" = "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'"
Conflicting files"$'\n\n'"${CONFLICTS_LIST}"$'\n\n'"
" + gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}" + fi + done diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/pr-claude-review.yaml similarity index 67% rename from .github/workflows/claude-pr-review.yml rename to .github/workflows/pr-claude-review.yaml index 3ec61cb3c..b09fde14f 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/pr-claude-review.yaml @@ -1,4 +1,5 @@ -name: Claude PR Review +name: "PR: Claude Review" +description: "AI-powered code review triggered by adding the 'claude-review' label to a PR" permissions: contents: read @@ -11,6 +12,10 @@ on: pull_request: types: [labeled] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: wait-for-ci: runs-on: ubuntu-latest @@ -29,11 +34,9 @@ jobs: - name: Check if we should proceed id: check-status run: | - # Get all check runs for this commit - CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("lint-and-format|test|playwright-tests")) | {name, conclusion}') - - # Check if any required checks failed - if echo "$CHECK_RUNS" | grep -q '"conclusion": "failure"'; then + CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("lint-and-format")) | {name, conclusion}') + + if echo "$CHECK_RUNS" | grep -Eq '"conclusion": "(failure|cancelled|timed_out|action_required)"'; then echo "Some CI checks failed - skipping Claude review" echo "proceed=false" >> $GITHUB_OUTPUT else @@ -47,11 +50,13 @@ jobs: needs: wait-for-ci if: needs.wait-for-ci.outputs.should-proceed == 'true' runs-on: ubuntu-latest + timeout-minutes: 30 steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 + ref: refs/pull/${{ github.event.pull_request.number }}/head - name: Install pnpm uses: pnpm/action-setup@v4 @@ -69,22 +74,26 @@ jobs: pnpm install -g typescript @vue/compiler-sfc - name: Run Claude PR Review - uses: anthropics/claude-code-action@main + uses: anthropics/claude-code-action@v1.0.6 with: label_trigger: "claude-review" - direct_prompt: | - 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. - DO NOT create a summary comment. + prompt: | + 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. + DO NOT create a summary comment. Each issue must be posted as a separate inline comment on the specific line of code. anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - max_turns: 256 - timeout_minutes: 30 - allowed_tools: "Bash(git:*),Bash(gh api:*),Bash(gh pr:*),Bash(gh repo:*),Bash(jq:*),Bash(echo:*),Read,Write,Edit,Glob,Grep,WebFetch" + 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'" env: PR_NUMBER: ${{ github.event.pull_request.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMMIT_SHA: ${{ github.event.pull_request.head.sha }} BASE_SHA: ${{ github.event.pull_request.base.sha }} - REPOSITORY: ${{ github.repository }} \ No newline at end of file + REPOSITORY: ${{ github.repository }} + + - 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 }} diff --git a/.github/workflows/pr-playwright-deploy.yaml b/.github/workflows/pr-playwright-deploy.yaml deleted file mode 100644 index 12051fa99..000000000 --- a/.github/workflows/pr-playwright-deploy.yaml +++ /dev/null @@ -1,280 +0,0 @@ -name: PR Playwright Deploy and Comment - -on: - workflow_run: - workflows: ["Tests CI"] - types: [requested, completed] - -env: - DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p' - -jobs: - deploy-reports: - runs-on: ubuntu-latest - if: github.repository == 'Comfy-Org/ComfyUI_frontend' && github.event.workflow_run.event == 'pull_request' && github.event.action == 'completed' - permissions: - actions: read - strategy: - fail-fast: false - matrix: - browser: [chromium, chromium-2x, chromium-0.5x, mobile-chrome] - steps: - - name: Get PR info - id: pr-info - 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 { number: null, sanitized_branch: null }; - } - - const pr = pullRequests[0]; - const branchName = context.payload.workflow_run.head_branch; - const sanitizedBranch = branchName.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/--+/g, '-').replace(/^-|-$/g, ''); - - return { - number: pr.number, - sanitized_branch: sanitizedBranch - }; - - - name: Set project name - if: fromJSON(steps.pr-info.outputs.result).number != null - id: project-name - run: | - if [ "${{ matrix.browser }}" = "chromium-0.5x" ]; then - echo "name=comfyui-playwright-chromium-0-5x" >> $GITHUB_OUTPUT - else - echo "name=comfyui-playwright-${{ matrix.browser }}" >> $GITHUB_OUTPUT - fi - echo "branch=${{ fromJSON(steps.pr-info.outputs.result).sanitized_branch }}" >> $GITHUB_OUTPUT - - - name: Download playwright report - if: fromJSON(steps.pr-info.outputs.result).number != null - uses: actions/download-artifact@v4 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - run-id: ${{ github.event.workflow_run.id }} - name: playwright-report-${{ matrix.browser }} - path: playwright-report - - - name: Install Wrangler - if: fromJSON(steps.pr-info.outputs.result).number != null - run: npm install -g wrangler - - - name: Deploy to Cloudflare Pages (${{ matrix.browser }}) - if: fromJSON(steps.pr-info.outputs.result).number != null - id: cloudflare-deploy - continue-on-error: true - run: | - # Retry logic for wrangler deploy (3 attempts) - RETRY_COUNT=0 - MAX_RETRIES=3 - SUCCESS=false - - while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ $SUCCESS = false ]; do - RETRY_COUNT=$((RETRY_COUNT + 1)) - echo "Deployment attempt $RETRY_COUNT of $MAX_RETRIES..." - - if npx wrangler pages deploy playwright-report --project-name=${{ steps.project-name.outputs.name }} --branch=${{ steps.project-name.outputs.branch }}; then - SUCCESS=true - echo "Deployment successful on attempt $RETRY_COUNT" - else - echo "Deployment failed on attempt $RETRY_COUNT" - if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then - echo "Retrying in 10 seconds..." - sleep 10 - fi - fi - done - - if [ $SUCCESS = false ]; then - echo "All deployment attempts failed" - exit 1 - fi - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - - comment-tests-starting: - runs-on: ubuntu-latest - if: github.repository == 'Comfy-Org/ComfyUI_frontend' && github.event.workflow_run.event == 'pull_request' && github.event.action == 'requested' - 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: Get completion time - id: completion-time - run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT - - - name: Generate comment body for start - if: steps.pr.outputs.result != 'null' - id: comment-body-start - run: | - echo "" > comment.md - echo "## 🎭 Playwright Test Results" >> comment.md - echo "" >> comment.md - echo "comfy-loading-gif **Tests are starting...** " >> comment.md - echo "" >> comment.md - echo "⏰ Started at: ${{ steps.completion-time.outputs.time }} UTC" >> comment.md - echo "" >> comment.md - echo "### 🚀 Running Tests" >> comment.md - echo "- 🧪 **chromium**: Running tests..." >> comment.md - echo "- 🧪 **chromium-0.5x**: Running tests..." >> comment.md - echo "- 🧪 **chromium-2x**: Running tests..." >> comment.md - echo "- 🧪 **mobile-chrome**: Running tests..." >> comment.md - echo "" >> comment.md - echo "---" >> comment.md - echo "⏱️ Please wait while tests are running across all browsers..." >> comment.md - - - name: Comment PR - Tests Started - if: steps.pr.outputs.result != 'null' - uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0 - with: - issue-number: ${{ steps.pr.outputs.result }} - body-includes: '' - comment-author: 'github-actions[bot]' - edit-mode: replace - body-path: comment.md - - comment-tests-completed: - runs-on: ubuntu-latest - needs: deploy-reports - if: github.repository == 'Comfy-Org/ComfyUI_frontend' && github.event.workflow_run.event == 'pull_request' && github.event.action == 'completed' && always() - 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: Download all deployment info - if: steps.pr.outputs.result != 'null' - uses: actions/download-artifact@v4 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - run-id: ${{ github.event.workflow_run.id }} - pattern: deployment-info-* - merge-multiple: true - path: deployment-info - - - name: Get completion time - id: completion-time - run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT - - - name: Generate comment body for completion - if: steps.pr.outputs.result != 'null' - id: comment-body-completed - run: | - echo "" > comment.md - echo "## 🎭 Playwright Test Results" >> comment.md - echo "" >> comment.md - - # Check if all tests passed - ALL_PASSED=true - for file in deployment-info/*.txt; do - if [ -f "$file" ]; then - browser=$(basename "$file" .txt) - info=$(cat "$file") - exit_code=$(echo "$info" | cut -d'|' -f2) - if [ "$exit_code" != "0" ]; then - ALL_PASSED=false - break - fi - fi - done - - if [ "$ALL_PASSED" = "true" ]; then - echo "✅ **All tests passed across all browsers!**" >> comment.md - else - echo "❌ **Some tests failed!**" >> comment.md - fi - - echo "" >> comment.md - echo "⏰ Completed at: ${{ steps.completion-time.outputs.time }} UTC" >> comment.md - echo "" >> comment.md - echo "### 📊 Test Reports by Browser" >> comment.md - - for file in deployment-info/*.txt; do - if [ -f "$file" ]; then - browser=$(basename "$file" .txt) - info=$(cat "$file") - exit_code=$(echo "$info" | cut -d'|' -f2) - url=$(echo "$info" | cut -d'|' -f3) - - # Validate URLs before using them in comments - sanitized_url=$(echo "$url" | grep -E '^https://[a-z0-9.-]+\.pages\.dev(/.*)?$' || echo "INVALID_URL") - if [ "$sanitized_url" = "INVALID_URL" ]; then - echo "Invalid deployment URL detected: $url" - url="#" # Use safe fallback - fi - - if [ "$exit_code" = "0" ]; then - status="✅" - else - status="❌" - fi - - echo "- $status **$browser**: [View Report]($url)" >> comment.md - fi - done - - echo "" >> comment.md - echo "---" >> comment.md - if [ "$ALL_PASSED" = "true" ]; then - echo "🎉 Your tests are passing across all browsers!" >> comment.md - else - echo "⚠️ Please check the test reports for details on failures." >> comment.md - fi - - - name: Comment PR - Tests Complete - if: steps.pr.outputs.result != 'null' - uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0 - with: - issue-number: ${{ steps.pr.outputs.result }} - body-includes: '' - comment-author: 'github-actions[bot]' - edit-mode: replace - body-path: comment.md \ No newline at end of file diff --git a/.github/workflows/pr-storybook-comment.yaml b/.github/workflows/pr-storybook-comment.yaml deleted file mode 100644 index 53691d826..000000000 --- a/.github/workflows/pr-storybook-comment.yaml +++ /dev/null @@ -1,126 +0,0 @@ -name: PR Storybook Comment - -on: - workflow_run: - workflows: ['Chromatic'] - types: [requested, completed] - -jobs: - comment-storybook: - runs-on: ubuntu-latest - if: >- - github.repository == 'Comfy-Org/ComfyUI_frontend' - && github.event.workflow_run.event == 'pull_request' - && startsWith(github.event.workflow_run.head_branch, 'version-bump-') - permissions: - pull-requests: write - actions: read - steps: - - name: Get PR number - id: pr - uses: actions/github-script@v7 - with: - script: | - const { data: pullRequests } = await github.rest.pulls.list({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`, - }); - - if (pullRequests.length === 0) { - console.log('No open PR found for this branch'); - return null; - } - - return pullRequests[0].number; - - - name: Log when no PR found - if: steps.pr.outputs.result == 'null' - run: | - echo "⚠️ No open PR found for branch: ${{ github.event.workflow_run.head_branch }}" - echo "Workflow run ID: ${{ github.event.workflow_run.id }}" - echo "Repository: ${{ github.event.workflow_run.repository.full_name }}" - echo "Event: ${{ github.event.workflow_run.event }}" - - - name: Get workflow run details - if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' - id: workflow-run - uses: actions/github-script@v7 - with: - script: | - const run = await github.rest.actions.getWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id, - }); - - return { - conclusion: run.data.conclusion, - html_url: run.data.html_url - }; - - - name: Get completion time - id: completion-time - run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT - - - name: Comment PR - Storybook Started - if: steps.pr.outputs.result != 'null' && github.event.action == 'requested' - uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0 - with: - issue-number: ${{ steps.pr.outputs.result }} - body-includes: '' - comment-author: 'github-actions[bot]' - edit-mode: replace - body: | - - ## 🎨 Storybook Build Status - - comfy-loading-gif **Build is starting...** - - ⏰ Started at: ${{ steps.completion-time.outputs.time }} UTC - - ### 🚀 Building Storybook - - 📦 Installing dependencies... - - 🔧 Building Storybook components... - - 🎨 Running Chromatic visual tests... - - --- - ⏱️ Please wait while the Storybook build is in progress... - - - name: Comment PR - Storybook Complete - if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' - uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0 - with: - issue-number: ${{ steps.pr.outputs.result }} - body-includes: '' - comment-author: 'github-actions[bot]' - edit-mode: replace - body: | - - ## 🎨 Storybook Build Status - - ${{ - fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && '✅' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && '⏭️' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && '🚫' - || '❌' - }} **${{ - fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && 'Build completed successfully!' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && 'Build skipped.' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && 'Build cancelled.' - || 'Build failed!' - }}** - - ⏰ Completed at: ${{ steps.completion-time.outputs.time }} UTC - - ### 🔗 Links - - [📊 View Workflow Run](${{ fromJSON(steps.workflow-run.outputs.result).html_url }}) - - --- - ${{ - fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && '🎉 Your Storybook is ready for review!' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && 'ℹ️ Chromatic was skipped for this PR.' - || fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && 'ℹ️ The Chromatic run was cancelled.' - || '⚠️ Please check the workflow logs for error details.' - }} diff --git a/.github/workflows/pr-update-playwright-expectations.yaml b/.github/workflows/pr-update-playwright-expectations.yaml new file mode 100644 index 000000000..f688c3250 --- /dev/null +++ b/.github/workflows/pr-update-playwright-expectations.yaml @@ -0,0 +1,108 @@ +# 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: + test: + 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') ) + steps: + - name: Find Update Comment + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad + id: "find-update-comment" + with: + issue-number: ${{ github.event.number || github.event.issue.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: ${{ github.event.number || github.event.issue.number }} + body: | + Updating Playwright Expectations + edit-mode: replace + reactions: eyes + + - name: Get Branch SHA + id: "get-branch" + run: echo ::set-output name=branch::$(gh pr view $PR_NO --repo $REPO --json headRefName --jq '.headRefName') + env: + REPO: ${{ github.repository }} + PR_NO: ${{ github.event.number || github.event.issue.number }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Initial Checkout + uses: actions/checkout@v5 + with: + ref: ${{ steps.get-branch.outputs.branch }} + - name: Setup 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: Run Playwright tests and update snapshots + id: playwright-tests + run: pnpm exec playwright test --update-snapshots + continue-on-error: true + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: ./playwright-report/ + retention-days: 30 + - name: Debugging info + run: | + echo "PR: ${{ github.event.issue.number }}" + echo "Branch: ${{ steps.get-branch.outputs.branch }}" + git status + - name: Commit updated expectations + run: | + git config --global user.name 'github-actions' + git config --global user.email 'github-actions@github.com' + git add browser_tests + if git diff --cached --quiet; then + echo "No changes to commit" + else + git commit -m "[automated] Update test expectations" + git push origin ${{ steps.get-branch.outputs.branch }} + fi + + - name: Add Done Reaction + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 + if: github.event_name == 'issue_comment' + with: + comment-id: ${{ steps.find-update-comment.outputs.comment-id }} + issue-number: ${{ github.event.number || github.event.issue.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 ${{ github.event.pull_request.number }} --remove-label "New Browser Test Expectations" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-desktop-ui-on-merge.yaml b/.github/workflows/publish-desktop-ui-on-merge.yaml new file mode 100644 index 000000000..b1adaa0c2 --- /dev/null +++ b/.github/workflows/publish-desktop-ui-on-merge.yaml @@ -0,0 +1,59 @@ +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 }} + diff --git a/.github/workflows/publish-desktop-ui.yaml b/.github/workflows/publish-desktop-ui.yaml new file mode 100644 index 000000000..356cf9e78 --- /dev/null +++ b/.github/workflows/publish-desktop-ui.yaml @@ -0,0 +1,206 @@ +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: Pack (preview only) + shell: bash + working-directory: ${{ steps.pkg.outputs.publish_dir }} + run: | + set -euo pipefail + npm pack --json | tee pack-result.json + + - name: Upload package tarball artifact + uses: actions/upload-artifact@v4 + with: + name: desktop-ui-npm-tarball-${{ inputs.version }} + path: ${{ steps.pkg.outputs.publish_dir }}/*.tgz + if-no-files-found: error + + - 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 }} diff --git a/.github/workflows/create-release-candidate-branch.yaml b/.github/workflows/release-branch-create.yaml similarity index 78% rename from .github/workflows/create-release-candidate-branch.yaml rename to .github/workflows/release-branch-create.yaml index 84b545478..992e779dd 100644 --- a/.github/workflows/create-release-candidate-branch.yaml +++ b/.github/workflows/release-branch-create.yaml @@ -1,4 +1,4 @@ -name: Create Release Branch +name: Release Branch Create on: pull_request: @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }} @@ -128,45 +128,6 @@ jobs: echo "- Critical security patches" echo "- Documentation updates" - - name: Create branch protection rules - if: steps.check_version.outputs.is_minor_bump == 'true' && env.branch_exists != 'true' - env: - GITHUB_TOKEN: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }} - run: | - BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}" - - # Create branch protection using GitHub API - echo "Setting up branch protection for $BRANCH_NAME..." - - RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${{ github.repository }}/branches/$BRANCH_NAME/protection" \ - -d '{ - "required_status_checks": { - "strict": true, - "contexts": ["lint-and-format", "test", "playwright-tests"] - }, - "enforce_admins": false, - "required_pull_request_reviews": { - "required_approving_review_count": 1, - "dismiss_stale_reviews": true - }, - "restrictions": null, - "allow_force_pushes": false, - "allow_deletions": false - }') - - HTTP_CODE=$(echo "$RESPONSE" | tail -n 1) - BODY=$(echo "$RESPONSE" | sed '$d') - - if [[ "$HTTP_CODE" -eq 200 ]] || [[ "$HTTP_CODE" -eq 201 ]]; then - echo "✅ Branch protection successfully applied" - else - echo "⚠️ Failed to apply branch protection (HTTP $HTTP_CODE)" - echo "Response: $BODY" - # Don't fail the workflow, just warn - fi - name: Post summary if: steps.check_version.outputs.is_minor_bump == 'true' diff --git a/.github/workflows/release.yaml b/.github/workflows/release-draft-create.yaml similarity index 79% rename from .github/workflows/release.yaml rename to .github/workflows/release-draft-create.yaml index 8958ce147..d26744f56 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release-draft-create.yaml @@ -1,4 +1,4 @@ -name: Create Release Draft +name: Release Draft Create on: pull_request: @@ -18,7 +18,7 @@ jobs: is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 with: @@ -55,6 +55,7 @@ jobs: SENTRY_DSN: ${{ secrets.SENTRY_DSN }} ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} + ENABLE_MINIFY: 'true' USE_PROD_CONFIG: 'true' run: | pnpm install --frozen-lockfile @@ -73,7 +74,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Download dist artifact uses: actions/download-artifact@v4 with: @@ -98,7 +99,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Download dist artifact uses: actions/download-artifact@v4 with: @@ -126,34 +127,8 @@ jobs: publish_types: needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - cache: 'pnpm' - registry-url: https://registry.npmjs.org - - - name: Cache tool outputs - uses: actions/cache@v4 - with: - path: | - .cache - tsconfig.tsbuildinfo - dist - key: types-tools-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - types-tools-cache-${{ runner.os }}- - - - run: pnpm install --frozen-lockfile - - run: pnpm build:types - - name: Publish package - run: pnpm publish --access public - working-directory: dist - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + uses: ./.github/workflows/release-npm-types.yaml + with: + version: ${{ needs.build.outputs.version }} + ref: ${{ github.event.pull_request.merge_commit_sha }} + secrets: inherit diff --git a/.github/workflows/release-npm-types.yaml b/.github/workflows/release-npm-types.yaml new file mode 100644 index 000000000..23f0cc016 --- /dev/null +++ b/.github/workflows/release-npm-types.yaml @@ -0,0 +1,139 @@ +name: Release NPM Types + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 1.26.7)' + required: true + type: string + dist_tag: + description: 'npm dist-tag to use' + required: true + default: latest + type: string + ref: + description: 'Git ref to checkout (commit SHA, tag, or branch)' + required: false + type: string + workflow_call: + inputs: + version: + required: true + type: string + dist_tag: + required: false + type: string + default: latest + ref: + required: false + type: string + +concurrency: + group: publish-frontend-types-${{ github.workflow }}-${{ inputs.version }}-${{ inputs.dist_tag }} + cancel-in-progress: false + +jobs: + publish_types_manual: + name: Publish @comfyorg/comfyui-frontend-types + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Validate inputs + shell: bash + run: | + set -euo pipefail + VERSION="${{ inputs.version }}" + SEMVER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$' + if [[ ! "$VERSION" =~ $SEMVER_REGEX ]]; then + echo "::error title=Invalid version::Version '$VERSION' must follow semantic versioning (x.y.z[-suffix][+build])" >&2 + exit 1 + fi + + - name: Determine ref to checkout + id: resolve_ref + shell: bash + run: | + set -euo pipefail + REF="${{ inputs.ref }}" + VERSION="${{ inputs.version }}" + if [ -n "$REF" ]; then + if ! git check-ref-format --allow-onelevel "$REF"; then + echo "::error title=Invalid ref::Ref '$REF' fails git check-ref-format validation." >&2 + exit 1 + fi + echo "ref=$REF" >> "$GITHUB_OUTPUT" + else + echo "ref=refs/tags/v$VERSION" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout repository + uses: actions/checkout@v5 + with: + ref: ${{ steps.resolve_ref.outputs.ref }} + fetch-depth: 1 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: 'lts/*' + cache: 'pnpm' + registry-url: https://registry.npmjs.org + + - name: Install dependencies + run: pnpm install --frozen-lockfile + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' + + - name: Build types + run: pnpm build:types + + - name: Verify version matches input + id: verify + shell: bash + run: | + PKG_VERSION=$(node -p "require('./package.json').version") + TYPES_PKG_VERSION=$(node -p "require('./dist/package.json').version") + if [ "$PKG_VERSION" != "${{ inputs.version }}" ]; then + echo "Error: package.json version $PKG_VERSION does not match input ${{ inputs.version }}" >&2 + exit 1 + fi + if [ "$TYPES_PKG_VERSION" != "${{ inputs.version }}" ]; then + echo "Error: dist/package.json version $TYPES_PKG_VERSION does not match input ${{ inputs.version }}" >&2 + exit 1 + fi + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + + - name: Check if version already on npm + id: check_npm + shell: bash + run: | + set -euo pipefail + NAME=$(node -p "require('./dist/package.json').name") + VER="${{ steps.verify.outputs.version }}" + STATUS=0 + OUTPUT=$(npm view "${NAME}@${VER}" --json 2>&1) || STATUS=$? + if [ "$STATUS" -eq 0 ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + echo "::warning title=Already published::${NAME}@${VER} already exists on npm. Skipping publish." + else + if echo "$OUTPUT" | grep -q "E404"; then + echo "exists=false" >> "$GITHUB_OUTPUT" + else + echo "::error title=Registry lookup failed::$OUTPUT" >&2 + exit "$STATUS" + fi + fi + + - name: Publish package + if: steps.check_npm.outputs.exists == 'false' + run: pnpm publish --access public --tag "${{ inputs.dist_tag }}" --no-git-checks + working-directory: dist + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/dev-release.yaml b/.github/workflows/release-pypi-dev.yaml similarity index 95% rename from .github/workflows/dev-release.yaml rename to .github/workflows/release-pypi-dev.yaml index 0b45420c6..1bb375025 100644 --- a/.github/workflows/dev-release.yaml +++ b/.github/workflows/release-pypi-dev.yaml @@ -1,4 +1,4 @@ -name: Create Dev PyPI Package +name: Release PyPI Dev on: workflow_dispatch: @@ -15,7 +15,7 @@ jobs: version: ${{ steps.current_version.outputs.version }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install pnpm uses: pnpm/action-setup@v4 with: @@ -44,6 +44,7 @@ jobs: SENTRY_DSN: ${{ secrets.SENTRY_DSN }} ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} + ENABLE_MINIFY: 'true' USE_PROD_CONFIG: 'true' run: | pnpm install --frozen-lockfile @@ -62,7 +63,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Download dist artifact uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/version-bump.yaml b/.github/workflows/release-version-bump.yaml similarity index 62% rename from .github/workflows/version-bump.yaml rename to .github/workflows/release-version-bump.yaml index 77021e5c7..5f3152285 100644 --- a/.github/workflows/version-bump.yaml +++ b/.github/workflows/release-version-bump.yaml @@ -1,4 +1,5 @@ -name: Version Bump +name: "Release: Version Bump" +description: "Manual workflow to increment package version with semantic versioning support" on: workflow_dispatch: @@ -14,6 +15,11 @@ on: 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: @@ -24,7 +30,25 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 + 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 uses: pnpm/action-setup@v4 @@ -58,7 +82,9 @@ jobs: title: ${{ steps.bump-version.outputs.NEW_VERSION }} body: | ${{ 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 }} - base: main + base: ${{ github.event.inputs.branch }} labels: | Release diff --git a/.github/workflows/size-data.yml b/.github/workflows/size-data.yml new file mode 100644 index 000000000..8da55d0c2 --- /dev/null +++ b/.github/workflows/size-data.yml @@ -0,0 +1,52 @@ +name: 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 diff --git a/.github/workflows/size-report.yml b/.github/workflows/size-report.yml new file mode 100644 index 000000000..caaafd30a --- /dev/null +++ b/.github/workflows/size-report.yml @@ -0,0 +1,104 @@ +name: size report + +on: + workflow_run: + workflows: ['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: + size-report: + 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: size-data.yml + 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 }} + + body-include: '' diff --git a/.github/workflows/test-browser-exp.yaml b/.github/workflows/test-browser-exp.yaml deleted file mode 100644 index 63052c3e4..000000000 --- a/.github/workflows/test-browser-exp.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# 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 diff --git a/.github/workflows/test-ui.yaml b/.github/workflows/test-ui.yaml deleted file mode 100644 index be5c0cfc2..000000000 --- a/.github/workflows/test-ui.yaml +++ /dev/null @@ -1,251 +0,0 @@ -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 }} - 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: 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: 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: Install Playwright Browsers - run: npx playwright install chromium --with-deps - working-directory: ComfyUI_frontend - - - 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: 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: Install Playwright Browsers - run: npx playwright install chromium --with-deps - working-directory: ComfyUI_frontend - - - name: Run Playwright tests (${{ matrix.browser }}) - id: playwright - run: npx playwright test --project=${{ matrix.browser }} --reporter=html - 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: npx playwright merge-reports --reporter html ./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 \ No newline at end of file diff --git a/.github/workflows/version-bump-desktop-ui.yaml b/.github/workflows/version-bump-desktop-ui.yaml new file mode 100644 index 000000000..123cdbd9b --- /dev/null +++ b/.github/workflows/version-bump-desktop-ui.yaml @@ -0,0 +1,95 @@ +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 + diff --git a/.github/workflows/vitest.yaml b/.github/workflows/vitest.yaml deleted file mode 100644 index cba1dbe05..000000000 --- a/.github/workflows/vitest.yaml +++ /dev/null @@ -1,46 +0,0 @@ -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 diff --git a/.gitignore b/.gitignore index db0b8454c..8f69ce164 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ yarn.lock # Cache files .eslintcache .prettiercache +.stylelintcache node_modules dist @@ -22,6 +23,8 @@ dist-ssr *.local # Claude configuration .claude/*.local.json +.claude/*.local.md +.claude/*.local.txt CLAUDE.local.md # Editor directories and files @@ -29,6 +32,7 @@ CLAUDE.local.md *.code-workspace !.vscode/extensions.json !.vscode/tailwind.json +!.vscode/custom-css.json !.vscode/settings.json.default !.vscode/launch.json.default .idea @@ -44,6 +48,7 @@ components.d.ts tests-ui/data/* tests-ui/ComfyUI_examples tests-ui/workflows/examples +coverage/ # Browser tests /test-results/ @@ -51,6 +56,7 @@ tests-ui/workflows/examples /blob-report/ /playwright/.cache/ browser_tests/**/*-win32.png +browser_tests/local/ .env @@ -77,8 +83,8 @@ vite.config.mts.timestamp-*.mjs *storybook.log storybook-static - - +# MCP Servers +.playwright-mcp/* .nx/cache .nx/workspace-data diff --git a/.husky/pre-commit b/.husky/pre-commit index 6b8a399e4..c0b5cf437 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env bash -npx lint-staged -npx tsx scripts/check-unused-i18n-keys.ts \ No newline at end of file +pnpm exec lint-staged +pnpm exec tsx scripts/check-unused-i18n-keys.ts \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 000000000..ec1fc17d0 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# Run Knip with cache via package script +pnpm knip + diff --git a/.i18nrc.cjs b/.i18nrc.cjs index 0429c3578..2efe5f966 100644 --- a/.i18nrc.cjs +++ b/.i18nrc.cjs @@ -9,7 +9,7 @@ module.exports = defineConfig({ entry: 'src/locales/en', entryLocale: 'en', output: 'src/locales', - outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar'], + outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr'], reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream. 'latent' is the short form of 'latent space'. 'mask' is in the context of image processing. diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index 82e215be0..000000000 --- a/.mcp.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "mcpServers": { - "playwright": { - "command": "npx", - "args": ["-y", "@executeautomation/playwright-mcp-server"] - }, - "context7": { - "command": "npx", - "args": ["-y", "@upstash/context7-mcp"] - } - } -} \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..1b28e47ce --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +ignore-workspace-root-check=true +catalog-mode=prefer diff --git a/.prettierignore b/.prettierignore index cccae51c9..4403edd8e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ -src/types/comfyRegistryTypes.ts -src/types/generatedManagerTypes.ts \ No newline at end of file +packages/registry-types/src/comfyRegistryTypes.ts +src/types/generatedManagerTypes.ts diff --git a/.storybook/CLAUDE.md b/.storybook/CLAUDE.md index 3877181a2..ca8248784 100644 --- a/.storybook/CLAUDE.md +++ b/.storybook/CLAUDE.md @@ -4,7 +4,7 @@ - `pnpm storybook`: Start Storybook development server - `pnpm build-storybook`: Build static Storybook -- `pnpm test:component`: Run component tests (includes Storybook components) +- `pnpm test:unit`: Run unit tests (includes Storybook components) ## Development Workflow for Storybook diff --git a/.storybook/README.md b/.storybook/README.md index 0d34474ec..be5405e51 100644 --- a/.storybook/README.md +++ b/.storybook/README.md @@ -211,18 +211,17 @@ This Storybook setup includes: ## Icon Usage in Storybook -In this project, the `` syntax from unplugin-icons is not supported in Storybook. +In this project, only the `` syntax from unplugin-icons is supported in Storybook. **Example:** ```vue ``` diff --git a/.storybook/main.ts b/.storybook/main.ts index a799ec143..d61f72eae 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -15,26 +15,37 @@ const config: StorybookConfig = { 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.filter((plugin: any) => { - if (plugin && plugin.name && plugin.name.includes('import-map')) { - return false - } - return true - }) + config.plugins = config.plugins + // Type guard: ensure we have valid plugin objects with names + .filter( + (plugin): plugin is NonNullable & { 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() + '/src/assets/icons/custom' + process.cwd() + '/packages/design-system/src/icons' ) } }), @@ -65,11 +76,6 @@ const config: StorybookConfig = { }, build: { rollupOptions: { - external: () => { - // Don't externalize any modules in Storybook build - // This ensures PrimeVue and other dependencies are bundled - return false - }, onwarn: (warning, warn) => { // Suppress specific warnings if ( diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index 76aca2401..05e082ef0 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -57,9 +57,8 @@ /* Override Storybook's problematic & selector styles */ /* Reset only the specific properties that Storybook injects */ - #storybook-root li+li, - #storybook-docs li+li { - margin: inherit; - padding: inherit; + li+li { + margin: 0; + padding: revert-layer; } \ No newline at end of file diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 747bbe802..bfe81f431 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,7 +1,7 @@ import { definePreset } from '@primevue/themes' import Aura from '@primevue/themes/aura' import { setup } from '@storybook/vue3' -import type { Preview } from '@storybook/vue3-vite' +import type { Preview, StoryContext, StoryFn } from '@storybook/vue3-vite' import { createPinia } from 'pinia' import 'primeicons/primeicons.css' import PrimeVue from 'primevue/config' @@ -9,11 +9,9 @@ import ConfirmationService from 'primevue/confirmationservice' import ToastService from 'primevue/toastservice' import Tooltip from 'primevue/tooltip' -import '../src/assets/css/style.css' -import { i18n } from '../src/i18n' -import '../src/lib/litegraph/public/css/litegraph.css' -import { useWidgetStore } from '../src/stores/widgetStore' -import { useColorPaletteStore } from '../src/stores/workspace/colorPaletteStore' +import '@/assets/css/style.css' +import { i18n } from '@/i18n' +import '@/lib/litegraph/public/css/litegraph.css' const ComfyUIPreset = definePreset(Aura, { semantic: { @@ -25,13 +23,11 @@ const ComfyUIPreset = definePreset(Aura, { // Setup Vue app for Storybook setup((app) => { app.directive('tooltip', Tooltip) + + // Create Pinia instance const pinia = createPinia() + app.use(pinia) - - // Initialize stores - useColorPaletteStore(pinia) - useWidgetStore(pinia) - app.use(i18n) app.use(PrimeVue, { theme: { @@ -50,8 +46,8 @@ setup((app) => { app.use(ToastService) }) -// Dark theme decorator -export const withTheme = (Story: any, context: any) => { +// Theme and dialog decorator +export const withTheme = (Story: StoryFn, context: StoryContext) => { const theme = context.globals.theme || 'light' // Apply theme class to document root @@ -63,7 +59,7 @@ export const withTheme = (Story: any, context: any) => { document.body.classList.remove('dark-theme') } - return Story() + return Story(context.args, context) } const preview: Preview = { diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 000000000..276ac8156 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,74 @@ +{ + "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"] +} diff --git a/.vscode/custom-css.json b/.vscode/custom-css.json new file mode 100644 index 000000000..67aa038b4 --- /dev/null +++ b/.vscode/custom-css.json @@ -0,0 +1,50 @@ +{ + "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" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json.default b/.vscode/settings.json.default index f1ed5fba6..18a614290 100644 --- a/.vscode/settings.json.default +++ b/.vscode/settings.json.default @@ -1,5 +1,6 @@ { "css.customData": [ - ".vscode/tailwind.json" + ".vscode/tailwind.json", + ".vscode/custom-css.json" ] } diff --git a/.vscode/tailwind.json b/.vscode/tailwind.json index 96a1f5797..659c28859 100644 --- a/.vscode/tailwind.json +++ b/.vscode/tailwind.json @@ -2,52 +2,92 @@ "version": 1.1, "atDirectives": [ { - "name": "@tailwind", - "description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.", + "name": "@import", + "description": "Use the `@import` directive to inline CSS files, including Tailwind itself, into your stylesheet.", "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#tailwind" + "url": "https://tailwindcss.com/docs/functions-and-directives#import-directive" + } + ] + }, + { + "name": "@theme", + "description": "Use the `@theme` directive to define custom design tokens like fonts, colors, and breakpoints.", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/functions-and-directives#theme-directive" + } + ] + }, + { + "name": "@layer", + "description": "Use the `@layer` directive inside `@theme` to organize custom styles into different layers like `base`, `components`, and `utilities`.", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/theme#layers" } ] }, { "name": "@apply", - "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.", + "description": "DO NOT USE. IF YOU ARE CAUGHT USING @apply YOU WILL FACE SEVERE CONSEQUENCES.", "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#apply" + "url": "https://tailwindcss.com/docs/functions-and-directives#apply-directive" } ] }, { - "name": "@responsive", - "description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n", + "name": "@config", + "description": "Use the `@config` directive to load a legacy JavaScript-based Tailwind configuration file.", "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#responsive" + "url": "https://tailwindcss.com/docs/functions-and-directives#config-directive" } ] }, { - "name": "@screen", - "description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n", + "name": "@reference", + "description": "Use the `@reference` directive to import theme variables, custom utilities, and custom variants from other files without duplicating CSS.", "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#screen" + "url": "https://tailwindcss.com/docs/functions-and-directives#reference-directive" } ] }, { - "name": "@variants", - "description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n", + "name": "@plugin", + "description": "Use the `@plugin` directive to load a legacy JavaScript-based Tailwind plugin.", "references": [ { "name": "Tailwind Documentation", - "url": "https://tailwindcss.com/docs/functions-and-directives#variants" + "url": "https://tailwindcss.com/docs/functions-and-directives#plugin-directive" + } + ] + }, + { + "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" } ] } diff --git a/AGENTS.md b/AGENTS.md index 5cec6b810..0d060af1f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,15 +5,14 @@ - 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/`. - Public assets: `public/`. Build output: `dist/`. -- Config: `vite.config.mts`, `vitest.config.ts`, `playwright.config.ts`, `eslint.config.js`, `.prettierrc`. +- Config: `vite.config.mts`, `vitest.config.ts`, `playwright.config.ts`, `eslint.config.ts`, `.prettierrc`. ## Build, Test, and Development Commands - `pnpm dev`: Start Vite dev server. - `pnpm dev:electron`: Dev server with Electron API mocks. - `pnpm build`: Type-check then production build to `dist/`. - `pnpm preview`: Preview the production build locally. -- `pnpm test:unit`: Run Vitest unit tests (`tests-ui/`). -- `pnpm test:component`: Run component tests (`src/components/`). +- `pnpm test:unit`: Run Vitest unit tests. - `pnpm test:browser`: Run Playwright E2E tests (`browser_tests/`). - `pnpm lint` / `pnpm lint:fix`: Lint (ESLint). `pnpm format` / `format:check`: Prettier. - `pnpm typecheck`: Vue TSC type checking. @@ -31,10 +30,9 @@ - Playwright: place tests in `browser_tests/`; optional tags like `@mobile`, `@2x` are respected by config. ## Commit & Pull Request Guidelines -- 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. Add/adjust tests and i18n strings when applicable. +- Commits: Use `[skip ci]` for locale-only updates when appropriate. +- PRs: Include clear description, linked issues (`- Fixes #123`), and screenshots/GIFs for UI changes. - Quality gates: `pnpm lint`, `pnpm typecheck`, and relevant tests must pass. Keep PRs focused and small. ## Security & Configuration Tips - Secrets: Use `.env` (see `.env_example`); do not commit secrets. -- Backend: Dev server expects ComfyUI backend at `localhost:8188` by default; configure via `.env`. diff --git a/CLAUDE.md b/CLAUDE.md index 2ac2ab06e..0b187fbfc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,7 +18,6 @@ This bootstraps the monorepo with dependencies, builds, tests, and dev server ve - `pnpm build`: Build for production (via nx) - `pnpm lint`: Linting (via nx) - `pnpm format`: Prettier formatting -- `pnpm test:component`: Run component tests with browser environment - `pnpm test:unit`: Run all unit tests - `pnpm test:browser`: Run E2E tests via Playwright - `pnpm test:unit -- tests-ui/tests/example.test.ts`: Run single test file @@ -127,3 +126,5 @@ const value = api.getServerFeature('config_name', defaultValue) // Get config - NEVER use `--no-verify` flag when committing - NEVER delete or disable tests to make them pass - 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 `:class="[]"` to merge class names - always use `import { cn } from '@/utils/tailwindUtil'`, for example: `
` diff --git a/CODEOWNERS b/CODEOWNERS index 8d4e4a90f..d754859b1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,17 +1,63 @@ -# Admins -* @Comfy-Org/comfy_frontend_devs +# Desktop/Electron +/apps/desktop-ui/ @webfiltered +/src/stores/electronDownloadStore.ts @webfiltered +/src/extensions/core/electronAdapter.ts @webfiltered +/vite.electron.config.mts @webfiltered -# Maintainers -*.md @Comfy-Org/comfy_maintainer -/tests-ui/ @Comfy-Org/comfy_maintainer -/browser_tests/ @Comfy-Org/comfy_maintainer -/.env_example @Comfy-Org/comfy_maintainer +# Common UI Components +/src/components/chip/ @viva-jinyi +/src/components/card/ @viva-jinyi +/src/components/button/ @viva-jinyi +/src/components/input/ @viva-jinyi -# Translations (AIGODLIKE team + shinshin86) -/src/locales/ @Yorha4D @KarryCharon @DorotaLuna @shinshin86 @Comfy-Org/comfy_maintainer +# Topbar +/src/components/topbar/ @pythongosssss -# Load 3D extension -/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs +# Thumbnail +/src/renderer/core/thumbnail/ @pythongosssss -# Mask Editor extension -/src/extensions/core/maskeditor.ts @brucew4yn3rp @trsommer @Comfy-Org/comfy_frontend_devs +# Legacy UI +/scripts/ui/ @pythongosssss + +# Link rendering +/src/renderer/core/canvas/links/ @benceruleanlu + +# Node help system +/src/utils/nodeHelpUtil.ts @benceruleanlu +/src/stores/workspace/nodeHelpStore.ts @benceruleanlu +/src/services/nodeHelpService.ts @benceruleanlu + +# Selection toolbox +/src/components/graph/selectionToolbox/ @Myestery + +# Minimap +/src/renderer/extensions/minimap/ @jtydhr88 + +# Assets +/src/platform/assets/ @arjansingh + +# Workflow Templates +/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki +/src/components/templates/ @Myestery @christian-byrne @comfyui-wiki + +# Mask Editor +/src/extensions/core/maskeditor.ts @trsommer @brucew4yn3rp +/src/extensions/core/maskEditorLayerFilenames.ts @trsommer @brucew4yn3rp +/src/extensions/core/maskEditorOld.ts @trsommer @brucew4yn3rp + +# 3D +/src/extensions/core/load3d.ts @jtydhr88 +/src/components/load3d/ @jtydhr88 + +# Manager +/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata + +# Translations +/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer + +# LLM Instructions (blank on purpose) +.claude/ +.cursor/ +.cursorrules +**/AGENTS.md +**/CLAUDE.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f4fd8db8..f1c9341d5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -213,12 +213,6 @@ Here's how Claude Code can use the Playwright MCP server to inspect the interfac - `pnpm i` to install all dependencies - `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 verify the whole app. See [browser_tests/README.md](browser_tests/README.md) for details. @@ -229,7 +223,6 @@ Before submitting a PR, ensure all tests pass: ```bash pnpm test:unit -pnpm test:component pnpm test:browser pnpm typecheck pnpm lint @@ -262,12 +255,12 @@ pnpm format The project supports three types of icons, all with automatic imports (no manual imports needed): 1. **PrimeIcons** - Built-in PrimeVue icons using CSS classes: `` -2. **Iconify Icons** - 200,000+ icons from various libraries: ``, `` +2. **Iconify Icons** - 200,000+ icons from various libraries: ``, `` 3. **Custom Icons** - Your own SVG icons: `` -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. +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. -For detailed instructions and code examples, see [src/assets/icons/README.md](src/assets/icons/README.md). +For detailed instructions and code examples, see [packages/design-system/src/icons/README.md](packages/design-system/src/icons/README.md). ## Working with litegraph.js diff --git a/apps/desktop-ui/.storybook/main.ts b/apps/desktop-ui/.storybook/main.ts new file mode 100644 index 000000000..91c29eb7a --- /dev/null +++ b/apps/desktop-ui/.storybook/main.ts @@ -0,0 +1,103 @@ +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 & { 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' + } + }, + build: { + rollupOptions: { + 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 diff --git a/apps/desktop-ui/.storybook/preview.ts b/apps/desktop-ui/.storybook/preview.ts new file mode 100644 index 000000000..a0ead30cc --- /dev/null +++ b/apps/desktop-ui/.storybook/preview.ts @@ -0,0 +1,88 @@ +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 diff --git a/apps/desktop-ui/index.html b/apps/desktop-ui/index.html new file mode 100644 index 000000000..5cb34ffc2 --- /dev/null +++ b/apps/desktop-ui/index.html @@ -0,0 +1,12 @@ + + + + + ComfyUI Desktop + + + +
+ + + diff --git a/apps/desktop-ui/package.json b/apps/desktop-ui/package.json new file mode 100644 index 000000000..686d35233 --- /dev/null +++ b/apps/desktop-ui/package.json @@ -0,0 +1,117 @@ +{ + "name": "@comfyorg/desktop-ui", + "version": "0.0.1", + "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:" + } +} diff --git a/public/assets/images/Git-Logo-White.svg b/apps/desktop-ui/public/assets/images/Git-Logo-White.svg similarity index 100% rename from public/assets/images/Git-Logo-White.svg rename to apps/desktop-ui/public/assets/images/Git-Logo-White.svg diff --git a/public/assets/images/apple-mps-logo.png b/apps/desktop-ui/public/assets/images/apple-mps-logo.png similarity index 100% rename from public/assets/images/apple-mps-logo.png rename to apps/desktop-ui/public/assets/images/apple-mps-logo.png diff --git a/apps/desktop-ui/public/assets/images/comfy-brand-mark.svg b/apps/desktop-ui/public/assets/images/comfy-brand-mark.svg new file mode 100644 index 000000000..9056ae149 --- /dev/null +++ b/apps/desktop-ui/public/assets/images/comfy-brand-mark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop-ui/public/assets/images/nvidia-logo-square.jpg b/apps/desktop-ui/public/assets/images/nvidia-logo-square.jpg new file mode 100644 index 000000000..c57f20b5d Binary files /dev/null and b/apps/desktop-ui/public/assets/images/nvidia-logo-square.jpg differ diff --git a/public/assets/images/sad_girl.png b/apps/desktop-ui/public/assets/images/sad_girl.png similarity index 100% rename from public/assets/images/sad_girl.png rename to apps/desktop-ui/public/assets/images/sad_girl.png diff --git a/apps/desktop-ui/src/App.vue b/apps/desktop-ui/src/App.vue new file mode 100644 index 000000000..a43d49c99 --- /dev/null +++ b/apps/desktop-ui/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/apps/desktop-ui/src/assets/css/style.css b/apps/desktop-ui/src/assets/css/style.css new file mode 100644 index 000000000..0eef377ae --- /dev/null +++ b/apps/desktop-ui/src/assets/css/style.css @@ -0,0 +1,6 @@ +@import '@comfyorg/design-system/css/style.css'; + +#desktop-app { + position: absolute; + inset: 0; +} diff --git a/apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue b/apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue new file mode 100644 index 000000000..ca0dcf34e --- /dev/null +++ b/apps/desktop-ui/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/components/common/RefreshButton.vue b/apps/desktop-ui/src/components/common/RefreshButton.vue similarity index 94% rename from src/components/common/RefreshButton.vue rename to apps/desktop-ui/src/components/common/RefreshButton.vue index b564ac278..2569d0fbd 100644 --- a/src/components/common/RefreshButton.vue +++ b/apps/desktop-ui/src/components/common/RefreshButton.vue @@ -32,7 +32,7 @@ import Button from 'primevue/button' import ProgressSpinner from 'primevue/progressspinner' -import { PrimeVueSeverity } from '@/types/primeVueTypes' +import type { PrimeVueSeverity } from '@/types/primeVueTypes' const { disabled, diff --git a/apps/desktop-ui/src/components/common/StartupDisplay.vue b/apps/desktop-ui/src/components/common/StartupDisplay.vue new file mode 100644 index 000000000..bd42c14e4 --- /dev/null +++ b/apps/desktop-ui/src/components/common/StartupDisplay.vue @@ -0,0 +1,71 @@ + + + diff --git a/apps/desktop-ui/src/components/common/UrlInput.vue b/apps/desktop-ui/src/components/common/UrlInput.vue new file mode 100644 index 000000000..91f8cfe56 --- /dev/null +++ b/apps/desktop-ui/src/components/common/UrlInput.vue @@ -0,0 +1,129 @@ + + + diff --git a/src/components/install/DesktopSettingsConfiguration.vue b/apps/desktop-ui/src/components/install/DesktopSettingsConfiguration.vue similarity index 87% rename from src/components/install/DesktopSettingsConfiguration.vue rename to apps/desktop-ui/src/components/install/DesktopSettingsConfiguration.vue index 2868d11a7..f1caa62fa 100644 --- a/src/components/install/DesktopSettingsConfiguration.vue +++ b/apps/desktop-ui/src/components/install/DesktopSettingsConfiguration.vue @@ -10,14 +10,14 @@

-
+

{{ $t('install.settings.autoUpdate') }}

-

+

{{ $t('install.settings.autoUpdateDescription') }}

@@ -32,14 +32,10 @@

{{ $t('install.settings.allowMetrics') }}

-

+

{{ $t('install.settings.allowMetricsDescription') }}

- + {{ $t('install.settings.learnMoreAboutData') }}
@@ -51,7 +47,9 @@

@@ -110,11 +108,7 @@ diff --git a/apps/desktop-ui/src/components/install/GpuPicker.vue b/apps/desktop-ui/src/components/install/GpuPicker.vue new file mode 100644 index 000000000..0a8cac0a9 --- /dev/null +++ b/apps/desktop-ui/src/components/install/GpuPicker.vue @@ -0,0 +1,103 @@ + + + diff --git a/apps/desktop-ui/src/components/install/HardwareOption.stories.ts b/apps/desktop-ui/src/components/install/HardwareOption.stories.ts new file mode 100644 index 000000000..d830af49f --- /dev/null +++ b/apps/desktop-ui/src/components/install/HardwareOption.stories.ts @@ -0,0 +1,73 @@ +// eslint-disable-next-line storybook/no-renderer-packages +import type { Meta, StoryObj } from '@storybook/vue3' + +import HardwareOption from './HardwareOption.vue' + +const meta: Meta = { + 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 + +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 + } +} diff --git a/apps/desktop-ui/src/components/install/HardwareOption.vue b/apps/desktop-ui/src/components/install/HardwareOption.vue new file mode 100644 index 000000000..ae254fd8f --- /dev/null +++ b/apps/desktop-ui/src/components/install/HardwareOption.vue @@ -0,0 +1,55 @@ + + + diff --git a/apps/desktop-ui/src/components/install/InstallFooter.vue b/apps/desktop-ui/src/components/install/InstallFooter.vue new file mode 100644 index 000000000..ef9ab698c --- /dev/null +++ b/apps/desktop-ui/src/components/install/InstallFooter.vue @@ -0,0 +1,79 @@ + + + diff --git a/apps/desktop-ui/src/components/install/InstallLocationPicker.stories.ts b/apps/desktop-ui/src/components/install/InstallLocationPicker.stories.ts new file mode 100644 index 000000000..e6ef924ae --- /dev/null +++ b/apps/desktop-ui/src/components/install/InstallLocationPicker.stories.ts @@ -0,0 +1,148 @@ +// 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 = { + 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: '' } + } + ] +} + +export default meta +type Story = StoryObj + +// 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(['models', 'custom_nodes']) + + return { + args, + installPath, + pathError, + migrationSourcePath, + migrationItemIds + } + }, + template: ` +
+ +
+ ` + }) +} + +// 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(['models', 'custom_nodes']) + + return { + args, + installPath, + pathError, + migrationSourcePath, + migrationItemIds + } + }, + template: ` +
+ +
+ ` + }) +} + +// 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(['models', 'custom_nodes']) + + return { + args, + installPath, + pathError, + migrationSourcePath, + migrationItemIds + } + }, + template: ` +
+
+
Parent bg: neutral-950 (#0a0a0a)
+
Accordion content: bg-transparent
+
Migration options: bg-transparent + p-4 rounded-lg
+
+ +
+ ` + }) +} diff --git a/apps/desktop-ui/src/components/install/InstallLocationPicker.vue b/apps/desktop-ui/src/components/install/InstallLocationPicker.vue new file mode 100644 index 000000000..6b139c1e9 --- /dev/null +++ b/apps/desktop-ui/src/components/install/InstallLocationPicker.vue @@ -0,0 +1,312 @@ + + + + + diff --git a/apps/desktop-ui/src/components/install/MigrationPicker.stories.ts b/apps/desktop-ui/src/components/install/MigrationPicker.stories.ts new file mode 100644 index 000000000..ad09e1871 --- /dev/null +++ b/apps/desktop-ui/src/components/install/MigrationPicker.stories.ts @@ -0,0 +1,45 @@ +// 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 = { + 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: '' } + } + ] +} + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => ({ + components: { MigrationPicker }, + setup() { + const sourcePath = ref('') + const migrationItemIds = ref([]) + return { sourcePath, migrationItemIds } + }, + template: + '' + }) +} diff --git a/src/components/install/MigrationPicker.vue b/apps/desktop-ui/src/components/install/MigrationPicker.vue similarity index 91% rename from src/components/install/MigrationPicker.vue rename to apps/desktop-ui/src/components/install/MigrationPicker.vue index 934ffc2f3..ba542ca69 100644 --- a/src/components/install/MigrationPicker.vue +++ b/apps/desktop-ui/src/components/install/MigrationPicker.vue @@ -2,10 +2,6 @@
-

- {{ $t('install.migrateFromExistingInstallation') }} -

-

{{ $t('install.migrationSourcePathDescription') }}

@@ -13,7 +9,7 @@
-
+

{{ $t('install.selectItemsToMigrate') }}

diff --git a/apps/desktop-ui/src/components/install/mirror/MirrorItem.vue b/apps/desktop-ui/src/components/install/mirror/MirrorItem.vue new file mode 100644 index 000000000..457940127 --- /dev/null +++ b/apps/desktop-ui/src/components/install/mirror/MirrorItem.vue @@ -0,0 +1,109 @@ + + + diff --git a/src/components/maintenance/StatusTag.vue b/apps/desktop-ui/src/components/maintenance/StatusTag.vue similarity index 100% rename from src/components/maintenance/StatusTag.vue rename to apps/desktop-ui/src/components/maintenance/StatusTag.vue diff --git a/src/components/maintenance/TaskCard.vue b/apps/desktop-ui/src/components/maintenance/TaskCard.vue similarity index 87% rename from src/components/maintenance/TaskCard.vue rename to apps/desktop-ui/src/components/maintenance/TaskCard.vue index 1287169ce..e7d24d100 100644 --- a/src/components/maintenance/TaskCard.vue +++ b/apps/desktop-ui/src/components/maintenance/TaskCard.vue @@ -1,23 +1,23 @@