diff --git a/.env_example b/.env_example index 520521fe0..81c31f225 100644 --- a/.env_example +++ b/.env_example @@ -5,6 +5,10 @@ PLAYWRIGHT_TEST_URL=http://localhost:5173 # Proxy target of the local development server # Note: localhost:8188 does not work. +# Cloud auto-detection: Setting this to any *.comfy.org URL automatically enables +# cloud mode (DISTRIBUTION=cloud) without needing to set DISTRIBUTION separately. +# Examples: https://testcloud.comfy.org/, https://stagingcloud.comfy.org/, +# https://pr-123.testenvs.comfy.org/, https://cloud.comfy.org/ DEV_SERVER_COMFYUI_URL=http://127.0.0.1:8188 # Allow dev server access from remote IP addresses. diff --git a/.github/workflows/ci-lint-format.yaml b/.github/workflows/ci-lint-format.yaml index 71d5eed15..6ebfd8d38 100644 --- a/.github/workflows/ci-lint-format.yaml +++ b/.github/workflows/ci-lint-format.yaml @@ -39,6 +39,9 @@ jobs: - name: Run ESLint with auto-fix run: pnpm lint:fix + - name: Run Stylelint with auto-fix + run: pnpm stylelint:fix + - name: Run Prettier with auto-format run: pnpm format @@ -63,6 +66,7 @@ jobs: - name: Final validation run: | pnpm lint + pnpm stylelint pnpm format:check pnpm knip diff --git a/.github/workflows/pr-update-playwright-expectations.yaml b/.github/workflows/pr-update-playwright-expectations.yaml index f688c3250..a01362466 100644 --- a/.github/workflows/pr-update-playwright-expectations.yaml +++ b/.github/workflows/pr-update-playwright-expectations.yaml @@ -12,11 +12,11 @@ concurrency: cancel-in-progress: true jobs: - test: + setup: runs-on: ubuntu-latest if: > ( github.event_name == 'pull_request' && github.event.label.name == 'New Browser Test Expectations' ) || - ( github.event.issue.pull_request && + ( github.event.issue.pull_request && github.event_name == 'issue_comment' && ( github.event.comment.author_association == 'OWNER' || @@ -24,12 +24,25 @@ jobs: github.event.comment.author_association == 'COLLABORATOR' ) && startsWith(github.event.comment.body, '/update-playwright') ) + outputs: + cache-key: ${{ steps.cache-key.outputs.key }} + pr-number: ${{ steps.pr-info.outputs.pr-number }} + branch: ${{ steps.pr-info.outputs.branch }} + comment-id: ${{ steps.find-update-comment.outputs.comment-id }} steps: + - name: Get PR info + id: pr-info + run: | + echo "pr-number=${{ github.event.number || github.event.issue.number }}" >> $GITHUB_OUTPUT + echo "branch=$(gh pr view ${{ github.event.number || github.event.issue.number }} --repo ${{ github.repository }} --json headRefName --jq '.headRefName')" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Find Update Comment uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad id: "find-update-comment" with: - issue-number: ${{ github.event.number || github.event.issue.number }} + issue-number: ${{ steps.pr-info.outputs.pr-number }} comment-author: "github-actions[bot]" body-includes: "Updating Playwright Expectations" @@ -37,72 +50,260 @@ jobs: 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 }} + issue-number: ${{ steps.pr-info.outputs.pr-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 + - name: Checkout repository uses: actions/checkout@v5 with: - ref: ${{ steps.get-branch.outputs.branch }} - - name: Setup Frontend + ref: ${{ steps.pr-info.outputs.branch }} + - name: Setup frontend uses: ./.github/actions/setup-frontend with: include_build_step: true - - name: Setup ComfyUI Server + # Save expensive build artifacts (Python env, built frontend, node_modules) + # Source code will be checked out fresh in sharded jobs + - name: Generate cache key + id: cache-key + run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT + - name: Save cache + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 + with: + path: | + ComfyUI + dist + key: comfyui-setup-${{ steps.cache-key.outputs.key }} + + # Sharded snapshot updates + update-snapshots-sharded: + needs: setup + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3, 4] + shardTotal: [4] + steps: + # Checkout source code fresh (not cached) + - name: Checkout repository + uses: actions/checkout@v5 + with: + ref: ${{ needs.setup.outputs.branch }} + + # Restore expensive build artifacts from setup job + - name: Restore cached artifacts + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 + with: + fail-on-cache-miss: true + path: | + ComfyUI + dist + key: comfyui-setup-${{ needs.setup.outputs.cache-key }} + + - name: Setup ComfyUI server (from cache) uses: ./.github/actions/setup-comfyui-server with: launch_server: true + + - name: Setup nodejs, pnpm, reuse built frontend + uses: ./.github/actions/setup-frontend + - name: Setup Playwright uses: ./.github/actions/setup-playwright - - name: Run Playwright tests and update snapshots + + # Run sharded tests with snapshot updates + - name: Update snapshots (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) id: playwright-tests - run: pnpm exec playwright test --update-snapshots + run: pnpm exec playwright test --update-snapshots --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} continue-on-error: true - - uses: actions/upload-artifact@v4 + + # Identify and stage only changed snapshot files + - name: Stage changed snapshot files + id: changed-snapshots + run: | + echo "==========================================" + echo "STAGING CHANGED SNAPSHOTS (Shard ${{ matrix.shardIndex }})" + echo "==========================================" + + # Get list of changed snapshot files + changed_files=$(git diff --name-only browser_tests/ 2>/dev/null | grep -E '\-snapshots/' || echo "") + + if [ -z "$changed_files" ]; then + echo "No snapshot changes in this shard" + echo "has-changes=false" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "✓ Found changed files:" + echo "$changed_files" + file_count=$(echo "$changed_files" | wc -l) + echo "Count: $file_count" + echo "has-changes=true" >> $GITHUB_OUTPUT + echo "" + + # Create staging directory + mkdir -p /tmp/changed_snapshots_shard + + # Copy only changed files, preserving directory structure + # Strip 'browser_tests/' prefix to avoid double nesting + echo "Copying changed files to staging directory..." + while IFS= read -r file; do + # Remove 'browser_tests/' prefix + file_without_prefix="${file#browser_tests/}" + # Create parent directories + mkdir -p "/tmp/changed_snapshots_shard/$(dirname "$file_without_prefix")" + # Copy file + cp "$file" "/tmp/changed_snapshots_shard/$file_without_prefix" + echo " → $file_without_prefix" + done <<< "$changed_files" + + echo "" + echo "Staged files for upload:" + find /tmp/changed_snapshots_shard -type f + + # Upload ONLY the changed files from this shard + - name: Upload changed snapshots + uses: actions/upload-artifact@v4 + if: steps.changed-snapshots.outputs.has-changes == 'true' + with: + name: snapshots-shard-${{ matrix.shardIndex }} + path: /tmp/changed_snapshots_shard/ + retention-days: 1 + + - name: Upload test report + uses: actions/upload-artifact@v4 if: always() with: - name: playwright-report + name: playwright-report-shard-${{ matrix.shardIndex }} path: ./playwright-report/ retention-days: 30 - - name: Debugging info + + # Merge snapshots and commit + merge-and-commit: + needs: [setup, update-snapshots-sharded] + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + ref: ${{ needs.setup.outputs.branch }} + + # Download all changed snapshot files from shards + - name: Download snapshot artifacts + uses: actions/download-artifact@v4 + with: + pattern: snapshots-shard-* + path: ./downloaded-snapshots + merge-multiple: false + + - name: List downloaded files run: | - echo "PR: ${{ github.event.issue.number }}" - echo "Branch: ${{ steps.get-branch.outputs.branch }}" - git status + echo "==========================================" + echo "DOWNLOADED SNAPSHOT FILES" + echo "==========================================" + find ./downloaded-snapshots -type f + echo "" + echo "Total files: $(find ./downloaded-snapshots -type f | wc -l)" + + # Merge only the changed files into browser_tests + - name: Merge changed snapshots + run: | + set -euo pipefail + + echo "==========================================" + echo "MERGING CHANGED SNAPSHOTS" + echo "==========================================" + + # Verify target directory exists + if [ ! -d "browser_tests" ]; then + echo "::error::Target directory 'browser_tests' does not exist" + exit 1 + fi + + merged_count=0 + + # For each shard's changed files, copy them directly + for shard_dir in ./downloaded-snapshots/snapshots-shard-*/; do + if [ ! -d "$shard_dir" ]; then + continue + fi + + shard_name=$(basename "$shard_dir") + file_count=$(find "$shard_dir" -type f | wc -l) + + if [ "$file_count" -eq 0 ]; then + echo " $shard_name: no files" + continue + fi + + echo "Processing $shard_name ($file_count file(s))..." + + # Copy files directly, preserving directory structure + # Since files are already in correct structure (no browser_tests/ prefix), just copy them all + cp -v -r "$shard_dir"* browser_tests/ 2>&1 | sed 's/^/ /' + + merged_count=$((merged_count + 1)) + echo " ✓ Merged" + echo "" + done + + echo "==========================================" + echo "MERGE COMPLETE" + echo "==========================================" + echo "Shards merged: $merged_count" + + - name: Show changes + run: | + echo "==========================================" + echo "CHANGES SUMMARY" + echo "==========================================" + echo "" + echo "Changed files in browser_tests:" + git diff --name-only browser_tests/ | head -20 || echo "No changes" + echo "" + echo "Total changes:" + git diff --name-only browser_tests/ | wc -l || echo "0" + - name: Commit updated expectations + id: commit run: | git config --global user.name 'github-actions' git config --global user.email 'github-actions@github.com' - git add browser_tests - if git diff --cached --quiet; then + + if git diff --quiet browser_tests/; then echo "No changes to commit" - else - git commit -m "[automated] Update test expectations" - git push origin ${{ steps.get-branch.outputs.branch }} + echo "has-changes=false" >> $GITHUB_OUTPUT + exit 0 fi + + echo "==========================================" + echo "COMMITTING CHANGES" + echo "==========================================" + + echo "has-changes=true" >> $GITHUB_OUTPUT + + git add browser_tests/ + git commit -m "[automated] Update test expectations" + + echo "Pushing to ${{ needs.setup.outputs.branch }}..." + git push origin ${{ needs.setup.outputs.branch }} + + echo "✓ Commit and push successful" - name: Add Done Reaction uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 - if: github.event_name == 'issue_comment' + if: github.event_name == 'issue_comment' && steps.commit.outputs.has-changes == 'true' with: - comment-id: ${{ steps.find-update-comment.outputs.comment-id }} - issue-number: ${{ github.event.number || github.event.issue.number }} + comment-id: ${{ needs.setup.outputs.comment-id }} + issue-number: ${{ needs.setup.outputs.pr-number }} reactions: +1 reactions-edit-mode: replace - name: Remove New Browser Test Expectations label if: always() && github.event_name == 'pull_request' - run: gh pr edit ${{ github.event.pull_request.number }} --remove-label "New Browser Test Expectations" + run: gh pr edit ${{ needs.setup.outputs.pr-number }} --remove-label "New Browser Test Expectations" env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 07c5ccc22..aa43a43ac 100644 --- a/.prettierrc +++ b/.prettierrc @@ -7,12 +7,5 @@ "importOrder": ["^@core/(.*)$", "", "^@/(.*)$", "^[./]"], "importOrderSeparation": true, "importOrderSortSpecifiers": true, - "overrides": [ - { - "files": "*.{js,cjs,mjs,ts,cts,mts,tsx,vue}", - "options": { - "plugins": ["@trivago/prettier-plugin-sort-imports"] - } - } - ] + "plugins": ["@prettier/plugin-oxc", "@trivago/prettier-plugin-sort-imports"] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f1c9341d5..6c84779ff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ Have another idea? Drop into Discord or open an issue, and let's chat! ``` 3. Configure environment (optional): - Create a `.env` file in the project root based on the provided [.env.example](.env.example) file. + Create a `.env` file in the project root based on the provided [.env_example](.env_example) file. **Note about ports**: By default, the dev server expects the ComfyUI backend at `localhost:8188`. If your ComfyUI instance runs on a different port, update this in your `.env` file. @@ -325,4 +325,4 @@ If you have questions about contributing: - Ask in our [Discord](https://discord.com/invite/comfyorg) - Open a new issue for clarification -Thank you for contributing to ComfyUI Frontend! \ No newline at end of file +Thank you for contributing to ComfyUI Frontend! diff --git a/browser_tests/helpers/actionbar.ts b/browser_tests/helpers/actionbar.ts index f37de855a..89d5e73ad 100644 --- a/browser_tests/helpers/actionbar.ts +++ b/browser_tests/helpers/actionbar.ts @@ -13,7 +13,7 @@ export class ComfyActionbar { async isDocked() { const className = await this.root.getAttribute('class') - return className?.includes('is-docked') ?? false + return className?.includes('static') ?? false } } diff --git a/browser_tests/tests/dialog.spec.ts b/browser_tests/tests/dialog.spec.ts index 7459acf58..9ccd4cd67 100644 --- a/browser_tests/tests/dialog.spec.ts +++ b/browser_tests/tests/dialog.spec.ts @@ -301,7 +301,9 @@ test.describe('Settings', () => { }) test.describe('Support', () => { - test('Should open external zendesk link', async ({ comfyPage }) => { + test('Should open external zendesk link with OSS tag', async ({ + comfyPage + }) => { await comfyPage.setSetting('Comfy.UseNewMenu', 'Top') const pagePromise = comfyPage.page.context().waitForEvent('page') await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Support']) @@ -309,6 +311,10 @@ test.describe('Support', () => { await newPage.waitForLoadState('networkidle') await expect(newPage).toHaveURL(/.*support\.comfy\.org.*/) + + const url = new URL(newPage.url()) + expect(url.searchParams.get('tf_42243568391700')).toBe('oss') + await newPage.close() }) }) diff --git a/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-hidden-links-chromium-linux.png b/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-hidden-links-chromium-linux.png index a8deb29a0..ed1732e67 100644 Binary files a/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-hidden-links-chromium-linux.png and b/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-hidden-links-chromium-linux.png differ diff --git a/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-visible-links-chromium-linux.png b/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-visible-links-chromium-linux.png index 106cae69c..16f7fcc64 100644 Binary files a/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-visible-links-chromium-linux.png and b/browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-visible-links-chromium-linux.png differ diff --git a/browser_tests/tests/loadWorkflowInMedia.spec.ts-snapshots/no-workflow-webp-chromium-linux.png b/browser_tests/tests/loadWorkflowInMedia.spec.ts-snapshots/no-workflow-webp-chromium-linux.png index 8f347b607..1d8908e17 100644 Binary files a/browser_tests/tests/loadWorkflowInMedia.spec.ts-snapshots/no-workflow-webp-chromium-linux.png and b/browser_tests/tests/loadWorkflowInMedia.spec.ts-snapshots/no-workflow-webp-chromium-linux.png differ diff --git a/browser_tests/tests/nodeBadge.spec.ts-snapshots/node-badge-unknown-color-palette-chromium-linux.png b/browser_tests/tests/nodeBadge.spec.ts-snapshots/node-badge-unknown-color-palette-chromium-linux.png index 6975bdeae..d1617f599 100644 Binary files a/browser_tests/tests/nodeBadge.spec.ts-snapshots/node-badge-unknown-color-palette-chromium-linux.png and b/browser_tests/tests/nodeBadge.spec.ts-snapshots/node-badge-unknown-color-palette-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png index 7f45abc48..f6130d900 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png index 657b187f4..1b4771e31 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png b/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png index 3acea9ade..ed2e63d04 100644 Binary files a/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png and b/browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png index 1d1b68028..db3883b5b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png index bd3588032..4ed230307 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png index 236741861..077e34405 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png index 8afad9621..d0235f54b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png index 9ad1da6c9..e4155c01f 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png index 80def98a7..c5d0151f1 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png index 5c88cc2d7..7b1d91b3d 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png index 8baf3d5b6..2dd92ee99 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png index d15a269f9..50f26e324 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png index fe87de172..95a0f2a7d 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png index b8f1c767c..088ec907c 100644 Binary files a/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png and b/browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-touch-mobile-chrome-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png index cad5563e7..de2f73910 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png index 36f8a21d4..e04cb92a7 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png index 1efcd1f82..2bee56e28 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts index f8c94aba5..cfe0ba1b3 100644 --- a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts +++ b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts @@ -23,10 +23,10 @@ test.describe('Vue Nodes - LOD', () => { const vueNodesContainer = comfyPage.vueNodes.nodes const textboxesInNodes = vueNodesContainer.getByRole('textbox') - const buttonsInNodes = vueNodesContainer.getByRole('button') + const comboboxesInNodes = vueNodesContainer.getByRole('combobox') await expect(textboxesInNodes.first()).toBeVisible() - await expect(buttonsInNodes.first()).toBeVisible() + await expect(comboboxesInNodes.first()).toBeVisible() await comfyPage.zoom(120, 10) await comfyPage.nextFrame() @@ -34,7 +34,7 @@ test.describe('Vue Nodes - LOD', () => { await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-lod-active.png') await expect(textboxesInNodes.first()).toBeHidden() - await expect(buttonsInNodes.first()).toBeHidden() + await expect(comboboxesInNodes.first()).toBeHidden() await comfyPage.zoom(-120, 10) await comfyPage.nextFrame() @@ -43,6 +43,6 @@ test.describe('Vue Nodes - LOD', () => { 'vue-nodes-lod-inactive.png' ) await expect(textboxesInNodes.first()).toBeVisible() - await expect(buttonsInNodes.first()).toBeVisible() + await expect(comboboxesInNodes.first()).toBeVisible() }) }) diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png index 8e06dd031..cdb700eb6 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png index 5fa28c0d6..19a832b92 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png index aa00c18be..9ad3d1c02 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png index 7306f6937..ec764bd4b 100644 Binary files a/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png and b/browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png differ diff --git a/build/plugins/comfyAPIPlugin.ts b/build/plugins/comfyAPIPlugin.ts index 5b7f4bec4..3e9a9e5b9 100644 --- a/build/plugins/comfyAPIPlugin.ts +++ b/build/plugins/comfyAPIPlugin.ts @@ -6,6 +6,34 @@ interface ShimResult { exports: string[] } +const SKIP_WARNING_FILES = new Set(['scripts/app', 'scripts/api']) + +/** Files that will be removed in v1.34 */ +const DEPRECATED_FILES = [ + 'scripts/ui', + 'extensions/core/maskEditorOld', + 'extensions/core/groupNode' +] as const + +function getWarningMessage( + fileKey: string, + shimFileName: string +): string | null { + if (SKIP_WARNING_FILES.has(fileKey)) { + return null + } + + const isDeprecated = DEPRECATED_FILES.some((deprecatedPath) => + fileKey.startsWith(deprecatedPath) + ) + + if (isDeprecated) { + return `[ComfyUI Deprecated] Importing from "${shimFileName}" is deprecated and will be removed in v1.34.` + } + + return `[ComfyUI Notice] "${shimFileName}" is an internal module, not part of the public API. Future updates may break this import.` +} + function isLegacyFile(id: string): boolean { return ( id.endsWith('.ts') && @@ -63,12 +91,22 @@ export function comfyAPIPlugin(isDev: boolean): Plugin { const relativePath = path.relative(path.join(projectRoot, 'src'), id) const shimFileName = relativePath.replace(/\.ts$/, '.js') - const shimComment = `// Shim for ${relativePath}\n` + let shimContent = `// Shim for ${relativePath}\n` + + const fileKey = relativePath.replace(/\.ts$/, '').replace(/\\/g, '/') + const warningMessage = getWarningMessage(fileKey, shimFileName) + + if (warningMessage) { + // It will only display once because it is at the root of the file. + shimContent += `console.warn('${warningMessage}');\n` + } + + shimContent += result.exports.join('') this.emitFile({ type: 'asset', fileName: shimFileName, - source: shimComment + result.exports.join('') + source: shimContent }) } diff --git a/global.d.ts b/global.d.ts index 5493cbb18..059e47732 100644 --- a/global.d.ts +++ b/global.d.ts @@ -4,12 +4,19 @@ declare const __SENTRY_DSN__: string declare const __ALGOLIA_APP_ID__: string declare const __ALGOLIA_API_KEY__: string declare const __USE_PROD_CONFIG__: boolean -declare const __MIXPANEL_TOKEN__: string -type BuildFeatureFlags = { - REQUIRE_SUBSCRIPTION: boolean +interface Window { + __CONFIG__: { + mixpanel_token?: string + subscription_required?: boolean + server_health_alert?: { + message: string + tooltip?: string + severity?: 'info' | 'warning' | 'error' + badge?: string + } + } } -declare const __BUILD_FLAGS__: BuildFeatureFlags interface Navigator { /** diff --git a/knip.config.ts b/knip.config.ts index 72cd5b775..a8cd73a77 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -12,10 +12,6 @@ const config: KnipConfig = { ], project: ['**/*.{js,ts,vue}', '*.{js,ts,mts}'] }, - 'apps/desktop-ui': { - entry: ['src/main.ts', 'src/i18n.ts'], - project: ['src/**/*.{js,ts,vue}', '*.{js,ts,mts}'] - }, 'packages/tailwind-utils': { project: ['src/**/*.{js,ts}'] }, @@ -34,13 +30,7 @@ const config: KnipConfig = { '@primeuix/forms', '@primeuix/styled', '@primeuix/utils', - '@primevue/icons', - // Dev - '@trivago/prettier-plugin-sort-imports', - // 3D and collaboration libraries - 'three', - '@types/three', - 'yjs' + '@primevue/icons' ], ignore: [ // Auto generated manager types @@ -49,16 +39,6 @@ const config: KnipConfig = { // Used by a custom node (that should move off of this) 'src/scripts/ui/components/splitButton.ts', '.pages/vite.config.ts', - // Utility files with exports that may be used by extensions or future features - 'src/constants/uvMirrors.ts', - 'src/lib/litegraph/src/measure.ts', - 'src/lib/litegraph/src/widgets/DisconnectedWidget.ts', - 'src/renderer/extensions/vueNodes/widgets/utils/audioUtils.ts', - 'src/utils/electronMirrorCheck.ts', - 'src/renderer/extensions/vueNodes/composables/slotLinkDragContext.ts', - 'src/types/spatialIndex.ts', - 'src/lib/litegraph/src/litegraph.ts', - 'src/utils/vintageClipboard.ts', // Service worker - registered at runtime via navigator.serviceWorker.register() 'public/auth-sw.js' ], diff --git a/lint-staged.config.js b/lint-staged.config.js index 0f3808700..5283d261e 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -8,8 +8,10 @@ export default { } function formatAndEslint(fileNames) { + // Convert absolute paths to relative paths for better ESLint resolution + const relativePaths = fileNames.map((f) => f.replace(process.cwd() + '/', '')) return [ - `pnpm exec eslint --cache --fix ${fileNames.join(' ')}`, - `pnpm exec prettier --cache --write ${fileNames.join(' ')}` + `pnpm exec eslint --cache --fix ${relativePaths.join(' ')}`, + `pnpm exec prettier --cache --write ${relativePaths.join(' ')}` ] } diff --git a/package.json b/package.json index 39badf7e7..2309388d9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@comfyorg/comfyui-frontend", "private": true, - "version": "1.30.1", + "version": "1.30.3", "type": "module", "repository": "https://github.com/Comfy-Org/ComfyUI_frontend", "homepage": "https://comfy.org", @@ -57,6 +57,7 @@ "@nx/vite": "catalog:", "@pinia/testing": "catalog:", "@playwright/test": "catalog:", + "@prettier/plugin-oxc": "catalog:", "@storybook/addon-docs": "catalog:", "@storybook/vue3": "catalog:", "@storybook/vue3-vite": "catalog:", diff --git a/packages/design-system/src/css/style.css b/packages/design-system/src/css/style.css index eee0c0658..4b559c4b3 100644 --- a/packages/design-system/src/css/style.css +++ b/packages/design-system/src/css/style.css @@ -200,7 +200,7 @@ --node-stroke-executing: var(--color-blue-100); --text-secondary: var(--color-stone-100); --text-primary: var(--color-charcoal-700); - --input-surface: rgba(0, 0, 0, 0.15); + --input-surface: rgb(0 0 0 / 0.15); } .dark-theme { @@ -247,7 +247,7 @@ --node-stroke-executing: var(--color-blue-100); --text-secondary: var(--color-slate-100); --text-primary: var(--color-pure-white); - --input-surface: rgba(130, 130, 130, 0.1); + --input-surface: rgb(130 130 130 / 0.1); } @theme inline { @@ -258,9 +258,15 @@ --color-button-surface: var(--button-surface); --color-button-surface-contrast: var(--button-surface-contrast); --color-dialog-surface: var(--dialog-surface); - --color-interface-menu-component-surface-hovered: var(--interface-menu-component-surface-hovered); - --color-interface-menu-component-surface-selected: var(--interface-menu-component-surface-selected); - --color-interface-menu-keybind-surface-default: var(--interface-menu-keybind-surface-default); + --color-interface-menu-component-surface-hovered: var( + --interface-menu-component-surface-hovered + ); + --color-interface-menu-component-surface-selected: var( + --interface-menu-component-surface-selected + ); + --color-interface-menu-keybind-surface-default: var( + --interface-menu-keybind-surface-default + ); --color-interface-panel-surface: var(--interface-panel-surface); --color-interface-stroke: var(--interface-stroke); --color-nav-background: var(--nav-background); @@ -324,7 +330,42 @@ } } -/* Everything below here to be cleaned up over time. */ + +/* ===================== Scrollbar Utilities (Tailwind) ===================== + Usage: Add `scrollbar-custom` class to scrollable containers. + The scrollbar styling adapts to light/dark theme automatically. +============================================================================ */ + +@utility scrollbar-custom { + overflow-y: auto; + /* Firefox */ + scrollbar-width: thin; + scrollbar-color: var(--dialog-surface) transparent; + + /* WebKit */ + &::-webkit-scrollbar { + width: 10px; + height: 10px; + background-color: transparent; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: var(--dialog-surface); + border-radius: 9999px; + border: 2px solid transparent; + } + &::-webkit-scrollbar-thumb:hover { + background: var(--dialog-surface); + } + &::-webkit-scrollbar-corner { + background: transparent; + } +} +/* =================== End Custom Scrollbar (cross-browser) =================== */ + +/* Everthing below here to be cleaned up over time. */ body { width: 100vw; @@ -1139,7 +1180,7 @@ audio.comfy-audio.empty-audio-widget { } .isLOD .lg-node-header { - border-radius: 0px; + border-radius: 0; pointer-events: none; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9ec818d7..eb38350f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,9 @@ catalogs: '@playwright/test': specifier: ^1.52.0 version: 1.52.0 + '@prettier/plugin-oxc': + specifier: ^0.0.4 + version: 0.0.4 '@primeuix/forms': specifier: 0.0.2 version: 0.0.2 @@ -498,6 +501,9 @@ importers: '@playwright/test': specifier: 'catalog:' version: 1.52.0 + '@prettier/plugin-oxc': + specifier: 'catalog:' + version: 0.0.4 '@storybook/addon-docs': specifier: 'catalog:' version: 9.1.1(@types/react@19.1.9)(storybook@9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))) @@ -2348,6 +2354,98 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@oxc-parser/binding-android-arm64@0.74.0': + resolution: {integrity: sha512-lgq8TJq22eyfojfa2jBFy2m66ckAo7iNRYDdyn9reXYA3I6Wx7tgGWVx1JAp1lO+aUiqdqP/uPlDaETL9tqRcg==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.74.0': + resolution: {integrity: sha512-xbY/io/hkARggbpYEMFX6CwFzb7f4iS6WuBoBeZtdqRWfIEi7sm/uYWXfyVeB8uqOATvJ07WRFC2upI8PSI83g==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.74.0': + resolution: {integrity: sha512-FIj2gAGtFaW0Zk+TnGyenMUoRu1ju+kJ/h71D77xc1owOItbFZFGa+4WSVck1H8rTtceeJlK+kux+vCjGFCl9Q==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.74.0': + resolution: {integrity: sha512-W1I+g5TJg0TRRMHgEWNWsTIfe782V3QuaPgZxnfPNmDMywYdtlzllzclBgaDq6qzvZCCQc/UhvNb37KWTCTj8A==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.74.0': + resolution: {integrity: sha512-gxqkyRGApeVI8dgvJ19SYe59XASW3uVxF1YUgkE7peW/XIg5QRAOVTFKyTjI9acYuK1MF6OJHqx30cmxmZLtiQ==} + engines: {node: '>=20.0.0'} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.74.0': + resolution: {integrity: sha512-jpnAUP4Fa93VdPPDzxxBguJmldj/Gpz7wTXKFzpAueqBMfZsy9KNC+0qT2uZ9HGUDMzNuKw0Se3bPCpL/gfD2Q==} + engines: {node: '>=20.0.0'} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.74.0': + resolution: {integrity: sha512-fcWyM7BNfCkHqIf3kll8fJctbR/PseL4RnS2isD9Y3FFBhp4efGAzhDaxIUK5GK7kIcFh1P+puIRig8WJ6IMVQ==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-arm64-musl@0.74.0': + resolution: {integrity: sha512-AMY30z/C77HgiRRJX7YtVUaelKq1ex0aaj28XoJu4SCezdS8i0IftUNTtGS1UzGjGZB8zQz5SFwVy4dRu4GLwg==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-gnu@0.74.0': + resolution: {integrity: sha512-/RZAP24TgZo4vV/01TBlzRqs0R7E6xvatww4LnmZEBBulQBU/SkypDywfriFqWuFoa61WFXPV7sLcTjJGjim/w==} + engines: {node: '>=20.0.0'} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-s390x-gnu@0.74.0': + resolution: {integrity: sha512-620J1beNAlGSPBD+Msb3ptvrwxu04B8iULCH03zlf0JSLy/5sqlD6qBs0XUVkUJv1vbakUw1gfVnUQqv0UTuEg==} + engines: {node: '>=20.0.0'} + cpu: [s390x] + os: [linux] + + '@oxc-parser/binding-linux-x64-gnu@0.74.0': + resolution: {integrity: sha512-WBFgQmGtFnPNzHyLKbC1wkYGaRIBxXGofO0+hz1xrrkPgbxbJS1Ukva1EB8sPaVBBQ52Bdc2GjLSp721NWRvww==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-linux-x64-musl@0.74.0': + resolution: {integrity: sha512-y4mapxi0RGqlp3t6Sm+knJlAEqdKDYrEue2LlXOka/F2i4sRN0XhEMPiSOB3ppHmvK4I2zY2XBYTsX1Fel0fAg==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-wasm32-wasi@0.74.0': + resolution: {integrity: sha512-yDS9bRDh5ymobiS2xBmjlrGdUuU61IZoJBaJC5fELdYT5LJNBXlbr3Yc6m2PWfRJwkH6Aq5fRvxAZ4wCbkGa8w==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.74.0': + resolution: {integrity: sha512-XFWY52Rfb4N5wEbMCTSBMxRkDLGbAI9CBSL24BIDywwDJMl31gHEVlmHdCDRoXAmanCI6gwbXYTrWe0HvXJ7Aw==} + engines: {node: '>=20.0.0'} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.74.0': + resolution: {integrity: sha512-1D3x6iU2apLyfTQHygbdaNbX3nZaHu4yaXpD7ilYpoLo7f0MX0tUuoDrqJyJrVGqvyXgc0uz4yXz9tH9ZZhvvg==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.74.0': + resolution: {integrity: sha512-KOw/RZrVlHGhCXh1RufBFF7Nuo7HdY5w1lRJukM/igIl6x9qtz8QycDvZdzb4qnHO7znrPyo2sJrFJK2eKHgfQ==} + '@oxc-resolver/binding-android-arm-eabi@11.6.1': resolution: {integrity: sha512-Ma/kg29QJX1Jzelv0Q/j2iFuUad1WnjgPjpThvjqPjpOyLjCUaiFCCnshhmWjyS51Ki1Iol3fjf1qAzObf8GIA==} cpu: [arm] @@ -2481,6 +2579,10 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@prettier/plugin-oxc@0.0.4': + resolution: {integrity: sha512-UGXe+g/rSRbglL0FOJiar+a+nUrst7KaFmsg05wYbKiInGWP6eAj/f8A2Uobgo5KxEtb2X10zeflNH6RK2xeIQ==} + engines: {node: '>=14'} + '@primeuix/forms@0.0.2': resolution: {integrity: sha512-DpecPQd/Qf/kav4LKCaIeGuT3AkwhJzuHCkLANTVlN/zBvo8KIj3OZHsCkm0zlIMVVnaJdtx1ULNlRQdudef+A==} engines: {node: '>=12.11.0'} @@ -6207,6 +6309,10 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxc-parser@0.74.0: + resolution: {integrity: sha512-2tDN/ttU8WE6oFh8EzKNam7KE7ZXSG5uXmvX85iNzxdJfMssDWcj3gpYzZi1E04XuE7m3v1dVWl/8BE886vPGw==} + engines: {node: '>=20.0.0'} + oxc-resolver@11.6.1: resolution: {integrity: sha512-WQgmxevT4cM5MZ9ioQnEwJiHpPzbvntV5nInGAKo9NQZzegcOonHvcVcnkYqld7bTG35UFHEKeF7VwwsmA3cZg==} @@ -9796,6 +9902,55 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@oxc-parser/binding-android-arm64@0.74.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.74.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.74.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.74.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.74.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.74.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.74.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.74.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.74.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.74.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.74.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.74.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.74.0': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.74.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.74.0': + optional: true + + '@oxc-project/types@0.74.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.6.1': optional: true @@ -9891,6 +10046,10 @@ snapshots: '@polka/url@1.0.0-next.29': {} + '@prettier/plugin-oxc@0.0.4': + dependencies: + oxc-parser: 0.74.0 + '@primeuix/forms@0.0.2': dependencies: '@primeuix/utils': 0.3.2 @@ -14173,6 +14332,26 @@ snapshots: safe-push-apply: 1.0.0 optional: true + oxc-parser@0.74.0: + dependencies: + '@oxc-project/types': 0.74.0 + optionalDependencies: + '@oxc-parser/binding-android-arm64': 0.74.0 + '@oxc-parser/binding-darwin-arm64': 0.74.0 + '@oxc-parser/binding-darwin-x64': 0.74.0 + '@oxc-parser/binding-freebsd-x64': 0.74.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.74.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.74.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.74.0 + '@oxc-parser/binding-linux-arm64-musl': 0.74.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.74.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.74.0 + '@oxc-parser/binding-linux-x64-gnu': 0.74.0 + '@oxc-parser/binding-linux-x64-musl': 0.74.0 + '@oxc-parser/binding-wasm32-wasi': 0.74.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.74.0 + '@oxc-parser/binding-win32-x64-msvc': 0.74.0 + oxc-resolver@11.6.1: dependencies: napi-postinstall: 0.3.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bbcd55d81..a7e91efdc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -16,6 +16,7 @@ catalog: '@nx/vite': 21.4.1 '@pinia/testing': ^0.1.5 '@playwright/test': ^1.52.0 + '@prettier/plugin-oxc': ^0.0.4 '@primeuix/forms': 0.0.2 '@primeuix/styled': 0.3.2 '@primeuix/utils': ^0.3.2 diff --git a/public/auth-sw.js b/public/auth-sw.js index 60a11eff1..2f21da21a 100644 --- a/public/auth-sw.js +++ b/public/auth-sw.js @@ -54,18 +54,50 @@ self.addEventListener('fetch', (event) => { headers.set(key, value) } - return fetch( + // Fetch with manual redirect to handle cross-origin redirects (e.g., GCS signed URLs) + const response = await fetch( new Request(event.request.url, { method: event.request.method, headers: headers, - mode: 'same-origin', credentials: event.request.credentials, cache: 'no-store', - redirect: event.request.redirect, + redirect: 'manual', referrer: event.request.referrer, integrity: event.request.integrity }) ) + + // Handle redirects to external storage (e.g., GCS signed URLs) + if (response.type === 'opaqueredirect') { + // Opaqueredirect: redirect occurred but response is opaque (headers not accessible) + // Re-fetch the original /api/view URL with redirect: 'follow' and mode: 'no-cors' + // - mode: 'no-cors' allows cross-origin fetches without CORS headers (GCS doesn't have CORS) + // - Returns opaque response, which works fine for images/videos/audio + // - Browser will send auth headers to /api/view (same-origin) + // - Browser will receive 302 redirect to GCS + // - Browser will follow redirect using GCS signed URL authentication + return fetch(event.request.url, { + method: 'GET', + headers: headers, + redirect: 'follow', + mode: 'no-cors' + }) + } + + // Non-opaque redirect (status visible) - shouldn't normally happen with redirect: 'manual' + // but handle as fallback + if (response.status === 302 || response.status === 301) { + const location = response.headers.get('location') + if (location) { + // Follow redirect manually - do NOT include auth headers for external URLs + return fetch(location, { + method: 'GET', + redirect: 'follow' + }) + } + } + + return response } catch (error) { console.error('[Auth SW] Request failed:', error) return fetch(event.request) diff --git a/scripts/build-pages.sh b/scripts/build-pages.sh index fe63c086f..28561d18e 100755 --- a/scripts/build-pages.sh +++ b/scripts/build-pages.sh @@ -53,7 +53,7 @@ fi echo "[build-pages] Generating Knip report" mkdir -p ".pages/knip" rm -f ".pages/knip/report.md" -if pnpm knip --reporter markdown --no-progress --no-exit-code > ".pages/knip/report.md" 2>/dev/null && [ -s ".pages/knip/report.md" ]; then +if pnpm knip --reporter markdown --no-progress --no-exit-code 2>/dev/null | sed 's/^\[log\] //' > ".pages/knip/report.md" && [ -s ".pages/knip/report.md" ]; then echo "✅ Knip report generated at .pages/knip/report.md" else echo "⚠️ Knip report failed, creating placeholder..." diff --git a/scripts/create-playwright-index.js b/scripts/create-playwright-index.js deleted file mode 100755 index de8ac433a..000000000 --- a/scripts/create-playwright-index.js +++ /dev/null @@ -1,290 +0,0 @@ -#!/usr/bin/env node -/** - * Creates an index page for Playwright test reports with test statistics - * Reads JSON reports from each browser and creates a landing page with cards - */ -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const reportsDir = path.join(__dirname, '..', '.pages', 'playwright-reports') - -function getTestStats(reportPath) { - try { - const reportJsonPath = path.join(reportPath, 'report.json') - if (!fs.existsSync(reportJsonPath)) { - console.warn(`No report.json found at ${reportJsonPath}`) - return null - } - - const reportData = JSON.parse(fs.readFileSync(reportJsonPath, 'utf-8')) - - let passed = 0 - let failed = 0 - let skipped = 0 - let flaky = 0 - - // Parse Playwright JSON report format - if (reportData.suites) { - const countResults = (suites) => { - for (const suite of suites) { - if (suite.specs) { - for (const spec of suite.specs) { - if (!spec.tests || spec.tests.length === 0) continue - - const test = spec.tests[0] - const results = test.results || [] - - // Check if test is flaky (has both pass and fail results) - const hasPass = results.some((r) => r.status === 'passed') - const hasFail = results.some((r) => r.status === 'failed') - - if (hasPass && hasFail) { - flaky++ - } else if (results.some((r) => r.status === 'passed')) { - passed++ - } else if (results.some((r) => r.status === 'failed')) { - failed++ - } else if (results.some((r) => r.status === 'skipped')) { - skipped++ - } - } - } - if (suite.suites) { - countResults(suite.suites) - } - } - } - - countResults(reportData.suites) - } - - return { passed, failed, skipped, flaky } - } catch (error) { - console.error(`Error reading report at ${reportPath}:`, error.message) - return null - } -} - -function generateIndexHtml(browsers) { - const cards = browsers - .map((browser) => { - const { name, stats } = browser - if (!stats) return '' - - const total = stats.passed + stats.failed + stats.skipped + stats.flaky - const passRate = total > 0 ? ((stats.passed / total) * 100).toFixed(1) : 0 - - return ` - -
-

