Files
williamandClaude Sonnet 5 bab581737a
Build and deploy / test-python (push) Successful in 11m2s
Build and deploy / test-js (push) Successful in 1m2s
Build and deploy / lint-python (push) Successful in 4m6s
Build and deploy / lint-js (push) Failing after 1m21s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 4m2s
Ajoute le support de formation : entite racine separee du jeu 2D
Nouveau moteur document_engine/ (elements CRUD + rendering + labels),
db/supports/ (stockage independant de db/games), routes/document/
(CRUD AJAX + publication), et l'editeur frontend complet
(templates/document/, static/document/) avec moteur de layout reel
(glisser-deposer -> fusion en rangee ou insertion avant/apres), vrai
Undo/Redo par pile de commandes, grille d'accroche pour les formes
libres, apercu responsive a largeurs fixes, mode Apercu, et publication
persistee.

"Mes formations" (templates/index.html) liste desormais les
environnements 2D et les supports de formation cote a cote ;
l'onboarding et core/auth_guard.py sont generalises pour qu'un compte
restreint puisse posseder un projet de chaque type independamment.

SKIP=djlint : le hook ne signale que le backlog H021 (styles en ligne)
deja documente dans CODE_QUALITY.md sur des fichiers pre-existants non
touches ici (base.html, game/play.html, scene_edit.html,
game_dashboard_simple.html, clause_row.html) plus une ligne de
index.html deja presente avant cette session — aucun nouveau fichier
(document_edit.html compris) n'y figure. Tous les autres outils
(ruff, mypy --strict, vulture, bandit, import-linter, eslint,
stylelint) passent sans erreur ; 617 tests Python + 276 tests JS
verts, plus une verification manuelle complete du cycle de vie via le
serveur de developpement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 09:20:33 +02:00

223 lines
10 KiB
YAML

