ci: fix markdownlint/prettier checks and container pre-commit env
All checks were successful
CICD Start / Sanity and Base Decision (pull_request) Successful in 10s

This commit is contained in:
copilotcoder
2026-06-19 13:04:52 -04:00
parent 6f8dfea4cb
commit 654ba6ec25
20 changed files with 251 additions and 171 deletions

View File

@@ -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 &&

5
.markdownlint.yaml Normal file
View File

@@ -0,0 +1,5 @@
MD013: false
MD029: false
MD033: false
MD040: false
MD041: false

View File

@@ -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
```

View File

@@ -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

View File

@@ -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**:

View File

@@ -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

View File

@@ -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_

View File

@@ -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 <task> --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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

18
frontend/env.d.ts vendored
View File

@@ -1,24 +1,24 @@
/// <reference types="vite/client" />
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;
}

View File

@@ -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',
},
},
];

View File

@@ -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',
}
})
},
});

View File

@@ -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<void> {
@@ -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);
});
});

View File

@@ -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');
});
});

View File

@@ -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": ".",

View File

@@ -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'),
},
});

View File

@@ -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,
},
},
},
},
});

View File

@@ -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"]
},