Files
williamandClaude Sonnet 5 ccf836f2c5
Build and deploy / test-python (push) Successful in 11m18s
Build and deploy / test-js (push) Successful in 1m16s
Build and deploy / lint-python (push) Successful in 4m24s
Build and deploy / lint-js (push) Successful in 2m51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Successful in 5m30s
Lot 4 modernisation JS (SonarLint) + restauration CI sonarqube non-bloquante
Corrige les findings SonarQube (via SonarLint IDE, fichier par fichier)
sur ~24 fichiers static/js/ : parseFloat/parseInt -> Number.*,
.replace(/x/g,y) -> .replaceAll, .indexOf() -> .includes()/.startsWith(),
getAttribute/setAttribute -> .dataset, tableaux -> Set, x && x.y -> x?.y
(verifie site par site), extraction de template litteraux imbriques,
ternaires imbriquees, refactors de complexite cognitive (S3776) via
tables de dispatch, Object.hasOwn, .at(), et deduplication de fonctions
identiques (S4144). Deux exceptions S2486 documentees/corrigees
(filter-repeater-rows.js) et un cas S2703 de partage inter-scripts
complete (_collisionWizard, trigger-editor.js <-> collision-rules-editor.js).
Details complets dans CODE_QUALITY.md section 5.

Restaure aussi le job CI "sonarqube" (non-bloquant) dans
.gitea/workflows/deploy.yml maintenant que l'instance prod est
operationnelle.

Suites vertes : 276/276 JS (node --test), 591/591 Python (pytest).

SKIP=djlint sur ce commit : hook djlint bloquant sur le backlog H021
(styles inline, 49 occurrences/6 templates) deja documente comme dette
assumee non traitee dans CODE_QUALITY.md section 6, aucun rapport avec
ce commit (aucun template touche ici) - valide explicitement avec
l'utilisateur avant de contourner ce hook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 16:38:29 +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/js/play/, et l'assistant déclencheurs de static/js/scenes/+static/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/js/play/ static/js/play/
COPY static/js/scenes/ static/js/scenes/
COPY static/js/triggers/ static/js/triggers/
RUN node --test static/js/play/__tests__/*.test.js static/js/play/offline/__tests__/*.test.js static/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 screens 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