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
All checks were successful
CICD Start / Sanity and Base Decision (pull_request) Successful in 10s
This commit is contained in:
@@ -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
5
.markdownlint.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
MD013: false
|
||||
MD029: false
|
||||
MD033: false
|
||||
MD040: false
|
||||
MD041: false
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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**:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
18
frontend/env.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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": ".",
|
||||
|
||||
@@ -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'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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"]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user