1. Duplication éliminée avant que la Phase 2 (hasard/opérations mathématiques) n'en ajoute 6 de plus aux DEUX fichiers : la chaîne d'opérations quasi identique entre screens/data_actions/ apply_data_action.py (champ d'objet) et apply_variable_action.py (variable globale) est factorisée dans un nouveau compute_operation.py::compute_new_value(operation, current, raw_value, is_decimal), réutilisé par les deux. Nouveau tests/test_compute_operation.py verrouille le comportement des 7 opérations existantes (dont les cas limites : valeur invalide, type décimal vs entier, opération inconnue) avant d'en ajouter d'autres. 2. .gitea/workflows/deploy.yml déployait en prod à chaque push sur main sans jamais exécuter la suite de tests — rien ne bloquait techniquement un commit cassé. Nouveau job "test" (pytest + node:test sur la logique pure de static/js/play/, via des conteneurs officiels plutôt que des actions du marketplace, cohérent avec le choix déjà fait dans ce fichier) tourne sur CHAQUE push (main ET dev, utile pour ce dépôt qui travaille sur dev) ; "build-and-push"/"deploy" gagnent un "needs: test" et restent réservés à main (filtre sur gitea.ref) — un push sur dev ne redéploie jamais la prod, seulement les tests. Vérifié : 223 tests passent (8 nouveaux), YAML validé. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
105 lines
4.3 KiB
YAML
105 lines
4.3 KiB
YAML
name: Build and deploy
|
|
|
|
on:
|
|
push:
|
|
branches: [main, dev]
|
|
|
|
# Le job "test" tourne sur CHAQUE push (main et dev) : jusqu'ici aucune
|
|
# étape de CI n'exécutait la suite de tests, 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 les tests.
|
|
|
|
# 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)
|
|
#
|
|
# 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:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# Conteneurs officiels plutôt que actions/setup-python|setup-node du
|
|
# marketplace — même logique que le reste de ce fichier (éviter une
|
|
# dépendance à la disponibilité de github.com/du miroir gitea.com
|
|
# depuis le runner self-hosted), et le runner n'a pas besoin
|
|
# d'installer quoi que ce soit d'autre que Docker, déjà requis pour
|
|
# build-and-push.
|
|
- name: Tests Python (pytest)
|
|
run: |
|
|
docker run --rm -v "$PWD":/app -w /app python:3.13-slim sh -c "
|
|
pip install --quiet -r requirements-dev.txt &&
|
|
python -m pytest tests/ -q
|
|
"
|
|
|
|
- name: Tests JS (node:test — logique pure de static/js/play/, voir le plan de modularisation)
|
|
run: |
|
|
docker run --rm -v "$PWD":/app -w /app node:20-slim \
|
|
node --test static/js/play/__tests__/*.test.js
|
|
|
|
build-and-push:
|
|
needs: test
|
|
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
|
|
docker login "${{ secrets.REGISTRY_HOST }}" -u "${{ secrets.REGISTRY_USER }}" -p "${{ secrets.REGISTRY_TOKEN }}"
|
|
docker compose -f docker-compose.prod.yml pull
|
|
docker compose -f docker-compose.prod.yml up -d
|
|
docker image prune -f
|
|
'
|