mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-19 22:34:15 +00:00
feat: add GitHub Pages deployment for development tools and reports
- Add GitHub Pages workflow (release-pages.yml) to deploy Storybook, Knip reports, and Playwright test reports - Create .pages directory structure for GitHub Pages content - Add build scripts for generating pages artifacts (build-pages.sh, create-playwright-index.js) - Update CI workflows to support pages deployment and artifact handling - Configure Knip and TypeScript for new .pages structure - Add HTML viewers for Knip and Playwright reports Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2
.github/workflows/ci-tests-e2e.yaml
vendored
2
.github/workflows/ci-tests-e2e.yaml
vendored
@@ -3,7 +3,7 @@ name: 'CI: Tests E2E'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, core/*, desktop/*]
|
||||
branches: [main, master, core/*, desktop/*, sno-deploy-ghpage]
|
||||
pull_request:
|
||||
branches-ignore: [wip/*, draft/*, temp/*]
|
||||
workflow_dispatch:
|
||||
|
||||
82
.github/workflows/ci-tests-storybook.yaml
vendored
82
.github/workflows/ci-tests-storybook.yaml
vendored
@@ -1,9 +1,13 @@
|
||||
# Description: Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages
|
||||
name: 'CI: Tests Storybook'
|
||||
name: Storybook and Chromatic CI
|
||||
|
||||
# - [Automate Chromatic with GitHub Actions • Chromatic docs]( https://www.chromatic.com/docs/github-actions/ )
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
pull_request:
|
||||
branches: [main, sno-deploy-ghpage]
|
||||
push:
|
||||
branches: [sno-deploy-ghpage]
|
||||
|
||||
jobs:
|
||||
# Post starting comment for non-forked PRs
|
||||
@@ -14,7 +18,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Post starting comment
|
||||
env:
|
||||
@@ -30,16 +34,40 @@ jobs:
|
||||
# Build Storybook for all PRs (free Cloudflare deployment)
|
||||
storybook-build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request' || github.event_name == 'push'
|
||||
outputs:
|
||||
conclusion: ${{ steps.job-status.outputs.conclusion }}
|
||||
workflow-url: ${{ steps.workflow-url.outputs.url }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
- 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
|
||||
@@ -58,7 +86,7 @@ jobs:
|
||||
|
||||
- name: Upload Storybook build
|
||||
if: success() && github.event.pull_request.head.repo.fork == false
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: storybook-static/
|
||||
@@ -75,16 +103,40 @@ jobs:
|
||||
chromatic-storybook-url: ${{ steps.chromatic.outputs.storybookUrl }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Required for Chromatic baseline
|
||||
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
- 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@07791f8243f4cb2698bf4d00426baf4b2d1cb7e0 # v13.3.5
|
||||
uses: chromaui/action@latest
|
||||
with:
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
buildScriptName: build-storybook
|
||||
@@ -114,11 +166,11 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download Storybook build
|
||||
if: needs.storybook-build.outputs.conclusion == 'success'
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: storybook-static
|
||||
@@ -148,7 +200,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Update comment with Chromatic URLs
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const buildUrl = '${{ needs.chromatic-deployment.outputs.chromatic-build-url }}';
|
||||
|
||||
186
.github/workflows/release-pages.yml
vendored
Normal file
186
.github/workflows/release-pages.yml
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
name: Deploy to GitHub Pages
|
||||
description: Build and deploy to GitHub Pages and Vercel on successful completion of tests and builds
|
||||
|
||||
on:
|
||||
# Triggers when any of these workflows complete on main branch
|
||||
# Runs ONCE per workflow completion (e.g., if "Vitest Tests" completes, this workflow runs once)
|
||||
workflow_run:
|
||||
workflows: ['Storybook and Chromatic CI', 'Vitest Tests', 'Tests CI']
|
||||
types: [completed]
|
||||
# Allow direct pushes to the debug branch to kick off the full pipeline
|
||||
push:
|
||||
branches: [sno-deploy-ghpage]
|
||||
# Keep manual trigger for debugging
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
incremental-build:
|
||||
runs-on: ubuntu-latest
|
||||
# Only run if the triggering workflow succeeded (or manual dispatch/push)
|
||||
if: github.event_name == 'workflow_dispatch' || github.event_name == 'push' || github.event.workflow_run.conclusion == 'success'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Cache build artifacts
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
.pages
|
||||
storybook-static
|
||||
coverage
|
||||
key: build-cache-${{ github.ref_name }}-${{ github.run_id }}-${{ hashFiles('pnpm-lock.yaml', 'package.json') }}
|
||||
restore-keys: |
|
||||
build-cache-${{ github.ref_name }}-
|
||||
build-cache-main-
|
||||
|
||||
- name: Download Storybook artifact (source run)
|
||||
id: fetch_storybook_trigger
|
||||
continue-on-error: true
|
||||
if: github.event_name == 'workflow_run' && github.event.workflow_run.name == 'Storybook and Chromatic CI'
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
workflow: storybook-and-chromatic-ci.yaml
|
||||
name: storybook-static
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: storybook-static
|
||||
|
||||
- name: Download Storybook artifact (latest successful run on main)
|
||||
continue-on-error: true
|
||||
if: steps.fetch_storybook_trigger.outcome != 'success'
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
workflow: storybook-and-chromatic-ci.yaml
|
||||
name: storybook-static
|
||||
branch: main
|
||||
workflow_conclusion: success
|
||||
path: storybook-static
|
||||
|
||||
- name: Download Vitest reports (source run)
|
||||
id: fetch_vitest_trigger
|
||||
continue-on-error: true
|
||||
if: github.event_name == 'workflow_run' && github.event.workflow_run.name == 'Vitest Tests'
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
workflow: vitest-tests.yaml
|
||||
name: vitest-reports
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: ./.pages/vitest-reports
|
||||
|
||||
- name: Download Vitest reports (latest successful run on main)
|
||||
continue-on-error: true
|
||||
if: steps.fetch_vitest_trigger.outcome != 'success'
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
workflow: vitest-tests.yaml
|
||||
name: vitest-reports
|
||||
branch: main
|
||||
workflow_conclusion: success
|
||||
path: ./.pages/vitest-reports
|
||||
|
||||
- name: Download Playwright E2E reports (source run)
|
||||
id: fetch_playwright_trigger
|
||||
continue-on-error: true
|
||||
if: github.event_name == 'workflow_run' && github.event.workflow_run.name == 'Tests CI'
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
workflow: ci-tests-e2e.yaml
|
||||
name_is_regexp: true
|
||||
name: playwright-report-.*
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: ./playwright-reports-temp
|
||||
|
||||
- name: Download Playwright E2E reports (latest successful run on main)
|
||||
continue-on-error: true
|
||||
if: steps.fetch_playwright_trigger.outcome != 'success'
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
workflow: ci-tests-e2e.yaml
|
||||
name_is_regexp: true
|
||||
name: playwright-report-.*
|
||||
branch: main
|
||||
workflow_conclusion: success
|
||||
path: ./playwright-reports-temp
|
||||
|
||||
- name: Organize Playwright reports by browser
|
||||
if: always()
|
||||
run: |
|
||||
mkdir -p ./.pages/playwright-reports
|
||||
|
||||
# Move each browser report to its own directory
|
||||
if [ -d "./playwright-reports-temp" ]; then
|
||||
for dir in ./playwright-reports-temp/playwright-report-*; do
|
||||
if [ -d "$dir" ]; then
|
||||
browser_name=$(basename "$dir" | sed 's/playwright-report-//')
|
||||
mkdir -p "./.pages/playwright-reports/${browser_name}"
|
||||
cp -r "$dir"/* "./.pages/playwright-reports/${browser_name}/"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Build static assets (with artifact reuse)
|
||||
run: ./scripts/build-pages.sh
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Upload built pages as cache
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
name: built-pages
|
||||
path: '.pages'
|
||||
|
||||
deploy-vercel-app:
|
||||
runs-on: ubuntu-latest
|
||||
needs: incremental-build
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: download built pages
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: built-pages
|
||||
path: ./artifact
|
||||
|
||||
- name: Extract artifact
|
||||
run: |
|
||||
mkdir -p ./.pages
|
||||
cd ./artifact
|
||||
tar -xf artifact.tar -C ../.pages
|
||||
|
||||
# debug ls of ./.pages
|
||||
- name: List ./.pages contents
|
||||
run: ls -la ./.pages
|
||||
|
||||
- name: Deploy to Vercel
|
||||
uses: amondnet/vercel-action@v20
|
||||
with:
|
||||
vercel-token: ${{ secrets.VERCEL_TOKEN }}
|
||||
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
|
||||
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||
working-directory: .pages
|
||||
vercel-args: ${{ github.ref_name == 'main' && '--prod' || '' }}
|
||||
github-comment: true
|
||||
alias-domains: |
|
||||
${{ github.ref_name }}-comfyui-frontend-reports.vercel.app
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -18,7 +18,6 @@ yarn.lock
|
||||
.stylelintcache
|
||||
|
||||
node_modules
|
||||
.pnpm-store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
@@ -79,7 +78,7 @@ templates_repo/
|
||||
vite.config.mts.timestamp-*.mjs
|
||||
|
||||
# Linux core dumps
|
||||
/core
|
||||
core
|
||||
|
||||
*storybook.log
|
||||
storybook-static
|
||||
@@ -94,7 +93,8 @@ storybook-static
|
||||
vite.config.*.timestamp*
|
||||
vitest.config.*.timestamp*
|
||||
|
||||
# Weekly docs check output
|
||||
/output.txt
|
||||
# Generated reports in .pages (exclude generated, keep HTML templates)
|
||||
/.pages/*/**/*
|
||||
/.pages-dist/
|
||||
|
||||
.amp
|
||||
.vercel
|
||||
|
||||
160
.pages/README.md
Normal file
160
.pages/README.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# GitHub Pages Deployment
|
||||
|
||||
This document describes the GitHub Pages deployment setup for ComfyUI Frontend development tools.
|
||||
|
||||
## Overview
|
||||
|
||||
The project automatically deploys the following development tools to GitHub Pages on every merge to the `main` branch:
|
||||
|
||||
- **Storybook** - Interactive component library and design system documentation
|
||||
- **Nx Dependency Graph** - Visual representation of project dependencies
|
||||
- **Test Coverage Reports** - Code coverage from Vitest unit tests
|
||||
- **Vitest Results** - Interactive test results and reports
|
||||
- **Knip Report** - Unused code and dependency analysis
|
||||
|
||||
## Accessing the Tools
|
||||
|
||||
Once deployed, all tools are accessible from a single landing page at:
|
||||
```
|
||||
https://comfy-org.github.io/ComfyUI_frontend/
|
||||
```
|
||||
|
||||
## Primary Use Case: Storybook for Design Team
|
||||
|
||||
The primary motivation for this deployment is to provide the design team with a consistent, bookmarkable URL to reference the latest component system state. Instead of sharing PR-specific Storybook builds, the design team can always access the latest approved components from the main branch.
|
||||
|
||||
## Deployment Workflow
|
||||
|
||||
The deployment is managed by the `.github/workflows/release-pages.yml` workflow, which:
|
||||
|
||||
1. **Triggers on**:
|
||||
- Push to `main` branch
|
||||
- Manual workflow dispatch
|
||||
|
||||
2. **Build Process**:
|
||||
- Installs dependencies with pnpm
|
||||
- Runs `scripts/build-pages.sh` to generate Storybook, Nx dependency graph, Vitest reports, coverage, and Knip analysis
|
||||
- Creates a landing page with links to all tools
|
||||
|
||||
3. **Deployment**:
|
||||
- Uses GitHub Pages deploy action
|
||||
- Deploys to `gh-pages` branch
|
||||
- Available at the GitHub Pages URL
|
||||
|
||||
## Workflow Details
|
||||
|
||||
### Build Steps
|
||||
|
||||
The build script handles optional tooling gracefully—if an individual tool fails to build, the remainder of the deployment still proceeds and the failure is logged as a warning.
|
||||
|
||||
#### Storybook (Required)
|
||||
```bash
|
||||
pnpm build-storybook --output-dir dist/storybook
|
||||
```
|
||||
|
||||
#### Nx Graph (Optional)
|
||||
```bash
|
||||
pnpm nx graph --file=dist/nx-graph/index.html
|
||||
```
|
||||
|
||||
#### Test Coverage (Optional)
|
||||
```bash
|
||||
pnpm exec vitest --run --coverage --coverage.reporter=html
|
||||
```
|
||||
|
||||
#### Vitest Results (Optional)
|
||||
```bash
|
||||
pnpm exec vitest --run --reporter=html --outputFile dist/vitest-ui/index.html
|
||||
```
|
||||
|
||||
#### Knip Report (Optional)
|
||||
```bash
|
||||
pnpm knip --reporter json
|
||||
```
|
||||
|
||||
### Permissions
|
||||
|
||||
The workflow requires the following permissions:
|
||||
```yaml
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
```
|
||||
|
||||
## Manual Deployment
|
||||
|
||||
You can manually trigger a deployment from the GitHub Actions tab:
|
||||
|
||||
1. Go to Actions → Deploy to GitHub Pages
|
||||
2. Click "Run workflow"
|
||||
3. Select the `main` branch
|
||||
4. Click "Run workflow"
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Storybook Build Fails
|
||||
|
||||
If the Storybook build fails:
|
||||
1. Check that all Storybook stories are syntactically correct
|
||||
2. Verify that all components can be imported
|
||||
3. Run `pnpm build-storybook` locally to reproduce the issue
|
||||
|
||||
### Other Tools Fail
|
||||
|
||||
Since all tools except Storybook are marked with `continue-on-error: true`, they will not prevent deployment. If a tool consistently fails:
|
||||
|
||||
1. Check the GitHub Actions logs for the specific error
|
||||
2. Test the build command locally
|
||||
3. Consider adjusting the build command in the workflow
|
||||
|
||||
### GitHub Pages Not Updating
|
||||
|
||||
If changes aren't reflected on the live site:
|
||||
|
||||
1. Check the workflow run in the Actions tab
|
||||
2. Verify that the deployment step succeeded
|
||||
3. GitHub Pages can take a few minutes to update
|
||||
4. Clear your browser cache or try an incognito window
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Adding New Tools
|
||||
|
||||
To add a new development tool to the deployment:
|
||||
|
||||
1. Add a new build step in `.github/workflows/release-pages.yml`
|
||||
2. Ensure the output goes to a subdirectory of `dist/`
|
||||
3. Add `continue-on-error: true` if the tool is optional
|
||||
4. Update the landing page `dist/index.html` with a link to the new tool
|
||||
|
||||
### Removing Tools
|
||||
|
||||
To remove a tool from deployment:
|
||||
|
||||
1. Remove the build step from the workflow
|
||||
2. Remove the corresponding link from the landing page
|
||||
|
||||
## Cost Considerations
|
||||
|
||||
GitHub Pages is free for public repositories and includes:
|
||||
- 1 GB storage
|
||||
- 100 GB bandwidth per month
|
||||
- 10 builds per hour
|
||||
|
||||
This should be more than sufficient for the development tools deployment.
|
||||
|
||||
## Security
|
||||
|
||||
The deployment only includes static, built artifacts:
|
||||
- No source code is directly exposed
|
||||
- No secrets or credentials are included
|
||||
- All content is publicly accessible (appropriate for public repo)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [GitHub Pages Documentation](https://docs.github.com/en/pages)
|
||||
- [Storybook Documentation](https://storybook.js.org/docs)
|
||||
- [Nx Documentation](https://nx.dev)
|
||||
- [Vitest Documentation](https://vitest.dev)
|
||||
- [Knip Documentation](https://knip.dev)
|
||||
211
.pages/index.html
Normal file
211
.pages/index.html
Normal file
@@ -0,0 +1,211 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ComfyUI Frontend - Development Tools</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #52b2bb 100%);
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.header p {
|
||||
font-size: 1.125rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
p,h1,h2,h3,h4,h5,h6 {
|
||||
transform: matrix(1, 0, -0.25, 1, 0, 0);
|
||||
}
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.card-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #1a202c;
|
||||
}
|
||||
.card-description {
|
||||
color: #718096;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.card-status {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.status-available {
|
||||
background: #c6f6d5;
|
||||
color: #22543d;
|
||||
}
|
||||
.status-loading {
|
||||
background: #e9d8fd;
|
||||
color: #44337a;
|
||||
}
|
||||
.status-unavailable {
|
||||
background: #fed7d7;
|
||||
color: #742a2a;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-top: 3rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.footer a {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🎨 ComfyUI Frontend</h1>
|
||||
<p>Development Tools & Documentation</p>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<a href="./storybook/index.html" class="card" data-status="storybook" data-fetch="./storybook/index.html">
|
||||
<div class="card-icon">📚</div>
|
||||
<h2 class="card-title">Storybook</h2>
|
||||
<p class="card-description">Interactive component library and design system documentation</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
|
||||
<a href="./nx-graph/index.html" class="card" data-status="nx-graph" data-fetch="./nx-graph/index.html">
|
||||
<div class="card-icon">🔗</div>
|
||||
<h2 class="card-title">Nx Dependency Graph</h2>
|
||||
<p class="card-description">Visual representation of project dependencies and build structure</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
|
||||
<a href="./coverage/index.html" class="card" data-status="coverage" data-fetch="./coverage/index.html">
|
||||
<div class="card-icon">📊</div>
|
||||
<h2 class="card-title">Test Coverage</h2>
|
||||
<p class="card-description">Code coverage reports from Vitest unit tests</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
|
||||
<a href="./playwright-reports/index.html" class="card" data-status="playwright" data-fetch="./playwright-reports/index.html">
|
||||
<div class="card-icon">🎭</div>
|
||||
<h2 class="card-title">Playwright E2E</h2>
|
||||
<p class="card-description">Browser end-to-end test reports generated by Playwright</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
|
||||
<a href="./vitest-reports/index.html" class="card" data-status="vitest-reports" data-fetch="./vitest-reports/index.html">
|
||||
<div class="card-icon">🧪</div>
|
||||
<h2 class="card-title">Vitest Results</h2>
|
||||
<p class="card-description">Interactive test results and reports</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
|
||||
<a href="./knip.html" class="card" data-status="knip" data-fetch="./knip/report.md">
|
||||
<div class="card-icon">🔍</div>
|
||||
<h2 class="card-title">Knip Report</h2>
|
||||
<p class="card-description">Unused code and dependency analysis</p>
|
||||
<span class="card-status status-loading" data-status-indicator>Checking…</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>
|
||||
Built from the <strong>main</strong> branch •
|
||||
<a href="https://github.com/Comfy-Org/ComfyUI_frontend" target="_blank">GitHub Repository</a> •
|
||||
<a href="https://docs.comfy.org" target="_blank">Official Documentation</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const cards = Array.from(document.querySelectorAll('.card[data-status]'))
|
||||
|
||||
const setStatus = (card, variant, text) => {
|
||||
const indicator = card.querySelector('[data-status-indicator]')
|
||||
if (!indicator) return
|
||||
indicator.classList.remove('status-loading', 'status-available', 'status-unavailable')
|
||||
indicator.classList.add(`status-${variant}`)
|
||||
indicator.textContent = text
|
||||
}
|
||||
|
||||
const absoluteUrl = (href) => new URL(href, window.location.href).toString()
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => controller.abort(), 10000)
|
||||
|
||||
cards.forEach((card) => {
|
||||
const href = card.dataset.fetch || card.getAttribute('href')
|
||||
if (!href) {
|
||||
setStatus(card, 'unavailable', 'No link configured')
|
||||
return
|
||||
}
|
||||
|
||||
fetch(absoluteUrl(href), { cache: 'no-store', signal: controller.signal })
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
setStatus(card, 'available', 'Available')
|
||||
} else {
|
||||
setStatus(card, 'unavailable', `Unavailable (${response.status})`)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
const reason = error.name === 'AbortError' ? 'Timed out' : 'Unavailable'
|
||||
setStatus(card, 'unavailable', reason)
|
||||
})
|
||||
})
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
clearTimeout(timeout)
|
||||
controller.abort()
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
87
.pages/knip.html
Normal file
87
.pages/knip.html
Normal file
@@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Knip Report</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||
padding: 2rem;
|
||||
background: #1a1a1a;
|
||||
color: #f0f0f0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
h1 {
|
||||
color: #4a9eff;
|
||||
border-bottom: 3px solid #4a9eff;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 1rem 0;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.error {
|
||||
background: #d32f2f;
|
||||
color: #ffebee;
|
||||
}
|
||||
.loading {
|
||||
background: #f57c00;
|
||||
color: #fff3e0;
|
||||
}
|
||||
#content {
|
||||
background: #2a2a2a;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #404040;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🧹 Knip Code Quality Report</h1>
|
||||
|
||||
<div id="status" class="status loading">Loading report...</div>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
<script>
|
||||
async function loadReport() {
|
||||
const statusEl = document.getElementById('status')
|
||||
const contentEl = document.getElementById('content')
|
||||
|
||||
try {
|
||||
const response = await fetch('./knip/report.md')
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const reportText = await response.text()
|
||||
statusEl.style.display = 'none'
|
||||
|
||||
// Wait for marked to be available
|
||||
if (typeof marked !== 'undefined') {
|
||||
contentEl.innerHTML = marked.parse(reportText)
|
||||
} else {
|
||||
contentEl.innerHTML = `<pre>${reportText}</pre>`
|
||||
}
|
||||
} catch (error) {
|
||||
statusEl.className = 'status error'
|
||||
statusEl.textContent = `Failed to load report: ${error.message}`
|
||||
|
||||
contentEl.innerHTML = '<p>The Knip report could not be loaded. This might happen if:</p><ul><li>The report generation failed during build</li><li>No unused code was detected</li><li>Network connectivity issues</li></ul>'
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for marked library to load before running
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', loadReport)
|
||||
} else {
|
||||
loadReport()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
300
.pages/playwright-reports.html
Normal file
300
.pages/playwright-reports.html
Normal file
@@ -0,0 +1,300 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Playwright E2E Test Reports</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
font-size: 2.5rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 1rem auto;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #d32f2f;
|
||||
color: #ffebee;
|
||||
}
|
||||
|
||||
.loading {
|
||||
background: #f57c00;
|
||||
color: #fff3e0;
|
||||
}
|
||||
|
||||
.cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
color: #333;
|
||||
font-size: 1.5rem;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.pass-rate {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.pass-rate.has-failures {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.stat .label {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat .value {
|
||||
font-size: 1.75rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stat.passed .value {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.stat.failed .value {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.stat.skipped .value {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.stat.flaky .value {
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.cards-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎭 Playwright E2E Test Reports</h1>
|
||||
|
||||
<div id="status" class="status loading">Loading reports...</div>
|
||||
|
||||
<div id="content" class="cards-grid"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function getTestStats(browserName) {
|
||||
try {
|
||||
const reportJsonPath = `./playwright-reports/${browserName}/report.json`
|
||||
const response = await fetch(reportJsonPath)
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`No report.json found for ${browserName}`)
|
||||
return null
|
||||
}
|
||||
|
||||
const reportData = await response.json()
|
||||
|
||||
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 for ${browserName}:`, error.message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function createCard(browserName, stats) {
|
||||
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 `
|
||||
<a href="./playwright-reports/${browserName}/index.html" class="card">
|
||||
<div class="card-header">
|
||||
<h2>${browserName}</h2>
|
||||
<span class="pass-rate ${stats.failed > 0 ? 'has-failures' : ''}">${passRate}%</span>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat passed">
|
||||
<span class="label">Passed</span>
|
||||
<span class="value">${stats.passed}</span>
|
||||
</div>
|
||||
<div class="stat failed">
|
||||
<span class="label">Failed</span>
|
||||
<span class="value">${stats.failed}</span>
|
||||
</div>
|
||||
<div class="stat skipped">
|
||||
<span class="label">Skipped</span>
|
||||
<span class="value">${stats.skipped}</span>
|
||||
</div>
|
||||
<div class="stat flaky">
|
||||
<span class="label">Flaky</span>
|
||||
<span class="value">${stats.flaky}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
`
|
||||
}
|
||||
|
||||
async function loadReports() {
|
||||
const statusEl = document.getElementById('status')
|
||||
const contentEl = document.getElementById('content')
|
||||
|
||||
try {
|
||||
// Known browser configurations from the workflow
|
||||
const browsers = ['chromium', 'chromium-2x', 'chromium-0.5x', 'mobile-chrome']
|
||||
|
||||
const cards = []
|
||||
|
||||
for (const browser of browsers) {
|
||||
const stats = await getTestStats(browser)
|
||||
if (stats) {
|
||||
cards.push(createCard(browser, stats))
|
||||
console.log(`✓ Found report for ${browser}:`, stats)
|
||||
}
|
||||
}
|
||||
|
||||
if (cards.length === 0) {
|
||||
throw new Error('No valid browser reports found')
|
||||
}
|
||||
|
||||
statusEl.style.display = 'none'
|
||||
contentEl.innerHTML = cards.join('')
|
||||
} catch (error) {
|
||||
statusEl.className = 'status error'
|
||||
statusEl.textContent = `Failed to load reports: ${error.message}`
|
||||
|
||||
contentEl.innerHTML = '<p style="color: white; text-align: center;">The Playwright reports could not be loaded. This might happen if:</p><ul style="color: white; text-align: center; list-style: none;"><li>The report generation failed during build</li><li>No test reports are available yet</li><li>Network connectivity issues</li></ul>'
|
||||
}
|
||||
}
|
||||
|
||||
// Load reports when page loads
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', loadReports)
|
||||
} else {
|
||||
loadReports()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
46
.pages/vite.config.ts
Normal file
46
.pages/vite.config.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import fs from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
const rootDir = __dirname
|
||||
const outDir = resolve(rootDir, '../.pages-dist')
|
||||
|
||||
const discoverHtmlEntries = () => {
|
||||
const entries = new Map<string, string>()
|
||||
const topLevel = resolve(rootDir, 'index.html')
|
||||
if (fs.existsSync(topLevel)) entries.set('index', topLevel)
|
||||
|
||||
for (const dirent of fs.readdirSync(rootDir, { withFileTypes: true })) {
|
||||
if (!dirent.isDirectory() || dirent.name.startsWith('.')) continue
|
||||
const candidate = resolve(rootDir, dirent.name, 'index.html')
|
||||
if (fs.existsSync(candidate)) entries.set(dirent.name, candidate)
|
||||
}
|
||||
|
||||
return entries.size > 0 ? Object.fromEntries(entries) : undefined
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
root: rootDir,
|
||||
base: '/ComfyUI_frontend/',
|
||||
appType: 'mpa',
|
||||
logLevel: 'info',
|
||||
publicDir: false,
|
||||
server: {
|
||||
open: '/index.html',
|
||||
fs: {
|
||||
allow: [rootDir],
|
||||
strict: false
|
||||
}
|
||||
},
|
||||
preview: {
|
||||
open: '/index.html'
|
||||
},
|
||||
build: {
|
||||
emptyOutDir: false,
|
||||
outDir,
|
||||
copyPublicDir: false,
|
||||
rollupOptions: {
|
||||
input: discoverHtmlEntries()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -8,38 +8,53 @@ const config: KnipConfig = {
|
||||
'src/assets/css/style.css',
|
||||
'src/main.ts',
|
||||
'src/scripts/ui/menu/index.ts',
|
||||
'src/types/index.ts',
|
||||
'src/storybook/mocks/**/*.ts'
|
||||
'src/types/index.ts'
|
||||
],
|
||||
project: ['**/*.{js,ts,vue}', '*.{js,ts,mts}']
|
||||
},
|
||||
'apps/desktop-ui': {
|
||||
entry: ['src/main.ts', 'src/i18n.ts'],
|
||||
project: ['src/**/*.{js,ts,vue}']
|
||||
},
|
||||
'packages/tailwind-utils': {
|
||||
project: ['src/**/*.{js,ts}']
|
||||
},
|
||||
'packages/design-system': {
|
||||
entry: ['src/**/*.ts'],
|
||||
project: ['src/**/*.{js,ts}', '*.{js,ts,mts}']
|
||||
},
|
||||
'packages/registry-types': {
|
||||
project: ['src/**/*.{js,ts}']
|
||||
}
|
||||
},
|
||||
ignoreBinaries: ['python3', 'gh'],
|
||||
ignoreBinaries: ['python3'],
|
||||
ignoreDependencies: [
|
||||
// Weird importmap things
|
||||
'@iconify-json/lucide',
|
||||
'@iconify/json',
|
||||
'@primeuix/forms',
|
||||
'@primeuix/styled',
|
||||
'@primeuix/utils',
|
||||
'@primevue/icons'
|
||||
'@primevue/icons',
|
||||
// Dev
|
||||
'@trivago/prettier-plugin-sort-imports',
|
||||
// 3D and collaboration libraries
|
||||
'three',
|
||||
'@types/three',
|
||||
'yjs'
|
||||
],
|
||||
ignore: [
|
||||
// Auto generated manager types
|
||||
'src/workbench/extensions/manager/types/generatedManagerTypes.ts',
|
||||
'packages/registry-types/src/comfyRegistryTypes.ts',
|
||||
// Used by a custom node (that should move off of this)
|
||||
'src/scripts/ui/components/splitButton.ts'
|
||||
'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'
|
||||
],
|
||||
compilers: {
|
||||
// https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
"lint:desktop": "nx run @comfyorg/desktop-ui:lint",
|
||||
"locale": "lobe-i18n locale",
|
||||
"oxlint": "oxlint src --type-aware",
|
||||
"pages:build": "bash scripts/build-pages.sh && vite build --config ./.pages/vite.config.ts",
|
||||
"pages:dev": "vite --config ./.pages/vite.config.ts",
|
||||
"preinstall": "pnpm dlx only-allow pnpm",
|
||||
"prepare": "husky || true && git config blame.ignoreRevsFile .git-blame-ignore-revs || true",
|
||||
"preview": "nx preview",
|
||||
|
||||
75
scripts/build-pages.sh
Executable file
75
scripts/build-pages.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
|
||||
# Build or reuse Storybook
|
||||
echo "[build-pages] Setting up Storybook"
|
||||
rm -rf ".pages/storybook"
|
||||
if [ -d "./storybook-static" ] && [ "$(find ./storybook-static -name '*.html' | wc -l)" -gt 0 ]; then
|
||||
echo "✅ Reusing downloaded Storybook build"
|
||||
cp -r "./storybook-static" ".pages/storybook"
|
||||
else
|
||||
echo "🔨 Building Storybook from source"
|
||||
pnpm build-storybook && cp -r "storybook-static" ".pages/storybook"
|
||||
fi
|
||||
|
||||
echo "[build-pages] Generating Nx dependency graph"
|
||||
rm -rf ".pages/nx-graph" && mkdir -p ".pages/nx-graph"
|
||||
pnpm nx graph --file=".pages/nx-graph/index.html"
|
||||
|
||||
# Generate or reuse Vitest test reports
|
||||
echo "[build-pages] Setting up Vitest test reports"
|
||||
rm -rf ".pages/vitest-reports" && mkdir -p ".pages/vitest-reports"
|
||||
if [ -d ".page/vitest-reports" ]; then
|
||||
echo "✅ Reusing downloaded Vitest reports"
|
||||
cp -r ".page/vitest-reports/"* ".pages/vitest-reports/" 2>/dev/null || echo "⚠️ No vitest reports to copy"
|
||||
else
|
||||
echo "🔨 Generating Vitest reports from source"
|
||||
pnpm exec vitest \
|
||||
--reporter=json --outputFile.json=".pages/vitest-reports/results.json" \
|
||||
--reporter=html --outputFile.html=".pages/vitest-reports/index.html" \
|
||||
--run
|
||||
fi
|
||||
|
||||
# Set up Playwright test reports if available
|
||||
echo "[build-pages] Setting up Playwright test reports"
|
||||
if [ -d ".page/playwright-reports" ]; then
|
||||
echo "✅ Reusing downloaded Playwright reports"
|
||||
mkdir -p ".pages/playwright-reports"
|
||||
cp -r ".page/playwright-reports/"* ".pages/playwright-reports/" 2>/dev/null || echo "⚠️ No playwright reports to copy"
|
||||
fi
|
||||
|
||||
echo "[build-pages] Generating coverage report"
|
||||
mkdir -p ".pages/coverage"
|
||||
if pnpm exec vitest --run --coverage --coverage.reporter=html --coverage.reportsDirectory=".pages/coverage"; then
|
||||
echo "✅ Coverage report completed"
|
||||
else
|
||||
echo "⚠️ Coverage report failed, continuing..."
|
||||
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
|
||||
echo "✅ Knip report generated at .pages/knip/report.md"
|
||||
else
|
||||
echo "⚠️ Knip report failed, creating placeholder..."
|
||||
cat > ".pages/knip/report.md" <<'EOF'
|
||||
# Knip report
|
||||
|
||||
> ⚠️ Knip report unavailable.
|
||||
>
|
||||
> Generation failed during build. See CI logs for details.
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo "[build-pages] Landing page already exists at .pages/index.html"
|
||||
|
||||
echo "[build-pages] Build artifacts ready in ./.pages"
|
||||
|
||||
echo "[build-pages] Note: For local dev, you can develop the .pages/index.html using:
|
||||
pnpm exec vite .pages
|
||||
"
|
||||
290
scripts/create-playwright-index.js
Executable file
290
scripts/create-playwright-index.js
Executable file
@@ -0,0 +1,290 @@
|
||||
#!/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 `
|
||||
<a href="./${name}/index.html" class="card">
|
||||
<div class="card-header">
|
||||
<h2>${name}</h2>
|
||||
<span class="pass-rate ${stats.failed > 0 ? 'has-failures' : ''}">${passRate}%</span>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat passed">
|
||||
<span class="label">Passed</span>
|
||||
<span class="value">${stats.passed}</span>
|
||||
</div>
|
||||
<div class="stat failed">
|
||||
<span class="label">Failed</span>
|
||||
<span class="value">${stats.failed}</span>
|
||||
</div>
|
||||
<div class="stat skipped">
|
||||
<span class="label">Skipped</span>
|
||||
<span class="value">${stats.skipped}</span>
|
||||
</div>
|
||||
<div class="stat flaky">
|
||||
<span class="label">Flaky</span>
|
||||
<span class="value">${stats.flaky}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
`
|
||||
})
|
||||
.join('')
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Playwright E2E Test Reports</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
font-size: 2.5rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
color: #333;
|
||||
font-size: 1.5rem;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.pass-rate {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.pass-rate.has-failures {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.stat .label {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat .value {
|
||||
font-size: 1.75rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stat.passed .value {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.stat.failed .value {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.stat.skipped .value {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.stat.flaky .value {
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.cards-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎭 Playwright E2E Test Reports</h1>
|
||||
<div class="cards-grid">
|
||||
${cards}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -35,6 +35,7 @@
|
||||
"rootDir": "./"
|
||||
},
|
||||
"include": [
|
||||
".pages/*.ts",
|
||||
".storybook/**/*",
|
||||
"eslint.config.ts",
|
||||
"global.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user