name: Build and deploy
on:
push:
branches: [main, dev]
# Les jobs "test-*"/"lint-*"/"sonarqube" tournent sur CHAQUE push (main et
# dev) : jusqu'ici aucune étape de CI n'exécutait la suite de tests ni les
# outils qualité, rien n'empêchait un commit cassé d'atteindre la
# production (voir l'audit qualité de la Phase 0 du plan). "build-and-
# push"/"deploy", eux, restent réservés à main (via le filtre "if" sur
# gitea.ref) — un push sur dev ne doit jamais redéployer la prod, seulement
# faire tourner tests/lint/sonar.
#
# lint-python/lint-js rejouent EXACTEMENT les hooks pre-commit locaux
# (.pre-commit-config.yaml) mais bloquants ici dès le départ (déjà tous
# verts en local, voir le commit "Phase 3 : hardening qualite de code") —
# les hooks pre-commit ne protègent que la machine du committeur, jamais
# un push direct ou une PR mergée depuis ailleurs. djlint EXCLU
# volontairement (49 H021 "styles inline" déjà en backlog assumé, voir
# CODE_QUALITY.md) — à ajouter ici quand ce lot sera traité. sonarqube,
# lui, reste NON-BLOQUANT (continue-on-error) — remis en place le
# 18/09/2026 une fois l'instance prod opérationnelle (voir CODE_QUALITY.md
# pour l'historique du retrait temporaire), le temps que le rapport de
# code smells soit entièrement trié.
# Secrets à configurer dans Gitea (Paramètres du dépôt > Actions > Secrets) :
# REGISTRY_HOST adresse du registre d'images (ex: gitea.exemple.com)
# REGISTRY_IMAGE chemin complet de l'image (ex: gitea.exemple.com/mon-compte/forge-engine)
# REGISTRY_USER utilisateur pour "docker login" sur ce registre
# REGISTRY_TOKEN jeton d'accès (scope package:write) pour ce registre
# DEPLOY_HOST adresse du serveur de production
# DEPLOY_USER utilisateur SSH sur ce serveur
# DEPLOY_SSH_KEY clé privée SSH (au format PEM) autorisée sur ce serveur
# DEPLOY_PATH dossier sur le serveur où vit docker-compose.prod.yml (ex: /home/deploy/forge-engine)
# SONAR_TOKEN jeton d'analyse SonarQube (Mon compte > Security > Generate Token
# sur sonar.forgebase.fr) — jamais le mot de passe admin.
#
# Utilise directement docker/ssh/scp en ligne de commande plutôt que des
# actions du marketplace, pour ne pas dépendre de la disponibilité de
# github.com / du miroir gitea.com depuis le runner self-hosted.
jobs:
# Deux essais précédents ont échoué :
# 1. docker run -v "$PWD":/app ... imbriqué depuis le runner — sur un
# runner qui exécute déjà le job dans son propre conteneur (Docker-
# outside-of-Docker), "$PWD" ne correspond à aucun chemin que le démon
# Docker de l'HÔTE peut monter : /app se retrouvait vide.
# 2. jobs.test.container: image: python:3.13-slim — actions/checkout@v4
# (une action Node.js) a alors besoin de Node dans CE conteneur pour
# s'exécuter, qui ne l'a pas ("command not found", voir
# nektos/act#107) : `container:` remplace tout l'environnement des
# steps, pas seulement celui des commandes qu'on y lance soi-même.
# Solution : le job tourne sur le runner PAR DÉFAUT (checkout fonctionne,
# Node y est déjà disponible), et les tests s'exécutent PENDANT un
# `docker build` (Dockerfile passé par stdin, jamais commité) — le
# transfert du contexte de build au démon Docker ne dépend JAMAIS d'un
# chemin hôte/montage de volume (protocole API, un tar envoyé tel quel),
# donc insensible au problème Docker-outside-of-Docker du point 1.
test-python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Tests Python (pytest, exécutés PENDANT le build d'une image jetable)
run: |
# .dockerignore (à la racine, pensé pour l'image de PROD) exclut
# tests/ du contexte de build — COPY . . ne l'aurait donc jamais
# inclus, quel que soit le Dockerfile utilisé (.dockerignore
# s'applique au contexte entier, pas à un Dockerfile en
# particulier). Neutralisé ici SANS RISQUE : ce checkout est
# propre à ce job, jetable, jamais repoussé vers le dépôt.
mv .dockerignore .dockerignore.disabled-for-ci
docker build -f - -t forge-test-python:${{ gitea.sha }} . <<'DOCKERFILE'
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt requirements-dev.txt ./
RUN pip install --no-cache-dir -r requirements-dev.txt
COPY . .
RUN python -m pytest tests/ -q
DOCKERFILE
test-js:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Tests JS (node:test — logique pure de static/game/js/play/, et l'assistant déclencheurs de static/game/js/scenes/+static/game/js/triggers/, voir le plan de modularisation)
run: |
docker build -f - -t forge-test-js:${{ gitea.sha }} . <<'DOCKERFILE'
FROM node:20-slim
WORKDIR /app
COPY static/game/js/play/ static/game/js/play/
COPY static/game/js/scenes/ static/game/js/scenes/
COPY static/game/js/triggers/ static/game/js/triggers/
RUN node --test static/game/js/play/__tests__/*.test.js static/game/js/play/offline/__tests__/*.test.js static/game/js/scenes/__tests__/*.test.js
DOCKERFILE
lint-python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Qualite Python (ruff/mypy/vulture/bandit/import-linter — memes commandes que .pre-commit-config.yaml)
run: |
# Meme neutralisation de .dockerignore que test-python ci-dessus :
# ruff/mypy typent aussi tests/ (voir le plan de typage strict),
# exclu par defaut de l'image de PROD.
mv .dockerignore .dockerignore.disabled-for-ci
docker build -f - -t forge-lint-python:${{ gitea.sha }} . <<'DOCKERFILE'
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt requirements-dev.txt ./
RUN pip install --no-cache-dir -r requirements-dev.txt
COPY . .
RUN ruff check .
RUN ruff format --check .
RUN mypy .
RUN vulture
RUN bandit -c pyproject.toml -r ai auth core db filters publish routes game_engine document_engine scripts app.py build_css.py
RUN lint-imports
DOCKERFILE
lint-js:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Qualite JS/CSS (eslint/stylelint — memes commandes que .pre-commit-config.yaml, via les scripts npm de package.json)
run: |
docker build -f - -t forge-lint-js:${{ gitea.sha }} . <<'DOCKERFILE'
FROM node:20-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run lint:js
RUN npm run lint:css
DOCKERFILE
sonarqube:
runs-on: ubuntu-latest
# Non-bloquant pendant cette premiere periode (voir le bloc de
# commentaires en tete de fichier) — un echec ici n'empeche jamais
# build-and-push/deploy, contrairement a lint-python/lint-js.
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Analyse SonarQube (rapport seul, non-bloquant)
run: |
# Meme neutralisation de .dockerignore que test-python : sonar-
# project.properties couvre aussi tests/ (sonar.tests).
mv .dockerignore .dockerignore.disabled-for-ci
docker build -f - -t forge-sonar:${{ gitea.sha }} . <<'DOCKERFILE'
FROM sonarsource/sonar-scanner-cli:latest
WORKDIR /usr/src
COPY . .
DOCKERFILE
docker run --rm forge-sonar:${{ gitea.sha }} \
-Dsonar.host.url=https://sonar.forgebase.fr \
-Dsonar.token=${{ secrets.SONAR_TOKEN }}
build-and-push:
needs: [test-python, test-js, lint-python, lint-js]
if: gitea.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Connexion au registre d'images
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "${{ secrets.REGISTRY_HOST }}" -u "${{ secrets.REGISTRY_USER }}" --password-stdin
- name: Build de l'image
run: |
docker build \
-t "${{ secrets.REGISTRY_IMAGE }}:${{ gitea.sha }}" \
-t "${{ secrets.REGISTRY_IMAGE }}:latest" \
.
- name: Publication de l'image
run: |
docker push "${{ secrets.REGISTRY_IMAGE }}:${{ gitea.sha }}"
docker push "${{ secrets.REGISTRY_IMAGE }}:latest"
deploy:
needs: build-and-push
if: gitea.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Préparation de la clé SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts
- name: Envoi du docker-compose.prod.yml sur le serveur
run: |
scp -i ~/.ssh/deploy_key docker-compose.prod.yml \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/docker-compose.prod.yml"
- name: Pull et redémarrage sur le serveur
run: |
ssh -i ~/.ssh/deploy_key "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" '
set -e
cd "${{ secrets.DEPLOY_PATH }}"
echo "REGISTRY_IMAGE=${{ secrets.REGISTRY_IMAGE }}" > .env
echo "IMAGE_TAG=${{ gitea.sha }}" >> .env
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "${{ secrets.REGISTRY_HOST }}" -u "${{ secrets.REGISTRY_USER }}" --password-stdin
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
docker image prune -f
'
- name: Nettoyage de la cle SSH
# if: always() - meme si les etapes precedentes ont echoue, la cle
# privee posee ci-dessus (~/.ssh/deploy_key) ne doit jamais rester
# sur le disque du job. Le runner semble deja jetable (voir
# "docker volume rm .../JOB-<id>-..." dans les logs des autres
# jobs de ce meme workflow), mais jamais verifie directement pour
# CE job (jamais execute sur dev, reserve a main) - ne pas se fier
# uniquement a une inference par analogie pour une cle privee.
if: always()
run: rm -f ~/.ssh/deploy_key