chore(deps): update all pnpm dependencies to latest (Vite 7 pinned) (… #1105
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: ["main"] | |
| pull_request: | |
| branches: ["main"] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Least-privilege: restrict GITHUB_TOKEN to read-only by default. | |
| # Jobs that need more (e.g. pull-requests: write) declare their own block. | |
| permissions: | |
| contents: read | |
| # Suppress deprecation warnings from upstream GitHub Actions dependencies | |
| env: | |
| NODE_OPTIONS: "--no-deprecation" | |
| jobs: | |
| ci-setup: | |
| name: "🚀 Initialize CI Report" | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Post Initial Comment | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const { default: script } = await import(`file://${process.env.GITHUB_WORKSPACE}/scripts/ci/update-ci-comment.mjs`) | |
| await script({github, context, step: 'init'}) | |
| build: | |
| name: Build Artifact | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: ci-setup | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Cache Playwright Browsers | |
| id: playwright-cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-browsers-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Cache Optimized Images | |
| uses: actions/cache@v5 | |
| with: | |
| path: .cache/optimized-images | |
| key: optimized-images-${{ runner.os }}-${{ hashFiles('astro.config.mjs', 'pnpm-lock.yaml', 'src/**/*.{png,jpg,jpeg,webp,avif,gif,svg}', 'public/**/*.{png,jpg,jpeg,webp,avif,gif,svg}') }} | |
| restore-keys: | | |
| optimized-images-${{ runner.os }}- | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Install Playwright Browsers | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| run: pnpm exec playwright install --with-deps chromium | |
| - name: Build Project (Production) | |
| run: pnpm run build | |
| - name: Upload build artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: dist-build | |
| path: dist/ | |
| retention-days: 1 | |
| # --- Stage: Quality & Security Checks (Parallel) --- | |
| sa-astro: | |
| name: "SA: Astro Check" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Run Astro Check | |
| run: pnpm exec astro check 2>&1 | tee astro-check.log | |
| - name: Upload Astro Check log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-astro | |
| path: astro-check.log | |
| retention-days: 1 | |
| sa-prettier: | |
| name: "SA: Prettier" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Run Prettier check | |
| run: pnpm exec prettier --check . 2>&1 | tee prettier.log | |
| - name: Upload Prettier log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-prettier | |
| path: prettier.log | |
| retention-days: 1 | |
| sa-eslint: | |
| name: "SA: ESLint" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Run ESLint | |
| run: pnpm exec eslint . --max-warnings=0 2>&1 | tee eslint.log | |
| - name: Upload ESLint log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-eslint | |
| path: eslint.log | |
| retention-days: 1 | |
| sa-audit: | |
| name: "SA: Security Audit" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Run Security Audit | |
| run: pnpm audit --audit-level=high 2>&1 | tee security-audit.log | |
| - name: Upload Audit log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-audit | |
| path: security-audit.log | |
| retention-days: 1 | |
| sa-stylelint: | |
| name: "SA: Stylelint" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Run Stylelint | |
| run: pnpm exec stylelint "src/**/*.{astro,css}" 2>&1 | tee stylelint.log | |
| - name: Upload Stylelint log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-stylelint | |
| path: stylelint.log | |
| retention-days: 1 | |
| sa-jsdoc: | |
| name: "SA: JSDoc Coverage" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| outputs: | |
| coverage: ${{ steps.jsdoc_calc.outputs.coverage }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Calculate JSDoc Coverage | |
| id: jsdoc_calc | |
| run: | | |
| OUTPUT=$(node scripts/ci/calculate-jsdoc-coverage.mjs 2>&1 | tee jsdoc-coverage.log) | |
| echo "$OUTPUT" | |
| COVERAGE=$(echo "$OUTPUT" | sed -nE 's/.*Coverage: ([0-9.]+).*/\1/p') | |
| if [ -z "$COVERAGE" ]; then | |
| echo "::error::Failed to parse JSDoc coverage from output." | |
| exit 1 | |
| fi | |
| echo "coverage=${COVERAGE}%" >> $GITHUB_OUTPUT | |
| - name: Upload JSDoc log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-jsdoc | |
| path: jsdoc-coverage.log | |
| retention-days: 1 | |
| sa-lychee: | |
| name: "SA: Link Checker (Dist)" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Restore lychee cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: .lycheecache | |
| key: cache-lychee-source-${{ github.sha }} | |
| restore-keys: cache-lychee-source- | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dist-build | |
| path: dist | |
| - uses: lycheeverse/lychee-action@v2.7.0 | |
| with: | |
| args: --config lychee.toml --root-dir ${{ github.workspace }}/dist --format markdown ./dist/**/*.html | |
| output: lychee-report.md | |
| fail: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.TOKEN_GITHUB }} | |
| - name: Generate Lychee HTML Report | |
| if: always() | |
| run: | | |
| set -e -o pipefail | |
| node scripts/ci/generate-lychee-report.mjs | |
| - name: Upload Lychee reports | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: lychee-report | |
| path: | | |
| lychee-report.md | |
| lychee-report.html | |
| retention-days: 1 | |
| sa-cspell: | |
| name: "SA: Spell Checker" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: package.json | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Run CSpell | |
| run: pnpm exec cspell lint "." --no-progress 2>&1 | tee cspell.log | |
| - name: Upload CSpell log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-cspell | |
| path: cspell.log | |
| retention-days: 1 | |
| sa-sonar: | |
| name: "SA: SonarQube" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: SonarQube Scan | |
| # Only run when SONAR_TOKEN is configured | |
| if: env.SONAR_TOKEN != '' | |
| uses: SonarSource/sonarqube-scan-action@v7 | |
| continue-on-error: true | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| bundle-size: | |
| name: Bundle Size Check | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dist-build | |
| path: dist | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Analyze bundle size | |
| run: node scripts/ci/analyze-bundle-size.mjs 2>&1 | tee bundle-size.log | |
| - name: Upload bundle log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-bundle | |
| path: bundle-size.log | |
| retention-days: 1 | |
| - name: Upload bundle analysis | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: bundle-analysis | |
| path: bundle-analysis.json | |
| retention-days: 1 | |
| html-validator: | |
| name: HTML Validation | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dist-build | |
| path: dist | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Validate HTML5 | |
| # Two html-validate runs: | |
| # 1. JSON formatter with || true: Always produce a best-effort JSON report for dashboard | |
| # 2. Stylish formatter (no || true): This run determines workflow pass/fail | |
| run: | | |
| pnpm exec html-validate -c .htmlvalidate.json --formatter json dist > html-validation.json 2>&1 || true | |
| pnpm exec html-validate -c .htmlvalidate.json --formatter stylish dist 2>&1 | tee html-validation.log | |
| node scripts/ci/generate-html-validation-report.mjs | |
| - name: Upload HTML log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-html | |
| path: html-validation.log | |
| retention-days: 1 | |
| - name: Upload HTML validation files | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: html-validation-report | |
| path: | | |
| html-validation.json | |
| html-validation-report.html | |
| retention-days: 1 | |
| rss-validation: | |
| name: RSS Feed Validation | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dist-build | |
| path: dist | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Validate RSS feed | |
| run: | | |
| set -e -o pipefail | |
| node scripts/ci/validate-rss.mjs dist 2>&1 | tee rss-validation.log | |
| - name: Generate RSS preview | |
| run: | | |
| set -e -o pipefail | |
| node scripts/preview-rss.mjs | |
| - name: Upload RSS log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-rss | |
| path: rss-validation.log | |
| retention-days: 1 | |
| - name: Upload RSS validation files | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: rss-validation-report | |
| path: | | |
| rss-validation.json | |
| dist/rss-preview.html | |
| retention-days: 1 | |
| schema-validation: | |
| name: Schema.org JSON-LD Validation | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dist-build | |
| path: dist | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Validate Schema.org structured data | |
| run: node scripts/ci/validate-schema.mjs dist 2>&1 | tee schema-validation.log | |
| - name: Generate Schema Report | |
| if: always() | |
| run: | | |
| if [ -f schema-report.json ]; then | |
| node scripts/ci/generate-schema-report.mjs | |
| fi | |
| - name: Upload Schema log | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: log-schema | |
| path: schema-validation.log | |
| retention-days: 1 | |
| - name: Upload Schema validation files | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: schema-validation-report | |
| path: | | |
| schema-report.json | |
| schema-report.html | |
| retention-days: 1 | |
| image-optimization: | |
| name: Image Optimization Check | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dist-build | |
| path: dist | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Analyze images | |
| run: | | |
| echo "## 🖼️ Image Optimization Analysis" >> $GITHUB_STEP_SUMMARY | |
| WEBP_COUNT=$(find dist -type f -name "*.webp" | wc -l) | |
| echo "- WebP images: $WEBP_COUNT ✅" >> $GITHUB_STEP_SUMMARY | |
| - name: Generate Image HTML Report | |
| run: node scripts/ci/generate-image-report.mjs | |
| - name: Upload image report | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: image-optimization-report | |
| path: image-report.html | |
| retention-days: 1 | |
| functional-tests: | |
| name: Functional Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: build | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dist-build | |
| path: dist | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Cache Playwright Browsers | |
| id: playwright-cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-browsers-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Install Playwright Browsers | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| run: pnpm exec playwright install --with-deps chromium | |
| - name: Run Playwright tests | |
| run: pnpm exec playwright test --project=functional --project=mobile-functional | |
| - name: Upload functional report | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: functional-report | |
| path: playwright-report/ | |
| retention-days: 1 | |
| accessibility: | |
| name: Accessibility Tests (${{ matrix.theme }} - ${{ matrix.locale }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: build | |
| strategy: | |
| matrix: | |
| theme: [light, dark] | |
| locale: [en, es] | |
| fail-fast: false | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dist-build | |
| path: dist | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Cache Playwright Browsers | |
| id: playwright-cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-browsers-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Install Playwright Browsers | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| run: pnpm exec playwright install --with-deps chromium | |
| - name: Run Axe accessibility tests | |
| run: THEME=${{ matrix.theme }} LOCALE_FILTER=${{ matrix.locale }} pnpm exec playwright test --project=accessibility | |
| - name: Upload Axe Results | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: axe-accessibility-report-${{ matrix.theme }}-${{ matrix.locale }} | |
| path: accessibility-report/ | |
| retention-days: 7 | |
| lighthouse-audit: | |
| name: LH Audit (${{ matrix.theme }} - ${{ matrix.formFactor }} - ${{ matrix.locale }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| needs: build | |
| strategy: | |
| matrix: | |
| theme: [light, dark] | |
| formFactor: [mobile, desktop] | |
| locale: [en, es] | |
| fail-fast: false | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: dist-build | |
| path: dist | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Run Lighthouse CI | |
| shell: bash | |
| env: | |
| FORM_FACTOR: ${{ matrix.formFactor }} | |
| LOCALE_FILTER: ${{ matrix.locale }} | |
| LHCI_GITHUB_TOKEN: ${{ secrets.TOKEN_GITHUB }} | |
| run: | | |
| if [ "${{ matrix.theme }}" = "dark" ]; then export LHCI_CHROME_FLAGS="--force-dark-mode --force-prefers-color-scheme=dark"; fi | |
| pnpm exec lhci autorun | |
| - name: Upload Lighthouse Results | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: lh-results-${{ matrix.theme }}-${{ matrix.formFactor }}-${{ matrix.locale }} | |
| path: lighthouse-results/ | |
| retention-days: 1 | |
| # --- Stage: Report Aggregation --- | |
| static-analysis-report: | |
| name: Static Analysis Report | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| if: always() | |
| needs: | |
| [ | |
| sa-astro, | |
| sa-prettier, | |
| sa-eslint, | |
| sa-audit, | |
| sa-jsdoc, | |
| sa-lychee, | |
| sa-cspell, | |
| sa-sonar, | |
| sa-stylelint, | |
| ci-setup, | |
| ] | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Update CI Comment (SA Results) | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v9 | |
| env: | |
| OUTCOME_ASTRO: ${{ needs.sa-astro.result }} | |
| OUTCOME_PRETTIER: ${{ needs.sa-prettier.result }} | |
| OUTCOME_ESLINT: ${{ needs.sa-eslint.result }} | |
| OUTCOME_LYCHEE: ${{ needs.sa-lychee.result }} | |
| OUTCOME_CSPELL: ${{ needs.sa-cspell.result }} | |
| OUTCOME_STYLELINT: ${{ needs.sa-stylelint.result }} | |
| OUTCOME_SECURITY: ${{ needs.sa-audit.result }} | |
| OUTCOME_SONAR: ${{ needs.sa-sonar.result }} | |
| OUTCOME_JSDOC: ${{ needs.sa-jsdoc.result }} | |
| JSDOC_COVERAGE: ${{ needs.sa-jsdoc.outputs.coverage }} | |
| with: | |
| script: | | |
| const { default: script } = await import(`file://${process.env.GITHUB_WORKSPACE}/scripts/ci/update-ci-comment.mjs`) | |
| await script({github, context, step: 'sa'}) | |
| accessibility-report: | |
| name: Accessibility Report | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: accessibility | |
| if: always() | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| # Download each locale separately to avoid summary JSON overwrites | |
| - name: Download Axe Results (Light EN) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: axe-accessibility-report-light-en | |
| path: a11y-deploy/light/en | |
| - name: Download Axe Results (Light ES) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: axe-accessibility-report-light-es | |
| path: a11y-deploy/light/es | |
| - name: Download Axe Results (Dark EN) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: axe-accessibility-report-dark-en | |
| path: a11y-deploy/dark/en | |
| - name: Download Axe Results (Dark ES) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: axe-accessibility-report-dark-es | |
| path: a11y-deploy/dark/es | |
| - name: Merge locale results per theme | |
| run: | | |
| for theme in light dark; do | |
| # Move HTML reports from locale subdirs to theme dir | |
| for locale in en es; do | |
| src="a11y-deploy/${theme}/${locale}" | |
| if [ -d "$src" ]; then | |
| # Move HTML reports (skip summary JSONs and index.html) | |
| find "$src" -maxdepth 1 -name '*.html' ! -name 'index.html' \ | |
| -exec mv {} "a11y-deploy/${theme}/" \; | |
| fi | |
| done | |
| # Merge summary JSONs per theme using Node.js | |
| node -e " | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const theme = '${theme}'; | |
| const base = 'a11y-deploy/${theme}'; | |
| const summaries = []; | |
| for (const locale of ['en', 'es']) { | |
| const f = path.join(base, locale, 'accessibility-summary-' + theme + '.json'); | |
| if (fs.existsSync(f)) summaries.push(JSON.parse(fs.readFileSync(f, 'utf-8'))); | |
| } | |
| if (summaries.length === 0) { console.log('No summaries for ' + theme); process.exit(0); } | |
| // Merge pages arrays | |
| const pages = summaries.flatMap(s => s.pages || []); | |
| // Merge violations/incomplete by rule id, summing nodes | |
| function mergeRules(arrays) { | |
| const map = new Map(); | |
| for (const arr of arrays) { | |
| for (const item of (arr || [])) { | |
| const existing = map.get(item.id); | |
| if (existing) { existing.nodes += item.nodes || 0; } | |
| else { map.set(item.id, { ...item }); } | |
| } | |
| } | |
| return [...map.values()]; | |
| } | |
| const merged = { | |
| theme, | |
| totalPages: pages.length, | |
| passed: pages.filter(p => p.violations === 0).length, | |
| failed: pages.filter(p => p.violations > 0).length, | |
| incomplete: pages.filter(p => p.incomplete > 0).length, | |
| violations: mergeRules(summaries.map(s => s.violations)), | |
| incompleteList: mergeRules(summaries.map(s => s.incompleteList)), | |
| pages, | |
| }; | |
| fs.writeFileSync('a11y-deploy/accessibility-summary-' + theme + '.json', JSON.stringify(merged, null, 2)); | |
| console.log('Merged ' + theme + ' summary: ' + merged.totalPages + ' pages'); | |
| " | |
| # Cleanup locale subdirs | |
| rm -rf "a11y-deploy/${theme}/en" "a11y-deploy/${theme}/es" | |
| done | |
| - name: Generate Dashboard | |
| run: node scripts/ci/generate-accessibility-index.mjs a11y-deploy | |
| - name: Upload consolidated assets | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: a11y-dashboard | |
| path: a11y-deploy/ | |
| retention-days: 1 | |
| lighthouse-report: | |
| name: Lighthouse Report | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: lighthouse-audit | |
| if: always() | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Download LH Results (Light - Mobile - EN) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: lh-results-light-mobile-en | |
| path: lh-deploy/light/mobile/en | |
| - name: Download LH Results (Light - Mobile - ES) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: lh-results-light-mobile-es | |
| path: lh-deploy/light/mobile/es | |
| - name: Download LH Results (Light - Desktop - EN) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: lh-results-light-desktop-en | |
| path: lh-deploy/light/desktop/en | |
| - name: Download LH Results (Light - Desktop - ES) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: lh-results-light-desktop-es | |
| path: lh-deploy/light/desktop/es | |
| - name: Download LH Results (Dark - Mobile - EN) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: lh-results-dark-mobile-en | |
| path: lh-deploy/dark/mobile/en | |
| - name: Download LH Results (Dark - Mobile - ES) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: lh-results-dark-mobile-es | |
| path: lh-deploy/dark/mobile/es | |
| - name: Download LH Results (Dark - Desktop - EN) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: lh-results-dark-desktop-en | |
| path: lh-deploy/dark/desktop/en | |
| - name: Download LH Results (Dark - Desktop - ES) | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: lh-results-dark-desktop-es | |
| path: lh-deploy/dark/desktop/es | |
| - name: Merge Lighthouse manifests per combo | |
| run: | | |
| for dir in lh-deploy/light/mobile lh-deploy/light/desktop lh-deploy/dark/mobile lh-deploy/dark/desktop; do | |
| merged="[]" | |
| for locale_dir in "$dir"/en "$dir"/es; do | |
| if [ -f "$locale_dir/manifest.json" ]; then | |
| merged=$(node -e " | |
| const a = JSON.parse(process.argv[1]); | |
| const b = JSON.parse(require('fs').readFileSync('$locale_dir/manifest.json','utf-8')); | |
| console.log(JSON.stringify([...a, ...b])); | |
| " "$merged") | |
| fi | |
| done | |
| echo "$merged" > "$dir/manifest.json" | |
| echo "Merged manifest for $dir ($(node -e "console.log(JSON.parse(process.argv[1]).length)" "$merged") entries)" | |
| done | |
| - name: Generate Dashboard | |
| run: node scripts/ci/generate-lighthouse-index.mjs lh-deploy | |
| - name: Upload LH Dashboard | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: lh-dashboard | |
| path: lh-deploy/ | |
| retention-days: 1 | |
| deploy-reports: | |
| name: "🚀 Deploy CI Dashboard" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: | |
| [ | |
| ci-setup, | |
| sa-astro, | |
| sa-prettier, | |
| sa-eslint, | |
| sa-audit, | |
| sa-jsdoc, | |
| sa-lychee, | |
| sa-cspell, | |
| sa-sonar, | |
| sa-stylelint, | |
| accessibility-report, | |
| bundle-size, | |
| html-validator, | |
| rss-validation, | |
| schema-validation, | |
| image-optimization, | |
| lighthouse-report, | |
| functional-tests, | |
| ] | |
| if: always() && (github.event_name == 'pull_request' || github.ref == 'refs/heads/main') | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download Accessibility Dashboard | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: a11y-dashboard | |
| path: a11y-deploy | |
| - name: Download Lighthouse Dashboard | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: lh-dashboard | |
| path: lh-deploy | |
| - name: Download HTML Validation | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: html-validation-report | |
| path: . | |
| - name: Download RSS Preview | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: rss-validation-report | |
| path: . | |
| - name: Download Schema Report | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: schema-validation-report | |
| path: . | |
| - name: Download Image Report | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: image-optimization-report | |
| path: . | |
| - name: Download Bundle Stats | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: bundle-analysis | |
| path: . | |
| - name: Download Lychee Report | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| name: lychee-report | |
| path: . | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile --prefer-offline | |
| - name: Download Logs | |
| uses: actions/download-artifact@v7 | |
| continue-on-error: true | |
| with: | |
| pattern: log-* | |
| path: logs | |
| merge-multiple: true | |
| - name: Build Dashboard | |
| run: node scripts/ci/build-report-dashboard.mjs | |
| env: | |
| GITHUB_RUN_NUMBER: ${{ github.run_number }} | |
| GITHUB_RUN_ID: ${{ github.run_id }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| OUTCOME_ASTRO: ${{ needs.sa-astro.result }} | |
| OUTCOME_PRETTIER: ${{ needs.sa-prettier.result }} | |
| OUTCOME_ESLINT: ${{ needs.sa-eslint.result }} | |
| OUTCOME_LYCHEE: ${{ needs.sa-lychee.result }} | |
| OUTCOME_CSPELL: ${{ needs.sa-cspell.result }} | |
| OUTCOME_SECURITY: ${{ needs.sa-audit.result }} | |
| OUTCOME_SONAR: ${{ needs.sa-sonar.result }} | |
| OUTCOME_JSDOC: ${{ needs.sa-jsdoc.result }} | |
| OUTCOME_STYLELINT: ${{ needs.sa-stylelint.result }} | |
| OUTCOME_A11Y: ${{ needs.accessibility-report.result }} | |
| OUTCOME_HTML: ${{ needs.html-validator.result }} | |
| OUTCOME_BUNDLE: ${{ needs.bundle-size.result }} | |
| OUTCOME_RSS: ${{ needs.rss-validation.result }} | |
| OUTCOME_SCHEMA: ${{ needs.schema-validation.result }} | |
| OUTCOME_IMAGE: ${{ needs.image-optimization.result }} | |
| OUTCOME_FUNCTIONAL: ${{ needs.functional-tests.result }} | |
| - name: Deploy to Vercel | |
| id: deploy | |
| env: | |
| VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} | |
| run: | | |
| # Deploy to production domain if on main branch, otherwise preview | |
| IS_PROD=${{ github.ref == 'refs/heads/main' && '--prod' || '' }} | |
| # NOTE: To view PR reports without Vercel authentication, you must disable | |
| # 'Deployment Protection' for this project in your Vercel Dashboard. | |
| PR_ID=${{ github.event.pull_request.number || '0' }} | |
| DEPLOY_URL=$(npx vercel deploy ./dist-reports --archive=tgz --name jmrp-ci-reports --token="$VERCEL_TOKEN" --yes --public $IS_PROD --meta prid=$PR_ID) | |
| echo "url=$DEPLOY_URL" >> $GITHUB_OUTPUT | |
| - name: Update CI Comment (Final Results) | |
| uses: actions/github-script@v9 | |
| env: | |
| VERCEL_URL: ${{ steps.deploy.outputs.url }} | |
| OUTCOME_ASTRO: ${{ needs.sa-astro.result }} | |
| OUTCOME_PRETTIER: ${{ needs.sa-prettier.result }} | |
| OUTCOME_ESLINT: ${{ needs.sa-eslint.result }} | |
| OUTCOME_LYCHEE: ${{ needs.sa-lychee.result }} | |
| OUTCOME_CSPELL: ${{ needs.sa-cspell.result }} | |
| OUTCOME_SECURITY: ${{ needs.sa-audit.result }} | |
| OUTCOME_SONAR: ${{ needs.sa-sonar.result }} | |
| OUTCOME_JSDOC: ${{ needs.sa-jsdoc.result }} | |
| OUTCOME_STYLELINT: ${{ needs.sa-stylelint.result }} | |
| JSDOC_COVERAGE: ${{ needs.sa-jsdoc.outputs.coverage }} | |
| OUTCOME_A11Y: ${{ needs.accessibility-report.result }} | |
| OUTCOME_HTML: ${{ needs.html-validator.result }} | |
| OUTCOME_BUNDLE: ${{ needs.bundle-size.result }} | |
| OUTCOME_RSS: ${{ needs.rss-validation.result }} | |
| OUTCOME_SCHEMA: ${{ needs.schema-validation.result }} | |
| OUTCOME_IMAGE: ${{ needs.image-optimization.result }} | |
| OUTCOME_FUNCTIONAL: ${{ needs.functional-tests.result }} | |
| with: | |
| script: | | |
| const { default: script } = await import(`file://${process.env.GITHUB_WORKSPACE}/scripts/ci/update-ci-comment.mjs`) | |
| await script({github, context, step: 'final'}) |