diff --git a/.gitea/workflows/cicd-checks.yaml b/.gitea/workflows/cicd-checks.yaml index f043790..299ef58 100644 --- a/.gitea/workflows/cicd-checks.yaml +++ b/.gitea/workflows/cicd-checks.yaml @@ -257,9 +257,9 @@ jobs: docker run --rm -e CI=true --entrypoint /bin/sh "${GITEA_REGISTRY}/darkhelm.org/plex-playlist-cicd:${HEAD_SHA}" -c " export HOME=/root && export PRE_COMMIT_HOME=/root/.cache/pre-commit && - mkdir -p "$PRE_COMMIT_HOME" && + mkdir -p "\$PRE_COMMIT_HOME" && echo 'running_hook=${HOOK}' && - echo 'pre_commit_home='"$PRE_COMMIT_HOME" && + echo 'pre_commit_home='"\$PRE_COMMIT_HOME" && echo 'container_shells=' && ls -l /bin/sh /bin/bash 2>/dev/null || true && echo 'tool_paths=' && command -v git /workspace/backend/.venv/bin/pre-commit python3 python 2>/dev/null || true && /workspace/backend/.venv/bin/pre-commit --version && diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..7271c80 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,5 @@ +MD013: false +MD029: false +MD033: false +MD040: false +MD041: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68b8ace..c98ac87 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,11 +38,13 @@ chore/PP-7-update-dependencies ### Useful Git Commands List all feature branches for this project: + ```sh git branch -l 'feat/PP-*' ``` List all branches for a specific issue: + ```sh git branch -l '*/PP-10-*' ``` @@ -52,11 +54,13 @@ git branch -l '*/PP-10-*' ### Title Format Keep PR titles human-friendly and descriptive: + ``` Upgrade backend to Python 3.14 with exact dependency pinning ``` Or if you prefer structured titles: + ``` [feat] PP-10: Upgrade backend to Python 3.14 with exact dependency pinning ``` @@ -64,17 +68,21 @@ Or if you prefer structured titles: ### PR Description Include the issue reference so Gitea can auto-link: + ```markdown Fixes PP-10 ## Summary + Brief description of changes. ## Changes + - Bullet 1 - Bullet 2 ## Testing + How to verify this PR works. ``` @@ -92,6 +100,7 @@ Fixes PP-10 ``` Example: + ``` feat: Add startup compatibility validation @@ -104,6 +113,7 @@ Fixes PP-10 ## Code Quality Gates All commits must pass local pre-commit hooks: + - `ruff` (format + lint) - `pyright` (type checking) - `pytest` (tests + coverage ≥95%) @@ -111,6 +121,7 @@ All commits must pass local pre-commit hooks: - `xdoctest` (doctest extraction) Run locally before pushing: + ```sh cd backend uv run pytest @@ -121,6 +132,7 @@ uv run pytest See [README.md](README.md#manual-setup-if-not-using-docker) for manual setup. For Docker development: + ```sh docker compose -f compose.dev.yml up ``` diff --git a/docs/CICD_SUCCESS_SUMMARY.md b/docs/CICD_SUCCESS_SUMMARY.md index b88e8be..de4902b 100644 --- a/docs/CICD_SUCCESS_SUMMARY.md +++ b/docs/CICD_SUCCESS_SUMMARY.md @@ -6,13 +6,13 @@ ## 📊 **Performance Metrics - Validated Results** -| Metric | Before Optimization | After Optimization | Improvement | -|--------|-------------------|------------------|------------| -| **Total Pipeline Time** | 15-25 minutes | 3-5 minutes | **85% faster** | -| **Build Success Rate** | ~70% (various failures) | **100%** | **30% improvement** | -| **E2E Test Reliability** | ~60% (browser issues) | **100%** | **40% improvement** | -| **Resource Efficiency** | High CPU/memory load | Optimized usage | **Significant** | -| **Developer Experience** | Frequent CI failures | Reliable pipeline | **Excellent** | +| Metric | Before Optimization | After Optimization | Improvement | +| ------------------------ | ----------------------- | ------------------ | ------------------- | +| **Total Pipeline Time** | 15-25 minutes | 3-5 minutes | **85% faster** | +| **Build Success Rate** | ~70% (various failures) | **100%** | **30% improvement** | +| **E2E Test Reliability** | ~60% (browser issues) | **100%** | **40% improvement** | +| **Resource Efficiency** | High CPU/memory load | Optimized usage | **Significant** | +| **Developer Experience** | Frequent CI failures | Reliable pipeline | **Excellent** | ## 🔧 **Key Technical Achievements** @@ -37,6 +37,7 @@ ## 🛠️ **Critical Issues Resolved** ### **Build Phase Issues** + 1. **✅ README.md Dependency Error** - **Problem**: Local package build failed during dependency-only phase - **Solution**: Dummy file creation for minimal package structure @@ -53,6 +54,7 @@ - **Impact**: 100% reliable frontend dependency management ### **Test Phase Issues** + 1. **✅ E2E Docker Pull Complexity** - **Problem**: Over-engineered retry logic for E2E tests only - **Solution**: Use same simple approach as all other successful tests diff --git a/docs/CICD_TROUBLESHOOTING_GUIDE.md b/docs/CICD_TROUBLESHOOTING_GUIDE.md index 865c002..b78a796 100644 --- a/docs/CICD_TROUBLESHOOTING_GUIDE.md +++ b/docs/CICD_TROUBLESHOOTING_GUIDE.md @@ -50,11 +50,11 @@ RUN git clone full_repo && merge_preserving_deps # ✅ Source changes don't bus 3. **No rsync Available**: Base image doesn't include rsync for selective copying - ```dockerfile - # Fix: Use standard cp with backup strategy instead of rsync - # rsync -av --exclude='node_modules' /tmp/fullrepo/ /workspace/ # ❌ Not available - # Standard cp with manual exclusions # ✅ Works everywhere - ``` +```dockerfile + # Fix: Use standard cp with backup strategy instead of rsync + # rsync -av --exclude='node_modules' /tmp/fullrepo/ /workspace/ # ❌ Not available + # Standard cp with manual exclusions # ✅ Works everywhere +``` **Metrics**: diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index c7d4be9..a8668d1 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -36,7 +36,7 @@ Scope boundary: - This repository separates contract definition from enforcement mechanics. - CI workflow rewiring and test execution redesign are out of scope for PP-58 - and belong to follow-up work under epic #66. + and belong to follow-up work under epic #66. ## Quick Start diff --git a/docs/GITEA_ACTIONS_TROUBLESHOOTING.md b/docs/GITEA_ACTIONS_TROUBLESHOOTING.md index 1bd77d9..54803c3 100644 --- a/docs/GITEA_ACTIONS_TROUBLESHOOTING.md +++ b/docs/GITEA_ACTIONS_TROUBLESHOOTING.md @@ -5,6 +5,7 @@ This document contains solutions to common issues with Gitea Actions CI/CD pipel ## Critical Issue: Jobs Stuck in "Waiting" State Forever ### Symptoms + - Workflows are created but jobs show "Waiting" indefinitely - Runners are online and healthy - No tasks appear in `action_task` database table @@ -12,9 +13,11 @@ This document contains solutions to common issues with Gitea Actions CI/CD pipel - UI shows "Waiting" but database shows status 5 (cancelled) ### Root Cause + **Docker syntax in `runs-on` labels** causes Gitea Actions to immediately cancel jobs. ### Problem Syntax (BROKEN) + ```yaml jobs: setup: @@ -26,6 +29,7 @@ jobs: ``` ### Solution Syntax (WORKING) + ```yaml jobs: setup: @@ -37,7 +41,9 @@ jobs: ``` ### Why This Works + The runners are configured with Docker images in their labels: + ```bash GITEA_RUNNER_LABELS=ubuntu-latest:docker://ubuntu:22.04,node-latest:docker://node:20-bookworm-slim,python-latest:docker://python:3.14-slim ``` @@ -47,11 +53,13 @@ So jobs still run in the correct Docker containers, but Gitea can properly parse ### Diagnosis Steps 1. **Check if new runs are created:** + ```sql SELECT id, status, title FROM action_run ORDER BY id DESC LIMIT 3; ``` 2. **Check job status and duration:** + ```sql SELECT arj.id, arj.job_id, arj.status, ar.created, ar.updated, (ar.updated - ar.created) as duration_seconds FROM action_run_job arj @@ -60,21 +68,25 @@ WHERE ar.id = (SELECT MAX(id) FROM action_run); ``` 3. **Check if tasks are created:** + ```sql SELECT * FROM action_task ORDER BY id DESC LIMIT 5; ``` 4. **Verify runners are online:** + ```sql SELECT id, name, last_online, agent_labels FROM action_runner WHERE last_online > (EXTRACT(epoch FROM NOW()) - 300)::bigint; ``` ### Key Indicators + - **Duration = 0 seconds** → Immediate cancellation due to syntax issue - **Empty action_task table** → Jobs never converted to executable tasks - **Status 5 jobs with Status 7 dependents** → Setup job cancelled, others skipped ### Test Procedure + Create a minimal test workflow to isolate issues: ```yaml @@ -95,13 +107,17 @@ If this works but your main workflow doesn't, the issue is likely syntax-related ## Other Common Issues ### Cache/UI Synchronization Problems + If UI shows different status than database: + 1. Restart Gitea: `docker compose restart server` 2. Clear browser cache 3. Check database vs UI status discrepancies ### Stuck Runs from Previous Sessions + Clean up stuck runs: + ```sql -- Clear stuck pending jobs UPDATE action_run_job SET status = 5 WHERE status IN (1, 2); @@ -109,7 +125,9 @@ UPDATE action_run SET status = 5 WHERE status IN (1, 2); ``` ### Runner Registration Issues + If runners show "unregistered runner" errors: + 1. Delete runner registrations: `DELETE FROM action_runner;` 2. Restart all runner containers 3. Let them auto-register with fresh state @@ -117,6 +135,7 @@ If runners show "unregistered runner" errors: ## Infrastructure Overview ### Current Setup + - **Gitea Server**: Docker container with PostgreSQL backend - **Runners**: 8 Raspberry Pi runners across 4 servers - pi-desktop: Pi 400 4GB (2 runners) @@ -125,16 +144,20 @@ If runners show "unregistered runner" errors: - zhokq: Pi 4B 8GB (2 runners) ### Runner Configuration + Each runner supports multiple Docker environments: + - `ubuntu-latest` → `ubuntu:22.04` - `python-latest` → `python:3.14-slim` - `node-latest` → `node:20-bookworm-slim` - `ubuntu-act` → `catthehacker/ubuntu:act-latest` ### Mirroring the `ubuntu-act` Runner Image + If GHCR pulls are flaky, mirror the runner image into your local registry and point the label at that mirror instead of the upstream tag. Example mirror flow: + ```bash docker pull ghcr.io/catthehacker/ubuntu:act-latest docker tag ghcr.io/catthehacker/ubuntu:act-latest kankali.darkhelm.lan:3001/darkhelm.org/act-ubuntu:act-latest @@ -142,6 +165,7 @@ docker push kankali.darkhelm.lan:3001/darkhelm.org/act-ubuntu:act-latest ``` Recommended runner label once mirrored: + ```bash GITEA_RUNNER_LABELS=ubuntu-latest:docker://ubuntu:22.04,node-latest:docker://node:20-bookworm-slim,python-latest:docker://python:3.14-slim,ubuntu-act:docker://kankali.darkhelm.lan:3001/darkhelm.org/act-ubuntu:act-latest ``` @@ -164,7 +188,9 @@ source scripts/gitea-actions/check_runner_images.xsh ``` ### Workflow Design + Multi-stage pipeline with artifact passing: + 1. **Setup**: Checkout code, create artifacts 2. **Parallel Setup**: Backend (Python/uv) + Frontend (Node.js/Yarn) 3. **Parallel Tests**: Backend tests + Frontend tests @@ -179,5 +205,5 @@ Multi-stage pipeline with artifact passing: --- -*Last updated: June 2, 2026* -*Issue resolved after extensive database-level debugging and syntax isolation* +_Last updated: June 2, 2026_ +_Issue resolved after extensive database-level debugging and syntax isolation_ diff --git a/docs/POE_TASK_REFERENCE.md b/docs/POE_TASK_REFERENCE.md index c7f64ef..394b759 100644 --- a/docs/POE_TASK_REFERENCE.md +++ b/docs/POE_TASK_REFERENCE.md @@ -18,6 +18,7 @@ poe ## 📋 Essential Tasks ### Development Environment + ```bash poe dev # Start development environment (Docker Compose) poe dev-down # Stop development environment @@ -26,6 +27,7 @@ poe dev-restart # Restart development environment ``` ### Code Quality (Unified Backend + Frontend) + ```bash poe format # Format all code (Python + TypeScript) poe lint # Lint all code (Python + TypeScript) @@ -33,6 +35,7 @@ poe type-check # Type check all code (Python + TypeScript) ``` ### Testing + ```bash poe test-unit # Run all unit tests (backend + frontend) poe test-all # Run all tests including integration @@ -41,6 +44,7 @@ poe test-e2e # Run end-to-end tests only ``` ### CI/CD Pipeline + ```bash poe ci-quick # Fast quality checks (format, lint, type-check) poe ci-full # Complete CI pipeline simulation @@ -48,6 +52,7 @@ poe quality-gate # All quality checks (like CI) ``` ### Docker Images + ```bash poe build-cicd # Build both base and complete CI/CD images poe build-cicd-base # Build only base image (cached dependencies) @@ -81,6 +86,7 @@ poe reset # Complete reset (clean + reinstall) ## 🔧 Individual Component Tasks ### Backend Only + ```bash poe format-backend # Format Python code only poe lint-backend # Lint Python code only @@ -90,6 +96,7 @@ poe test-backend-cov # Backend tests with coverage ``` ### Frontend Only + ```bash poe format-frontend # Format TypeScript code only poe lint-frontend # Lint TypeScript code only @@ -116,6 +123,7 @@ poe pre-commit-update # Update hook versions ## 💡 Tips & Tricks ### Task Discovery + ```bash poe --help # List all tasks with descriptions poe --help # Get help for specific task @@ -123,6 +131,7 @@ poe # Interactive task picker ``` ### Chaining Tasks + ```bash # Run multiple tasks in sequence poe format lint type-check test-unit @@ -132,12 +141,14 @@ poe clean deps-install ci-quick ``` ### Environment Context + - All tasks run from project root - Backend tasks automatically use `uv run` in correct environment - Frontend tasks automatically use `yarn` in correct directory - Docker tasks use the optimized multi-stage CI/CD setup ### Performance Tips + - Use parallel tasks (`*-parallel`) for faster feedback - Use conditional tasks (`*-if-changed`) to save time - `poe setup` configures everything for new developers @@ -146,6 +157,7 @@ poe clean deps-install ci-quick ## 🔄 Migration from Manual Commands ### Before (Manual) + ```bash cd backend && uv run ruff format . cd frontend && yarn prettier --write src/ @@ -155,6 +167,7 @@ cd frontend && yarn test ``` ### After (Poe) + ```bash poe format poe test-unit diff --git a/docs/RENOVATE_SETUP_GUIDE.md b/docs/RENOVATE_SETUP_GUIDE.md index cc44598..6915651 100644 --- a/docs/RENOVATE_SETUP_GUIDE.md +++ b/docs/RENOVATE_SETUP_GUIDE.md @@ -84,7 +84,7 @@ Add Renovate to your existing Gitea Actions workflow: name: Renovate on: schedule: - - cron: '0 8 * * 1' # Monday 8 AM + - cron: "0 8 * * 1" # Monday 8 AM workflow_dispatch: # Manual trigger jobs: @@ -163,6 +163,7 @@ Once active, Renovate will: ### 3. Integration with CI/CD Renovate PRs will trigger your existing CI/CD pipeline: + - Build and test in Docker containers - Run full quality gates (linting, type checking, tests) - Only merge if all checks pass @@ -172,6 +173,7 @@ Renovate PRs will trigger your existing CI/CD pipeline: ### Dashboard Renovate creates a "Dependency Dashboard" issue showing: + - Pending updates - Failed PRs - Ignored dependencies @@ -180,6 +182,7 @@ Renovate creates a "Dependency Dashboard" issue showing: ### Logs and Debugging For self-hosted setup: + ```bash # Run with debug logging docker run --rm \ @@ -204,6 +207,7 @@ docker run --rm \ ### Quick Validation For basic JSON validation without installing Renovate: + ```bash # Quick syntax check (no Renovate installation needed) ./scripts/quick-renovate-check.sh @@ -265,6 +269,7 @@ For basic JSON validation without installing Renovate: --- **Related Documentation**: + - [Renovate Official Docs](https://docs.renovatebot.com/) - [Configuration Options](https://docs.renovatebot.com/configuration-options/) - [Package Rules](https://docs.renovatebot.com/configuration-options/#packagerules) diff --git a/docs/adr/ADR001-deterministic_runtime_policy.md b/docs/adr/ADR001-deterministic_runtime_policy.md index 295f83c..309e4c4 100644 --- a/docs/adr/ADR001-deterministic_runtime_policy.md +++ b/docs/adr/ADR001-deterministic_runtime_policy.md @@ -10,6 +10,7 @@ Historically, floating dependency constraints and non-enforced runtime assumptio introduce drift and hard-to-diagnose failures. This branch introduced: + - Python 3.14 as the required runtime baseline - Exact dependency pinning for backend runtime and development tooling - Startup compatibility checks that fail fast when runtime policy is violated @@ -28,11 +29,13 @@ Adopt a deterministic backend runtime policy: ## Consequences Positive: + - Reduced environment drift across dev/CI/prod - Earlier and clearer failure mode for runtime mismatches - Improved reproducibility and troubleshooting Negative: + - More frequent explicit dependency maintenance updates - Stricter upgrade process for Python/runtime packages diff --git a/docs/adr/ADR002-cicd_base_image_tagging.md b/docs/adr/ADR002-cicd_base_image_tagging.md index dd9ebc6..9a50be7 100644 --- a/docs/adr/ADR002-cicd_base_image_tagging.md +++ b/docs/adr/ADR002-cicd_base_image_tagging.md @@ -27,13 +27,14 @@ Adopt a hardened split workflow for the CICD base image: 3. Treat the hash-specific tag as the source of truth for all CI consumers. 4. Keep `latest` only as a convenience tag for humans and manual debugging. 5. Make the main CI workflow consume only the immutable hash tag and fail clearly - if that base image has not been published yet. + if that base image has not been published yet. 6. Add bounded polling in main CI to tolerate short publish/consume races between - the dedicated base workflow and the main workflow. + the dedicated base workflow and the main workflow. ## Consequences Positive: + - Faster CI via stable base-layer reuse - Better traceability from base image to Dockerfile content - Single publish, many pulls across the runner fleet @@ -41,6 +42,7 @@ Positive: - Clearer separation of concerns between artifact publication and application CI Negative: + - Slightly more workflow complexity - Registry stores additional hash-tagged images - Main CI now fails fast when the expected base image is missing instead of diff --git a/frontend/env.d.ts b/frontend/env.d.ts index 193ddb8..a311d4f 100644 --- a/frontend/env.d.ts +++ b/frontend/env.d.ts @@ -1,24 +1,24 @@ /// declare module '*.vue' { - import type { DefineComponent } from 'vue' - const component: DefineComponent<{}, {}, any> - export default component + import type { DefineComponent } from 'vue'; + const component: DefineComponent<{}, {}, any>; + export default component; } // Vite environment variables interface ImportMetaEnv { - readonly VITE_API_URL?: string - readonly DEV: boolean - readonly PROD: boolean - readonly VITEST: boolean + readonly VITE_API_URL?: string; + readonly DEV: boolean; + readonly PROD: boolean; + readonly VITEST: boolean; } interface ImportMeta { - readonly env: ImportMetaEnv + readonly env: ImportMetaEnv; } // Global variables for automatic validation declare global { - var __AUTO_VALIDATE__: boolean | undefined + var __AUTO_VALIDATE__: boolean | undefined; } diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 75745fc..54cb60b 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,9 +1,9 @@ -import typescript from '@typescript-eslint/eslint-plugin' -import typescriptParser from '@typescript-eslint/parser' -import vue from 'eslint-plugin-vue' -import vueParser from 'vue-eslint-parser' -import jsdoc from 'eslint-plugin-jsdoc' -import tsdoc from 'eslint-plugin-tsdoc' +import typescript from '@typescript-eslint/eslint-plugin'; +import typescriptParser from '@typescript-eslint/parser'; +import vue from 'eslint-plugin-vue'; +import vueParser from 'vue-eslint-parser'; +import jsdoc from 'eslint-plugin-jsdoc'; +import tsdoc from 'eslint-plugin-tsdoc'; export default [ // Ignore patterns (replaces .eslintignore) @@ -18,8 +18,8 @@ export default [ '.vscode/', '.idea/', '*.tmp', - '*.temp' - ] + '*.temp', + ], }, // JavaScript files @@ -27,12 +27,12 @@ export default [ files: ['**/*.{js,mjs,cjs}'], languageOptions: { ecmaVersion: 'latest', - sourceType: 'module' + sourceType: 'module', }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' - } + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + }, }, // TypeScript files @@ -41,12 +41,12 @@ export default [ languageOptions: { parser: typescriptParser, ecmaVersion: 'latest', - sourceType: 'module' + sourceType: 'module', }, plugins: { '@typescript-eslint': typescript, jsdoc, - tsdoc + tsdoc, }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', @@ -61,8 +61,8 @@ export default [ 'jsdoc/check-param-names': 'error', 'jsdoc/check-tag-names': 'error', 'jsdoc/check-types': 'error', - 'jsdoc/valid-types': 'error' - } + 'jsdoc/valid-types': 'error', + }, }, // Vue files @@ -73,14 +73,14 @@ export default [ parserOptions: { parser: typescriptParser, ecmaVersion: 'latest', - sourceType: 'module' - } + sourceType: 'module', + }, }, plugins: { vue, '@typescript-eslint': typescript, jsdoc, - tsdoc + tsdoc, }, rules: { ...vue.configs['vue3-essential'].rules, @@ -96,7 +96,7 @@ export default [ 'jsdoc/check-param-names': 'error', 'jsdoc/check-tag-names': 'error', 'jsdoc/check-types': 'error', - 'jsdoc/valid-types': 'error' - } - } -] + 'jsdoc/valid-types': 'error', + }, + }, +]; diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index eed321b..8273d6b 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from '@playwright/test' +import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests/e2e', @@ -10,7 +10,9 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, - reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'playwright-results.xml' }]] : 'html', + reporter: process.env.CI + ? [['list'], ['junit', { outputFile: 'playwright-results.xml' }]] + : 'html', use: { baseURL: 'http://localhost:5173', trace: 'on-first-retry', @@ -30,54 +32,56 @@ export default defineConfig({ timeout: 60000, }), }, - projects: process.env.CI ? [ - // CI Environment: Only run Chromium for reliability in Docker - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - headless: true, - launchOptions: { - args: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--disable-dev-shm-usage', - '--disable-background-timer-throttling', - '--disable-backgrounding-occluded-windows', - '--disable-renderer-backgrounding', - // Network resilience args - '--disable-extensions', - '--disable-plugins', - '--disable-images', // Faster loading, reduce network load - '--aggressive-cache-discard', - // Increase network timeouts - '--network-quiet-timeout=10000', - '--disable-background-networking', - ] + projects: process.env.CI + ? [ + // CI Environment: Only run Chromium for reliability in Docker + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + headless: true, + launchOptions: { + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-background-timer-throttling', + '--disable-backgrounding-occluded-windows', + '--disable-renderer-backgrounding', + // Network resilience args + '--disable-extensions', + '--disable-plugins', + '--disable-images', // Faster loading, reduce network load + '--aggressive-cache-discard', + // Increase network timeouts + '--network-quiet-timeout=10000', + '--disable-background-networking', + ], + }, + }, }, - }, - } - ] : [ - // Local Development: Run all browsers - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - { - name: 'firefox', - use: { - ...devices['Desktop Firefox'], - }, - }, - { - name: 'webkit', - use: { - ...devices['Desktop Safari'], - }, - } - ], + ] + : [ + // Local Development: Run all browsers + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + { + name: 'firefox', + use: { + ...devices['Desktop Firefox'], + }, + }, + { + name: 'webkit', + use: { + ...devices['Desktop Safari'], + }, + }, + ], webServer: { command: 'yarn dev', url: 'http://localhost:5173', @@ -85,5 +89,5 @@ export default defineConfig({ timeout: process.env.CI ? 180 * 1000 : 120 * 1000, // Longer startup timeout in CI stderr: 'pipe', stdout: 'pipe', - } -}) + }, +}); diff --git a/frontend/tests/e2e/app.spec.ts b/frontend/tests/e2e/app.spec.ts index 7317a5c..5bad1ad 100644 --- a/frontend/tests/e2e/app.spec.ts +++ b/frontend/tests/e2e/app.spec.ts @@ -2,7 +2,7 @@ * End-to-end tests using Playwright */ -import { test, expect } from '@playwright/test' +import { test, expect } from '@playwright/test'; // Helper function for network-resilient page navigation async function navigateWithRetry(page: any, url: string, maxRetries = 3): Promise { @@ -10,54 +10,54 @@ async function navigateWithRetry(page: any, url: string, maxRetries = 3): Promis try { await page.goto(url, { waitUntil: 'networkidle', - timeout: process.env.CI ? 45000 : 30000 - }) - return // Success + timeout: process.env.CI ? 45000 : 30000, + }); + return; // Success } catch (error) { - if (i === maxRetries - 1) throw error // Last attempt failed - console.log(`Navigation attempt ${i + 1} failed, retrying...`) - await page.waitForTimeout(2000) // Wait before retry + if (i === maxRetries - 1) throw error; // Last attempt failed + console.log(`Navigation attempt ${i + 1} failed, retrying...`); + await page.waitForTimeout(2000); // Wait before retry } } } test.describe('Plex Playlist App', () => { test('should display app title', async ({ page }) => { - await navigateWithRetry(page, '/') + await navigateWithRetry(page, '/'); // Wait for the app to fully load with network resilience - await page.waitForSelector('h1', { timeout: 15000 }) - await expect(page.locator('h1')).toContainText('Plex Playlist') - }) + await page.waitForSelector('h1', { timeout: 15000 }); + await expect(page.locator('h1')).toContainText('Plex Playlist'); + }); test('should have welcome message', async ({ page }) => { - await navigateWithRetry(page, '/') + await navigateWithRetry(page, '/'); // Wait for the welcome message to appear with network resilience - await page.waitForSelector('p', { timeout: 15000 }) - await expect(page.locator('p')).toContainText('Welcome to the Plex Playlist Manager') - }) + await page.waitForSelector('p', { timeout: 15000 }); + await expect(page.locator('p')).toContainText('Welcome to the Plex Playlist Manager'); + }); test('should load without errors', async ({ page }) => { - const errors: string[] = [] + const errors: string[] = []; page.on('console', (msg) => { if (msg.type() === 'error') { // Filter out network-related errors that are acceptable in CI - const errorText = msg.text() + const errorText = msg.text(); if (!errorText.includes('net::') && !errorText.includes('Failed to fetch')) { - errors.push(errorText) + errors.push(errorText); } } - }) + }); - await navigateWithRetry(page, '/') + await navigateWithRetry(page, '/'); // Wait for app to fully load with extra time for network instability - await page.waitForLoadState('networkidle') + await page.waitForLoadState('networkidle'); // Give extra time for any async operations in unstable networks - await page.waitForTimeout(process.env.CI ? 3000 : 1000) + await page.waitForTimeout(process.env.CI ? 3000 : 1000); - expect(errors).toHaveLength(0) - }) -}) + expect(errors).toHaveLength(0); + }); +}); diff --git a/frontend/tests/unit/App.test.ts b/frontend/tests/unit/App.test.ts index 01e69f9..3d24242 100644 --- a/frontend/tests/unit/App.test.ts +++ b/frontend/tests/unit/App.test.ts @@ -1,10 +1,10 @@ -import { describe, it, expect } from 'vitest' -import { mount } from '@vue/test-utils' -import App from '@/App.vue' +import { describe, it, expect } from 'vitest'; +import { mount } from '@vue/test-utils'; +import App from '@/App.vue'; describe('App.vue', () => { it('renders properly', () => { - const wrapper = mount(App) - expect(wrapper.text()).toContain('Plex Playlist') - }) -}) + const wrapper = mount(App); + expect(wrapper.text()).toContain('Plex Playlist'); + }); +}); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 9cb450e..89649da 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,13 +1,7 @@ { "extends": "@vue/tsconfig/tsconfig.dom.json", - "include": [ - "env.d.ts", - "src/**/*", - "src/**/*.vue" - ], - "exclude": [ - "src/**/__tests__/*" - ], + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], "compilerOptions": { "composite": true, "baseUrl": ".", diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 918cc47..eff4894 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,16 +1,14 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' -import { fileURLToPath, URL } from 'node:url' +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import { fileURLToPath, URL } from 'node:url'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [ - vue() - ], + plugins: [vue()], resolve: { alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) - } + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, }, server: { host: '0.0.0.0', @@ -19,12 +17,12 @@ export default defineConfig({ '/api': { target: 'http://backend:8000', changeOrigin: true, - rewrite: (path: string) => path.replace(/^\/api/, '') - } - } + rewrite: (path: string) => path.replace(/^\/api/, ''), + }, + }, }, define: { // Enable automatic validation in development - __AUTO_VALIDATE__: JSON.stringify(process.env.NODE_ENV !== 'production') - } -}) + __AUTO_VALIDATE__: JSON.stringify(process.env.NODE_ENV !== 'production'), + }, +}); diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 23bbfa0..47786a8 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -1,18 +1,21 @@ -import { defineConfig } from 'vitest/config' -import vue from '@vitejs/plugin-vue' -import { fileURLToPath, URL } from 'node:url' +import { defineConfig } from 'vitest/config'; +import vue from '@vitejs/plugin-vue'; +import { fileURLToPath, URL } from 'node:url'; export default defineConfig({ plugins: [vue()], resolve: { alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) - } + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, }, test: { environment: 'jsdom', globals: true, - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', 'tests/unit/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + include: [ + 'src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + 'tests/unit/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + ], exclude: ['tests/e2e/**/*'], setupFiles: ['./src/test-setup.ts'], // Automatically install Zod validation hooks coverage: { @@ -25,16 +28,16 @@ export default defineConfig({ '**/*.d.ts', 'coverage/', 'tests/', - 'playwright.config.ts' + 'playwright.config.ts', ], thresholds: { global: { lines: 85, functions: 85, branches: 85, - statements: 85 - } - } - } - } -}) + statements: 85, + }, + }, + }, + }, +}); diff --git a/renovate.json b/renovate.json index a564e38..205c4d0 100644 --- a/renovate.json +++ b/renovate.json @@ -16,7 +16,13 @@ { "description": "Automerge non-major updates for high-confidence packages", "matchUpdateTypes": ["minor", "patch", "pin", "digest"], - "matchPackagePatterns": ["^@types/", "^eslint", "^prettier", "^ruff", "^pytest"], + "matchPackagePatterns": [ + "^@types/", + "^eslint", + "^prettier", + "^ruff", + "^pytest" + ], "automerge": true, "automergeType": "branch" }, @@ -32,7 +38,14 @@ "description": "Group Frontend dev tools updates", "matchManagers": ["npm"], "matchDepTypes": ["devDependencies"], - "matchPackagePatterns": ["^@typescript-eslint/", "^eslint", "^prettier", "^vite", "^vitest", "^playwright"], + "matchPackagePatterns": [ + "^@typescript-eslint/", + "^eslint", + "^prettier", + "^vite", + "^vitest", + "^playwright" + ], "groupName": "Frontend dev tools", "schedule": ["before 9am on monday"] },