diff --git a/.github/workflows/ci-tests-e2e.yaml b/.github/workflows/ci-tests-e2e.yaml index 34d24f7f04..7f8b9762b7 100644 --- a/.github/workflows/ci-tests-e2e.yaml +++ b/.github/workflows/ci-tests-e2e.yaml @@ -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: diff --git a/.github/workflows/ci-tests-storybook.yaml b/.github/workflows/ci-tests-storybook.yaml index 7a91e7a014..5a715cc01a 100644 --- a/.github/workflows/ci-tests-storybook.yaml +++ b/.github/workflows/ci-tests-storybook.yaml @@ -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 }}'; diff --git a/.github/workflows/release-pages.yml b/.github/workflows/release-pages.yml new file mode 100644 index 0000000000..943f16db57 --- /dev/null +++ b/.github/workflows/release-pages.yml @@ -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 diff --git a/.gitignore b/.gitignore index 6eddbcb531..7b42c0ef0b 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file +.vercel diff --git a/.pages/README.md b/.pages/README.md new file mode 100644 index 0000000000..3aec49ca01 --- /dev/null +++ b/.pages/README.md @@ -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) \ No newline at end of file diff --git a/.pages/index.html b/.pages/index.html new file mode 100644 index 0000000000..e15888a01c --- /dev/null +++ b/.pages/index.html @@ -0,0 +1,211 @@ + + + + + + ComfyUI Frontend - Development Tools + + + +
+
+

🎨 ComfyUI Frontend

+

Development Tools & Documentation

+
+ +
+ +
📚
+

Storybook

+

Interactive component library and design system documentation

+ Checking… +
+ + +
🔗
+

Nx Dependency Graph

+

Visual representation of project dependencies and build structure

+ Checking… +
+ + +
📊
+

Test Coverage

+

Code coverage reports from Vitest unit tests

+ Checking… +
+ + +
🎭
+

Playwright E2E

+

Browser end-to-end test reports generated by Playwright

+ Checking… +
+ + +
🧪
+

Vitest Results

+

Interactive test results and reports

+ Checking… +
+ + +
🔍
+

Knip Report

+

Unused code and dependency analysis

+ Checking… +
+
+ + +
+ + + + \ No newline at end of file diff --git a/.pages/knip.html b/.pages/knip.html new file mode 100644 index 0000000000..d4f9b7342b --- /dev/null +++ b/.pages/knip.html @@ -0,0 +1,87 @@ + + + + + Knip Report + + + + +

🧹 Knip Code Quality Report

+ +
Loading report...
+ +
+ + + + diff --git a/.pages/playwright-reports.html b/.pages/playwright-reports.html new file mode 100644 index 0000000000..8a8a5f3bf6 --- /dev/null +++ b/.pages/playwright-reports.html @@ -0,0 +1,300 @@ + + + + + + Playwright E2E Test Reports + + + +
+

🎭 Playwright E2E Test Reports

+ +
Loading reports...
+ +
+
+ + + + diff --git a/.pages/vite.config.ts b/.pages/vite.config.ts new file mode 100644 index 0000000000..1a90484ee0 --- /dev/null +++ b/.pages/vite.config.ts @@ -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() + 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() + } + } +}) diff --git a/knip.config.ts b/knip.config.ts index e000e18825..3f8495d401 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -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 diff --git a/package.json b/package.json index ab5abe7c90..d352ef324d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/build-pages.sh b/scripts/build-pages.sh new file mode 100755 index 0000000000..fe63c086fd --- /dev/null +++ b/scripts/build-pages.sh @@ -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 +" diff --git a/scripts/create-playwright-index.js b/scripts/create-playwright-index.js new file mode 100755 index 0000000000..de8ac433a3 --- /dev/null +++ b/scripts/create-playwright-index.js @@ -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 ` + +
+

${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/tsconfig.json b/tsconfig.json index 988be359ba..06324654f6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,6 +35,7 @@ "rootDir": "./" }, "include": [ + ".pages/*.ts", ".storybook/**/*", "eslint.config.ts", "global.d.ts",