rename cts-work to pp-planer, move Dockerfile to build/, optimize dev scripts

- Rename all cts-work references to pp-planer (valet, sanctum, playwright, e2e, docs)
- Fix docker-compose build context to use project root with build/Dockerfile
- Add .dockerignore to exclude unnecessary files from Docker build
- start_dev.sh: stale PID cleanup, dependency checks, APP_KEY check, process health verification
- stop_dev.sh: fix set -e crash on arithmetic, report already-dead processes, idempotent exit
This commit is contained in:
Thorsten Bus 2026-03-30 16:00:02 +02:00
parent af9b8d1882
commit 1eb4f1642f
10 changed files with 101 additions and 36 deletions

10
.dockerignore Normal file
View file

@ -0,0 +1,10 @@
node_modules
vendor
.git
.env
storage/logs/*
storage/framework/cache/*
storage/framework/views/*
test-results
tests/e2e/.auth
.dev.pid

View file

@ -104,14 +104,14 @@ ## Repository Structure
| Repo | Path | Branch | Purpose | | Repo | Path | Branch | Purpose |
|------|------|--------|---------| |------|------|--------|---------|
| **cts-work** | `/Users/thorsten/AI/cts-work` | `cts-presenter-app` | Laravel app (main codebase) | | **pp-planer** | `/Users/thorsten/AI/pp-planer` | `cts-presenter-app` | Laravel app (main codebase) |
| **propresenter-work** | `/Users/thorsten/AI/propresenter-work/php` | `propresenter-parser` | ProPresenter .pro/.proplaylist parser (composer path dependency) | | **propresenter-work** | `/Users/thorsten/AI/propresenter-work/php` | `propresenter-parser` | ProPresenter .pro/.proplaylist parser (composer path dependency) |
The parser is linked via `composer.json` path repository: `"url": "../propresenter-work/php"`. The parser is linked via `composer.json` path repository: `"url": "../propresenter-work/php"`.
## Build, Test, Lint Commands ## Build, Test, Lint Commands
### cts-work (Laravel App) ### pp-planer (Laravel App)
```bash ```bash
# First-time setup # First-time setup
@ -141,7 +141,7 @@ # PHP formatting (Laravel Pint, default preset — no pint.json)
./vendor/bin/pint ./vendor/bin/pint
./vendor/bin/pint --test # check only ./vendor/bin/pint --test # check only
# E2E tests (requires dev server at http://cts-work.test) # E2E tests (requires dev server at http://pp-planer.test)
npx playwright test npx playwright test
npx playwright test tests/e2e/service-list.spec.ts npx playwright test tests/e2e/service-list.spec.ts
@ -164,7 +164,7 @@ # Single test file
## Architecture ## Architecture
``` ```
cts-work/ pp-planer/
app/Http/Controllers/ # Inertia controllers (Inertia::render or JSON) app/Http/Controllers/ # Inertia controllers (Inertia::render or JSON)
app/Models/ # Eloquent models (factories in database/factories/) app/Models/ # Eloquent models (factories in database/factories/)
app/Services/ # Business logic (ChurchToolsService, ProExportService, etc.) app/Services/ # Business logic (ChurchToolsService, ProExportService, etc.)
@ -241,7 +241,7 @@ ### ProPresenter Parser Tests (PHPUnit 11)
### E2E Tests (Playwright) ### E2E Tests (Playwright)
- TypeScript in `tests/e2e/`, baseURL `http://cts-work.test` - TypeScript in `tests/e2e/`, baseURL `http://pp-planer.test`
- Auth via `auth.setup.ts` (XSRF token + `/dev-login` endpoint, saves state to `.auth/user.json`) - Auth via `auth.setup.ts` (XSRF token + `/dev-login` endpoint, saves state to `.auth/user.json`)
- Selectors: `page.getByTestId('...')`, `page.getByText('...')`, `page.getByRole('...')` - Selectors: `page.getByTestId('...')`, `page.getByText('...')`, `page.getByRole('...')`
- German text assertions: `await expect(page.getByText('Mit ChurchTools anmelden')).toBeVisible()` - German text assertions: `await expect(page.getByText('Mit ChurchTools anmelden')).toBeVisible()`

View file

@ -1,5 +1,5 @@
<?php <?php
return [ return [
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,localhost:8000,127.0.0.1,127.0.0.1:8000,::1,cts-work.test')), 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,localhost:8000,127.0.0.1,127.0.0.1:8000,::1,pp-planer.test')),
]; ];

View file

@ -1,11 +1,11 @@
version: '3.8' version: "3.8"
services: services:
app: app:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: build/Dockerfile
container_name: cts-presenter-app container_name: pp-planer-app
restart: unless-stopped restart: unless-stopped
working_dir: /app working_dir: /app
environment: environment:
@ -27,13 +27,13 @@ services:
ports: ports:
- "8000:8000" - "8000:8000"
networks: networks:
- cts-network - pp-planer-network
depends_on: depends_on:
- node - node
node: node:
image: node:20-alpine image: node:20-alpine
container_name: cts-presenter-node container_name: pp-planer-node
restart: unless-stopped restart: unless-stopped
working_dir: /app working_dir: /app
environment: environment:
@ -44,14 +44,14 @@ services:
ports: ports:
- "5173:5173" - "5173:5173"
networks: networks:
- cts-network - pp-planer-network
command: npm run dev command: npm run dev
# Optional: SQLite database service (for reference, SQLite runs in-process) # Optional: SQLite database service (for reference, SQLite runs in-process)
# For MySQL, uncomment and configure: # For MySQL, uncomment and configure:
# mysql: # mysql:
# image: mysql:8.0 # image: mysql:8.0
# container_name: cts-presenter-mysql # container_name: pp-planer-mysql
# restart: unless-stopped # restart: unless-stopped
# environment: # environment:
# MYSQL_DATABASE: ${DB_DATABASE} # MYSQL_DATABASE: ${DB_DATABASE}
@ -63,12 +63,11 @@ services:
# ports: # ports:
# - "3306:3306" # - "3306:3306"
# networks: # networks:
# - cts-network # - pp-planer-network
networks: networks:
cts-network: pp-planer-network:
driver: bridge driver: bridge
# volumes: # volumes:
# mysql_data: # mysql_data:
# driver: local # driver: local

2
package-lock.json generated
View file

@ -1,5 +1,5 @@
{ {
"name": "cts-work", "name": "pp-planer",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {

View file

@ -9,7 +9,7 @@ export default defineConfig({
timeout: 5000, timeout: 5000,
}, },
use: { use: {
baseURL: 'http://cts-work.test', baseURL: 'http://pp-planer.test',
trace: 'on-first-retry', trace: 'on-first-retry',
}, },
projects: [ projects: [

View file

@ -3,6 +3,7 @@ set -euo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
PID_FILE="$PROJECT_DIR/.dev.pid" PID_FILE="$PROJECT_DIR/.dev.pid"
SITE_NAME="pp-planer"
cd "$PROJECT_DIR" cd "$PROJECT_DIR"
GREEN='\033[0;32m' GREEN='\033[0;32m'
@ -11,21 +12,55 @@ RED='\033[0;31m'
CYAN='\033[0;36m' CYAN='\033[0;36m'
NC='\033[0m' NC='\033[0m'
# ── Stale PID cleanup ──────────────────────────────────────
if [ -f "$PID_FILE" ]; then if [ -f "$PID_FILE" ]; then
echo -e "${RED}Dev-Umgebung läuft bereits.${NC}" ALL_DEAD=true
echo -e "Stoppe zuerst mit: ${CYAN}./stop_dev.sh${NC}" while read -r PID; do
exit 1 if kill -0 "$PID" 2>/dev/null; then
ALL_DEAD=false
break
fi
done < <(tr ' ' '\n' < "$PID_FILE")
if [ "$ALL_DEAD" = true ]; then
echo -e "${YELLOW}▸ Verwaiste PID-Datei entfernt${NC}"
rm -f "$PID_FILE"
else
echo -e "${RED}Dev-Umgebung läuft bereits.${NC}"
echo -e "Stoppe zuerst mit: ${CYAN}./stop_dev.sh${NC}"
exit 1
fi
fi fi
# ── Valet link ────────────────────────────────────────────── # ── Valet link ──────────────────────────────────────────────
echo -e "${YELLOW}▸ Valet-Link prüfen …${NC}" echo -e "${YELLOW}▸ Valet-Link prüfen …${NC}"
if [ ! -L "$HOME/.config/valet/Sites/cts-work" ]; then if [ ! -L "$HOME/.config/valet/Sites/$SITE_NAME" ]; then
valet link cts-work 2>/dev/null valet link "$SITE_NAME" 2>/dev/null
echo -e " ${GREEN}${NC} Valet-Link erstellt: cts-work.test" echo -e " ${GREEN}${NC} Valet-Link erstellt: ${SITE_NAME}.test"
else else
echo -e " ${GREEN}${NC} Valet-Link vorhanden" echo -e " ${GREEN}${NC} Valet-Link vorhanden"
fi fi
# ── Dependencies check ──────────────────────────────────────
if [ ! -d "node_modules" ]; then
echo -e "${YELLOW}▸ npm install …${NC}"
npm install --silent
echo -e " ${GREEN}${NC} Node-Module installiert"
fi
if [ ! -d "vendor" ]; then
echo -e "${YELLOW}▸ composer install …${NC}"
composer install --quiet
echo -e " ${GREEN}${NC} Composer-Pakete installiert"
fi
# ── APP_KEY check ───────────────────────────────────────────
if ! grep -q '^APP_KEY=base64:' .env 2>/dev/null; then
echo -e "${YELLOW}▸ APP_KEY generieren …${NC}"
php artisan key:generate --quiet
echo -e " ${GREEN}${NC} APP_KEY generiert"
fi
# ── Migrate ───────────────────────────────────────────────── # ── Migrate ─────────────────────────────────────────────────
echo -e "${YELLOW}▸ Migrations ausführen …${NC}" echo -e "${YELLOW}▸ Migrations ausführen …${NC}"
php artisan migrate --force --quiet php artisan migrate --force --quiet
@ -35,13 +70,26 @@ echo -e " ${GREEN}✓${NC} Datenbank aktuell"
echo -e "${YELLOW}▸ Queue-Worker starten …${NC}" echo -e "${YELLOW}▸ Queue-Worker starten …${NC}"
php artisan queue:listen --tries=1 --timeout=0 > /dev/null 2>&1 & php artisan queue:listen --tries=1 --timeout=0 > /dev/null 2>&1 &
QUEUE_PID=$! QUEUE_PID=$!
echo -e " ${GREEN}${NC} Queue-Worker (PID $QUEUE_PID)" sleep 0.3
if kill -0 "$QUEUE_PID" 2>/dev/null; then
echo -e " ${GREEN}${NC} Queue-Worker (PID $QUEUE_PID)"
else
echo -e " ${RED}${NC} Queue-Worker konnte nicht gestartet werden"
exit 1
fi
# ── Vite dev server ───────────────────────────────────────── # ── Vite dev server ─────────────────────────────────────────
echo -e "${YELLOW}▸ Vite starten …${NC}" echo -e "${YELLOW}▸ Vite starten …${NC}"
npm run dev > /dev/null 2>&1 & npx --yes vite --port 5173 > /dev/null 2>&1 &
VITE_PID=$! VITE_PID=$!
echo -e " ${GREEN}${NC} Vite (PID $VITE_PID)" sleep 0.5
if kill -0 "$VITE_PID" 2>/dev/null; then
echo -e " ${GREEN}${NC} Vite (PID $VITE_PID)"
else
echo -e " ${RED}${NC} Vite konnte nicht gestartet werden"
kill "$QUEUE_PID" 2>/dev/null || true
exit 1
fi
# ── Save PIDs ─────────────────────────────────────────────── # ── Save PIDs ───────────────────────────────────────────────
echo "$QUEUE_PID $VITE_PID" > "$PID_FILE" echo "$QUEUE_PID $VITE_PID" > "$PID_FILE"
@ -51,7 +99,7 @@ echo -e "${GREEN}═════════════════════
echo -e "${GREEN} Dev-Umgebung läuft!${NC}" echo -e "${GREEN} Dev-Umgebung läuft!${NC}"
echo -e "${GREEN}═══════════════════════════════════════════════${NC}" echo -e "${GREEN}═══════════════════════════════════════════════${NC}"
echo "" echo ""
echo -e " App: ${CYAN}http://cts-work.test${NC}" echo -e " App: ${CYAN}http://${SITE_NAME}.test${NC}"
echo -e " Vite: ${CYAN}http://localhost:5173${NC}" echo -e " Vite: ${CYAN}http://localhost:5173${NC}"
echo "" echo ""
echo -e " Stop: ${YELLOW}./stop_dev.sh${NC}" echo -e " Stop: ${YELLOW}./stop_dev.sh${NC}"

View file

@ -3,7 +3,6 @@ set -euo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
PID_FILE="$PROJECT_DIR/.dev.pid" PID_FILE="$PROJECT_DIR/.dev.pid"
cd "$PROJECT_DIR"
GREEN='\033[0;32m' GREEN='\033[0;32m'
RED='\033[0;31m' RED='\033[0;31m'
@ -11,18 +10,27 @@ YELLOW='\033[1;33m'
NC='\033[0m' NC='\033[0m'
if [ ! -f "$PID_FILE" ]; then if [ ! -f "$PID_FILE" ]; then
echo -e "${RED}Dev-Umgebung läuft nicht.${NC}" echo -e "${RED}Dev-Umgebung läuft nicht (keine PID-Datei).${NC}"
exit 1 exit 0
fi fi
STOPPED=0
ALREADY_DEAD=0
while read -r PID; do while read -r PID; do
if kill "$PID" 2>/dev/null; then if kill "$PID" 2>/dev/null; then
STOPPED=$((STOPPED + 1))
echo -e " ${YELLOW}${NC} Prozess $PID gestoppt" echo -e " ${YELLOW}${NC} Prozess $PID gestoppt"
else
ALREADY_DEAD=$((ALREADY_DEAD + 1))
fi fi
done < <(tr ' ' '\n' < "$PID_FILE") done < <(tr ' ' '\n' < "$PID_FILE")
rm -f "$PID_FILE" rm -f "$PID_FILE"
echo "" echo ""
echo -e "${GREEN}Dev-Umgebung gestoppt.${NC}" if [ "$ALREADY_DEAD" -gt 0 ]; then
echo -e "${YELLOW} $ALREADY_DEAD Prozess(e) waren bereits beendet.${NC}"
fi
echo -e "${GREEN}Dev-Umgebung gestoppt. ($STOPPED Prozess(e) beendet)${NC}"
echo "" echo ""

View file

@ -4,22 +4,22 @@ const authFile = 'tests/e2e/.auth/user.json';
setup('authenticate', async ({ page }) => { setup('authenticate', async ({ page }) => {
// Navigate to login page to establish session cookies (incl. XSRF-TOKEN) // Navigate to login page to establish session cookies (incl. XSRF-TOKEN)
await page.goto('http://cts-work.test/login'); await page.goto('http://pp-planer.test/login');
// Get XSRF token from cookies for CSRF protection // Get XSRF token from cookies for CSRF protection
const cookies = await page.context().cookies('http://cts-work.test'); const cookies = await page.context().cookies('http://pp-planer.test');
const xsrfCookie = cookies.find((c) => c.name === 'XSRF-TOKEN'); const xsrfCookie = cookies.find((c) => c.name === 'XSRF-TOKEN');
const xsrfToken = decodeURIComponent(xsrfCookie?.value || ''); const xsrfToken = decodeURIComponent(xsrfCookie?.value || '');
// POST to dev-login route directly (bypasses Vue rendering dependency) // POST to dev-login route directly (bypasses Vue rendering dependency)
await page.request.post('http://cts-work.test/dev-login', { await page.request.post('http://pp-planer.test/dev-login', {
headers: { headers: {
'X-XSRF-TOKEN': xsrfToken, 'X-XSRF-TOKEN': xsrfToken,
}, },
}); });
// Navigate to dashboard to confirm login and load session // Navigate to dashboard to confirm login and load session
await page.goto('http://cts-work.test/dashboard'); await page.goto('http://pp-planer.test/dashboard');
await page.waitForURL('**/dashboard'); await page.waitForURL('**/dashboard');
// Save signed-in state // Save signed-in state