${name}

- ${passRate}% -
-
-
- Passed - ${stats.passed} -
-
- Failed - ${stats.failed} -
-
- Skipped - ${stats.skipped} -
-
- Flaky - ${stats.flaky} -
-
-
- ` - }) - .join('') - - return ` - - - - - Playwright E2E Test Reports - - - -
-

🎭 Playwright E2E Test Reports

-
- ${cards} -
-
- -` -} - -function main() { - if (!fs.existsSync(reportsDir)) { - console.log( - 'No playwright reports directory found, skipping index creation' - ) - return - } - - const browsers = [] - const browserDirs = fs.readdirSync(reportsDir, { withFileTypes: true }) - - for (const dirent of browserDirs) { - if (dirent.isDirectory()) { - const browserName = dirent.name - const browserPath = path.join(reportsDir, browserName) - const stats = getTestStats(browserPath) - - if (stats) { - browsers.push({ name: browserName, stats }) - console.log(`✓ Found report for ${browserName}:`, stats) - } - } - } - - if (browsers.length === 0) { - console.warn('No valid browser reports found') - return - } - - const html = generateIndexHtml(browsers) - const indexPath = path.join(reportsDir, 'index.html') - - fs.writeFileSync(indexPath, html, 'utf-8') - console.log(`✓ Created index page at ${indexPath}`) -} - -main() diff --git a/src/base/common/downloadUtil.ts b/src/base/common/downloadUtil.ts index 307a3e35b..4462dd1a7 100644 --- a/src/base/common/downloadUtil.ts +++ b/src/base/common/downloadUtil.ts @@ -1,29 +1,64 @@ /** * Utility functions for downloading files */ +import { isCloud } from '@/platform/distribution/types' // Constants const DEFAULT_DOWNLOAD_FILENAME = 'download.png' +/** + * Trigger a download by creating a temporary anchor element + * @param href - The URL or blob URL to download + * @param filename - The filename to suggest to the browser + */ +function triggerLinkDownload(href: string, filename: string): void { + const link = document.createElement('a') + link.href = href + link.download = filename + link.style.display = 'none' + + document.body.appendChild(link) + link.click() + document.body.removeChild(link) +} + /** * Download a file from a URL by creating a temporary anchor element * @param url - The URL of the file to download (must be a valid URL string) * @param filename - Optional filename override (will use URL filename or default if not provided) * @throws {Error} If the URL is invalid or empty */ -export const downloadFile = (url: string, filename?: string): void => { +export function downloadFile(url: string, filename?: string): void { if (!url || typeof url !== 'string' || url.trim().length === 0) { throw new Error('Invalid URL provided for download') } - const link = document.createElement('a') - link.href = url - link.download = + + const inferredFilename = filename || extractFilenameFromUrl(url) || DEFAULT_DOWNLOAD_FILENAME - // Trigger download - document.body.appendChild(link) - link.click() - document.body.removeChild(link) + if (isCloud) { + // Assets from cross-origin (e.g., GCS) cannot be downloaded this way + void downloadViaBlobFetch(url, inferredFilename).catch((error) => { + console.error('Failed to download file', error) + }) + return + } + + triggerLinkDownload(url, inferredFilename) +} + +/** + * Download a Blob by creating a temporary object URL and anchor element + * @param filename - The filename to suggest to the browser + * @param blob - The Blob to download + */ +export function downloadBlob(filename: string, blob: Blob): void { + const url = URL.createObjectURL(blob) + + triggerLinkDownload(url, filename) + + // Revoke on the next microtask to give the browser time to start the download + queueMicrotask(() => URL.revokeObjectURL(url)) } /** @@ -39,3 +74,15 @@ const extractFilenameFromUrl = (url: string): string | null => { return null } } + +const downloadViaBlobFetch = async ( + href: string, + filename: string +): Promise => { + const response = await fetch(href) + if (!response.ok) { + throw new Error(`Failed to fetch ${href}: ${response.status}`) + } + const blob = await response.blob() + downloadBlob(filename, blob) +} diff --git a/src/components/LiteGraphCanvasSplitterOverlay.vue b/src/components/LiteGraphCanvasSplitterOverlay.vue index 1c467ef05..1bb9e89c7 100644 --- a/src/components/LiteGraphCanvasSplitterOverlay.vue +++ b/src/components/LiteGraphCanvasSplitterOverlay.vue @@ -42,7 +42,7 @@ +
- - + +
@@ -29,9 +29,11 @@ import LoginButton from '@/components/topbar/LoginButton.vue' import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { app } from '@/scripts/app' import { useWorkspaceStore } from '@/stores/workspaceStore' +import { isElectron } from '@/utils/envUtil' const workspaceStore = useWorkspaceStore() const { isLoggedIn } = useCurrentUser() +const isDesktop = isElectron() // Maintain support for legacy topbar elements attached by custom scripts const legacyCommandsContainerRef = ref() diff --git a/src/components/actionbar/ComfyActionbar.vue b/src/components/actionbar/ComfyActionbar.vue index c2b7fe8ed..f01beee25 100644 --- a/src/components/actionbar/ComfyActionbar.vue +++ b/src/components/actionbar/ComfyActionbar.vue @@ -2,10 +2,7 @@
@@ -13,18 +10,15 @@
-
+
- - - {{ - $field.error.message - }} - -