239 lines
11 KiB
YAML
239 lines
11 KiB
YAML
name: CI
|
|
# CI Wakdo - Forgejo Actions (runner stark-wakdo, label `docker`).
|
|
# Strategie solo dev : PR obligatoire + auto-merge sur CI verte (voir SECURITY.md).
|
|
#
|
|
# Etat des jobs selon la phase projet :
|
|
# - secret-scan : fonctionnel des maintenant (gitleaks scanne tout le depot)
|
|
# - php-lint : fonctionnel sur les fichiers PHP presents (stubs P1, code P2+)
|
|
# - static-tests: PHPStan + PHPUnit GARDES - s'activent quand P2 ajoute
|
|
# composer.json / phpstan.neon / tests + phpunit.xml
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [dev, main]
|
|
# `labeled` : permet au job auto-merge de s'evaluer quand on pose le label.
|
|
types: [opened, synchronize, reopened, labeled]
|
|
push:
|
|
# dev/main : porte de merge. feat|fix|ci|refactor : feedback avant la PR.
|
|
branches: [dev, main, 'feat/**', 'fix/**', 'ci/**', 'refactor/**']
|
|
|
|
jobs:
|
|
secret-scan:
|
|
runs-on: docker
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
- name: Install tools
|
|
run: |
|
|
apt-get update -qq
|
|
apt-get install -y -qq curl ca-certificates tar >/dev/null
|
|
- name: Install gitleaks
|
|
run: |
|
|
VER=8.21.2
|
|
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${VER}/gitleaks_${VER}_linux_x64.tar.gz" -o /tmp/gl.tgz
|
|
tar -xzf /tmp/gl.tgz -C /usr/local/bin gitleaks
|
|
gitleaks version
|
|
- name: Scan for secrets
|
|
run: gitleaks detect --config .gitleaks.toml --redact --no-banner --verbose
|
|
|
|
php-lint:
|
|
runs-on: docker
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Install PHP CLI
|
|
run: |
|
|
apt-get update -qq
|
|
apt-get install -y -qq php-cli >/dev/null
|
|
php --version
|
|
- name: Lint all PHP files
|
|
run: |
|
|
set -eu
|
|
files=$(find . -path ./node_modules -prune -o -name '*.php' -print)
|
|
if [ -z "$files" ]; then echo "No PHP files yet - skip"; exit 0; fi
|
|
echo "$files" | while IFS= read -r f; do
|
|
[ -z "$f" ] && continue
|
|
php -l "$f"
|
|
done
|
|
|
|
static-tests:
|
|
runs-on: docker
|
|
# COMPOSER-LESS (decision 4 / 5, PROJECT_CONTEXT.md) : PHPStan et PHPUnit
|
|
# tournent depuis leur .phar autonome telecharge ici, jamais via Composer.
|
|
# Versions epinglees pour des CI reproductibles (pas de "latest").
|
|
#
|
|
# Service MariaDB ephemere : le schema (db/migrations) et le seed (db/seeds)
|
|
# y sont appliques, puis PHPUnit tourne avec WAKDO_DB_TESTS=1 pour que les
|
|
# tests d'integration (tests/Integration/*DbTest) s'executent REELLEMENT.
|
|
# Sans base, ils s'auto-skippent et le SQL porteur de securite (throttle,
|
|
# RBAC is_active, audit in-transaction, FK) n'est jamais valide en CI.
|
|
# Identifiants ci-dessous : ephemeres, CI uniquement, jamais des secrets.
|
|
services:
|
|
mariadb:
|
|
image: mariadb:11.4
|
|
env:
|
|
MARIADB_ROOT_PASSWORD: root
|
|
MARIADB_DATABASE: wakdo_test
|
|
MARIADB_USER: wakdo
|
|
MARIADB_PASSWORD: wakdo
|
|
env:
|
|
PHPUNIT_VERSION: "11.5.2"
|
|
PHPSTAN_VERSION: "1.12.27"
|
|
# Connexion des tests d'integration au service `mariadb` ci-dessus
|
|
# (Database lit ces DB_* via getenv ; cf. src/app/Core/Database.php).
|
|
WAKDO_DB_TESTS: "1"
|
|
DB_HOST: mariadb
|
|
DB_PORT: "3306"
|
|
DB_NAME: wakdo_test
|
|
DB_USER: wakdo
|
|
DB_PASSWORD: wakdo
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: PHPStan (guarded)
|
|
run: |
|
|
set -eu
|
|
if [ ! -f phpstan.neon ]; then
|
|
echo "PHPStan skipped: no phpstan.neon yet (activates in P2)"
|
|
exit 0
|
|
fi
|
|
echo "phpstan.neon detected - running PHPStan ${PHPSTAN_VERSION} via .phar"
|
|
apt-get update -qq && apt-get install -y -qq php-cli php-xml php-mbstring curl ca-certificates >/dev/null
|
|
# PHPUnit phar present pour que phpstan.neon (scanDirectories phar://phpunit.phar)
|
|
# resolve les symboles PHPUnit\Framework\* utilises sous tests/.
|
|
curl -sSL "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" -o phpunit.phar
|
|
curl -sSL "https://github.com/phpstan/phpstan/releases/download/${PHPSTAN_VERSION}/phpstan.phar" -o phpstan.phar
|
|
php phpstan.phar --version
|
|
# memory_limit=-1 : l'analyse parallele depasse les 128M par defaut du php-cli.
|
|
php -d memory_limit=-1 phpstan.phar analyse --no-progress --error-format=raw
|
|
- name: PHPUnit (guarded, avec tests d'integration DB)
|
|
run: |
|
|
set -eu
|
|
if [ ! -d tests ] || [ ! -f phpunit.xml ]; then
|
|
echo "PHPUnit skipped: no tests/ + phpunit.xml yet (activates in P2)"
|
|
exit 0
|
|
fi
|
|
echo "phpunit.xml + tests/ detected - running PHPUnit ${PHPUNIT_VERSION} via .phar"
|
|
# php-mysql = pilote pdo_mysql requis par les *DbTest ; mariadb-client
|
|
# pour appliquer schema + seed au service mariadb.
|
|
apt-get update -qq && apt-get install -y -qq php-cli php-xml php-mbstring php-mysql mariadb-client curl ca-certificates >/dev/null
|
|
# Attente active que le service MariaDB reponde (en plus du lien de service).
|
|
echo "Attente du service MariaDB ${DB_HOST}:${DB_PORT} ..."
|
|
ready=0
|
|
for i in $(seq 1 30); do
|
|
if mariadb -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASSWORD}" -e "SELECT 1" "${DB_NAME}" >/dev/null 2>&1; then
|
|
echo "MariaDB pret (tentative ${i})."; ready=1; break
|
|
fi
|
|
sleep 2
|
|
done
|
|
[ "${ready}" = 1 ] || { echo "ERREUR: MariaDB injoignable apres 60s"; exit 1; }
|
|
# Schema (db/migrations) puis seed (db/seeds), ordre lexicographique.
|
|
for f in db/migrations/*.sql; do
|
|
echo "migrate $(basename "$f")"
|
|
mariadb -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASSWORD}" "${DB_NAME}" < "$f"
|
|
done
|
|
for f in db/seeds/*.sql; do
|
|
echo "seed $(basename "$f")"
|
|
mariadb -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASSWORD}" "${DB_NAME}" < "$f"
|
|
done
|
|
curl -sSL "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" -o phpunit.phar
|
|
php phpunit.phar --version
|
|
# --fail-on-skipped : si un *DbTest s'auto-skippe (base injoignable), la
|
|
# CI echoue au lieu de masquer le trou derriere un vert. C'est le coeur
|
|
# du correctif : plus aucun skip silencieux des chemins securite.
|
|
php phpunit.phar -c phpunit.xml --fail-on-skipped
|
|
|
|
js-tests:
|
|
# Tests du front borne (kiosk) : node:test + jsdom, sans navigateur.
|
|
# GARDE : ne s'active que si package.json + tests/js/ existent.
|
|
runs-on: docker
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Install Node.js 20
|
|
run: |
|
|
set -eu
|
|
# Node 20 epingle via NodeSource (self-contained, comme les .phar/gitleaks)
|
|
# plutot que l'apt bookworm (18.x, limite basse pour jsdom). Reproductible.
|
|
apt-get update -qq && apt-get install -y -qq curl ca-certificates >/dev/null
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - >/dev/null
|
|
apt-get install -y -qq nodejs >/dev/null
|
|
node --version && npm --version
|
|
- name: Install deps + run kiosk JS tests
|
|
run: |
|
|
set -eu
|
|
if [ ! -f package.json ] || [ ! -d tests/js ]; then
|
|
echo "JS tests skipped: no package.json + tests/js/ yet"
|
|
exit 0
|
|
fi
|
|
# Skip le download des browsers Playwright : ce job ne fait que node:test+jsdom.
|
|
# (@playwright/test est en devDep pour l'E2E, mais ses browsers ne servent
|
|
# qu'a tests/e2e via le conteneur officiel.)
|
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm ci
|
|
npm run test:js
|
|
|
|
e2e:
|
|
# Tests navigateur Playwright (parcours borne + admin) sur une pile JETABLE.
|
|
# tests/e2e/run.sh monte la pile via docker compose et joue les specs (inchange).
|
|
# Specificite CI : act_runner range le workspace du job dans un volume dont le chemin
|
|
# n'est pas valide cote demon hote, donc les bind-mounts ./src ./db du compose ne s'y
|
|
# resolvent pas. Parade : copier le repo dans un volume nomme dont le mountpoint EST un
|
|
# chemin hote valide, puis lancer run.sh depuis un wrapper monte sur ce mountpoint.
|
|
# La socket docker est propagee au job (verifie) ; code applicatif et Dockerfiles intacts.
|
|
runs-on: docker
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Playwright (pile jetable, parcours borne + admin)
|
|
run: |
|
|
set -e
|
|
curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz -o /tmp/d.tgz
|
|
tar -xzf /tmp/d.tgz -C /usr/local/bin --strip-components=1 docker/docker
|
|
VOL="wakdo_e2e_${GITHUB_SHA:-run}"
|
|
docker volume rm "$VOL" >/dev/null 2>&1 || true
|
|
docker volume create "$VOL" >/dev/null
|
|
tar -C "$GITHUB_WORKSPACE" \
|
|
--exclude=./.git --exclude=./node_modules --exclude=./var/backups \
|
|
--exclude=./playwright-report --exclude=./test-results \
|
|
-cf - . | docker run --rm -i -v "$VOL":/dst alpine sh -c 'cd /dst && tar xf -'
|
|
HOSTSRC="$(docker volume inspect "$VOL" -f '{{.Mountpoint}}')"
|
|
echo "workspace -> volume $VOL @ $HOSTSRC"
|
|
set +e
|
|
docker run --rm \
|
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
-v "$VOL":"$HOSTSRC" \
|
|
-w "$HOSTSRC" \
|
|
docker:27-cli sh -c 'apk add --no-cache bash perl >/dev/null 2>&1 && bash tests/e2e/run.sh'
|
|
RC=$?
|
|
docker volume rm "$VOL" >/dev/null 2>&1 || true
|
|
exit $RC
|
|
|
|
auto-merge:
|
|
# Fusion automatique OPT-IN : poser le label `auto-merge` sur la PR.
|
|
# Ne s'execute que si tous les checks requis passent (needs).
|
|
# IMPORTANT : le filtrage par label se fait DANS le step via l'API, pas dans
|
|
# `if:` — l'expression contains(github.event.pull_request.labels.*.name, ...)
|
|
# de Forgejo n'est pas fiable (elle s'evalue a vrai meme sans label, ce qui
|
|
# fusionnait toute PR verte). La verification shell sur l'API est le vrai gate.
|
|
needs: [secret-scan, php-lint, static-tests, js-tests, e2e]
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: docker
|
|
steps:
|
|
- name: Install curl
|
|
run: apt-get update -qq && apt-get install -y -qq curl ca-certificates >/dev/null
|
|
- name: Merge PR (squash) si label auto-merge present et CI verte
|
|
run: |
|
|
API="${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
|
PR="${{ github.event.pull_request.number }}"
|
|
TOKEN="${{ secrets.FORGEJO_TOKEN }}"
|
|
labels=$(curl -s -H "Authorization: token $TOKEN" "$API/issues/$PR/labels")
|
|
if ! printf '%s' "$labels" | grep -q '"name"[[:space:]]*:[[:space:]]*"auto-merge"'; then
|
|
echo "Pas de label 'auto-merge' sur la PR #$PR -> relecture manuelle, pas de fusion auto."
|
|
exit 0
|
|
fi
|
|
echo "Label 'auto-merge' present + CI verte -> fusion de la PR #$PR"
|
|
code=$(curl -s -o /tmp/resp -w "%{http_code}" -X POST \
|
|
-H "Authorization: token $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"Do":"squash","delete_branch_after_merge":true}' \
|
|
"$API/pulls/$PR/merge")
|
|
echo "merge HTTP $code"; cat /tmp/resp || true; echo
|
|
[ "$code" = "200" ] || { echo "auto-merge failed (HTTP $code)"; exit 1; }
|
|
echo "PR #$PR mergee."
|