mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
test(r0.12): ESLint monorepo + parcours démo Playwright — R0.12 clos
- ESLint 10 flat config racine (typescript-eslint, react-hooks) ; règle ADR-001 codée : no-restricted-imports interdit « minio » partout sauf minio-storage.service.ts (violation vérifiée) ; jobs lint par paquet - Playwright apps/web/e2e : redirection sans jeton, connexion démo 1 clic, coquille (rail actif, chip DÉMO), bascule de rôle chronométrée < 3 s (ADR-002), /design, déconnexion avec purge ; webServer api dist + vite, seed idempotent en globalSetup, PW_CHANNEL=chrome en local - CI : jobs lint + e2e (services PostgreSQL/Redis, artefact playwright-report sur échec) ; vitest restreint à src/ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
64
.github/workflows/ci.yml
vendored
64
.github/workflows/ci.yml
vendored
@@ -3,7 +3,10 @@
|
|||||||
# moindre diff entre le contrat committé et la génération ;
|
# moindre diff entre le contrat committé et la génération ;
|
||||||
# api : migration + tests Jest (couverture ≥ 70 % bloquante, jest.config) ;
|
# api : migration + tests Jest (couverture ≥ 70 % bloquante, jest.config) ;
|
||||||
# web : typecheck + vitest + build de production.
|
# web : typecheck + vitest + build de production.
|
||||||
# Le parcours Playwright (connexion démo → coquille) complétera R0.12.
|
# lint : ESLint monorepo (dont la règle ADR-001 « pas d'import MinIO
|
||||||
|
# hors FileStorage ») ;
|
||||||
|
# e2e : parcours démo Playwright (connexion 1 clic → coquille →
|
||||||
|
# bascule de rôle < 3 s → /design) contre l'API construite.
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -16,6 +19,19 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: lint (ESLint monorepo)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
cache: pnpm
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm lint
|
||||||
|
|
||||||
contract:
|
contract:
|
||||||
name: ci-contract (règle d'or)
|
name: ci-contract (règle d'or)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -93,3 +109,49 @@ jobs:
|
|||||||
- run: pnpm --filter @siop/shared build
|
- run: pnpm --filter @siop/shared build
|
||||||
- run: pnpm --filter @siop/web test
|
- run: pnpm --filter @siop/web test
|
||||||
- run: pnpm --filter @siop/web build
|
- run: pnpm --filter @siop/web build
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
name: e2e (parcours démo Playwright)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:18
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: siop
|
||||||
|
POSTGRES_PASSWORD: siop
|
||||||
|
POSTGRES_DB: siop
|
||||||
|
ports: ['5432:5432']
|
||||||
|
options: >-
|
||||||
|
--health-cmd "pg_isready -U siop -d siop"
|
||||||
|
--health-interval 5s --health-timeout 3s --health-retries 10
|
||||||
|
redis:
|
||||||
|
image: redis:7.4-alpine
|
||||||
|
ports: ['6379:6379']
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 5s --health-timeout 3s --health-retries 10
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgresql://siop:siop@localhost:5432/siop
|
||||||
|
REDIS_URL: redis://localhost:6379
|
||||||
|
JWT_SECRET: ci-only-secret-0123456789abcdef
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
cache: pnpm
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm --filter @siop/shared build
|
||||||
|
- run: pnpm --filter @siop/api prisma:generate
|
||||||
|
- run: pnpm --filter @siop/api exec prisma migrate deploy
|
||||||
|
- run: pnpm --filter @siop/api build
|
||||||
|
- run: pnpm --filter @siop/web exec playwright install --with-deps chromium
|
||||||
|
- run: pnpm --filter @siop/web e2e
|
||||||
|
- name: Traces Playwright en cas d'échec
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: apps/web/playwright-report/
|
||||||
|
retention-days: 7
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@ dist/
|
|||||||
.turbo/
|
.turbo/
|
||||||
coverage/
|
coverage/
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
test-results/
|
||||||
|
playwright-report/
|
||||||
|
|||||||
@@ -37,6 +37,6 @@ pnpm + Turborepo. `apps/api` : NestJS, Prisma, PostgreSQL (pgvector + PostGIS),
|
|||||||
- ✅ **R0 (1/2)** : playbook 00-vision, 01-cadrage, 02-design — **charte + tokens + maquettes HD VALIDÉES par le référent le 15/07/2026** (artefact : maquette-web.html ; 7 écrans, bi-thème). 03-architecture rédigé (ADR-001 stack, ADR-002 démo-login, C4, modèle R0). Racine monorepo posée (package.json/pnpm-workspace/turbo/.nvmrc) + `infra/` (compose PostgreSQL pgvector+PostGIS, Redis, MinIO — services préfixés `siop2-`).
|
- ✅ **R0 (1/2)** : playbook 00-vision, 01-cadrage, 02-design — **charte + tokens + maquettes HD VALIDÉES par le référent le 15/07/2026** (artefact : maquette-web.html ; 7 écrans, bi-thème). 03-architecture rédigé (ADR-001 stack, ADR-002 démo-login, C4, modèle R0). Racine monorepo posée (package.json/pnpm-workspace/turbo/.nvmrc) + `infra/` (compose PostgreSQL pgvector+PostGIS, Redis, MinIO — services préfixés `siop2-`).
|
||||||
- ✅ **R0.10 `apps/api`** : NestJS + Prisma (migration `r0_identity`), guard JWT global fermé par défaut + `@Public()`, PermissionsGuard (matrice en base, cache 60 s), démo-login ADR-002 (module conditionnel, 404 sinon, double verrou prod), seed idempotent (7 rôles, 70 lignes de matrice, 7 comptes démo), `FileStorage`/health ; `packages/shared` (Zod) + `pnpm contract` → `docs/openapi.json` committée ; 19 tests Jest verts + smoke test build prod.
|
- ✅ **R0.10 `apps/api`** : NestJS + Prisma (migration `r0_identity`), guard JWT global fermé par défaut + `@Public()`, PermissionsGuard (matrice en base, cache 60 s), démo-login ADR-002 (module conditionnel, 404 sinon, double verrou prod), seed idempotent (7 rôles, 70 lignes de matrice, 7 comptes démo), `FileStorage`/health ; `packages/shared` (Zod) + `pnpm contract` → `docs/openapi.json` committée ; 19 tests Jest verts + smoke test build prod.
|
||||||
- ✅ **R0.11 `apps/web`** : React 19 + Vite + Tailwind v4, `tokens.css` copié tel quel + classes extraites de la maquette validée, Manrope auto-hébergée ; connexion + sélecteur démo (masqué si 404 — une seule source de vérité : l'API), coquille sidebar (rail safran, écrans futurs marqués R1-R3) / topbar (thème, chip DÉMO, sélecteur de rôle), page /design ; client typé `schema.d.ts` généré depuis `docs/openapi.json` et committé ; vérifié en navigateur réel (bascule de rôle 101 ms, bi-thème, 0 erreur console).
|
- ✅ **R0.11 `apps/web`** : React 19 + Vite + Tailwind v4, `tokens.css` copié tel quel + classes extraites de la maquette validée, Manrope auto-hébergée ; connexion + sélecteur démo (masqué si 404 — une seule source de vérité : l'API), coquille sidebar (rail safran, écrans futurs marqués R1-R3) / topbar (thème, chip DÉMO, sélecteur de rôle), page /design ; client typé `schema.d.ts` généré depuis `docs/openapi.json` et committé ; vérifié en navigateur réel (bascule de rôle 101 ms, bi-thème, 0 erreur console).
|
||||||
- ✅ **R0.12 (1/2) — CI GitHub Actions** (`.github/workflows/ci.yml`, 3 jobs verts sur le dépôt) : `ci-contract` (régénération spec+client, diff bloquant), `api` (PostgreSQL 18 + Redis en services, migrate deploy, Jest couverture ≥ 70 % bloquante — mesurée 97,5 %), `web` (typecheck + vitest + build). Badge au README.
|
- ✅ **R0.12 — tests + CI** (5 jobs verts) : `lint` (ESLint 10 flat config racine, règle ADR-001 anti-import MinIO codée et vérifiée), `ci-contract` (régénération spec+client, diff bloquant), `api` (PostgreSQL 18 + Redis, migrate deploy, Jest couverture ≥ 70 % bloquante — mesurée 97,5 %), `web` (typecheck + vitest + build), `e2e` (Playwright : parcours démo, bascule < 3 s chronométrée, déconnexion). Badge au README.
|
||||||
- 🔄 **R0 (2/2) — reprise ici** : R0.12 (2/2) ESLint (dont règle « pas d'import MinIO hors FileStorage ») + parcours Playwright (connexion démo → coquille → bascule de rôle) → R0.13 Dockerfiles + runbook Dokploy (le déploiement réel attend les accès au serveur du partenaire).
|
- 🔄 **R0 (2/2) — reprise ici** : R0.13 Dockerfiles (api, web) + runbook Dokploy (le déploiement réel attend les accès au serveur du partenaire), puis recette R0 avec le référent (revue pixel écrans ↔ maquettes).
|
||||||
- Détail quotidien : `docs/journal/journal.md`. Dépôt : `siop-spelev/siop2` (privé), jalons R0→R5.
|
- Détail quotidien : `docs/journal/journal.md`. Dépôt : `siop-spelev/siop2` (privé), jalons R0→R5.
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"prisma:generate": "prisma generate",
|
"prisma:generate": "prisma generate",
|
||||||
"prisma:migrate": "prisma migrate dev",
|
"prisma:migrate": "prisma migrate dev",
|
||||||
"seed": "tsx prisma/seed.ts"
|
"seed": "tsx prisma/seed.ts",
|
||||||
|
"lint": "eslint src test prisma"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "tsx prisma/seed.ts"
|
"seed": "tsx prisma/seed.ts"
|
||||||
|
|||||||
11
apps/web/e2e/global-setup.ts
Normal file
11
apps/web/e2e/global-setup.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
/** Seed idempotent avant le parcours : mêmes comptes en local et en CI. */
|
||||||
|
export default function globalSetup() {
|
||||||
|
execSync('pnpm --filter @siop/api seed', {
|
||||||
|
cwd: fileURLToPath(new URL('../../..', import.meta.url)),
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: process.env,
|
||||||
|
});
|
||||||
|
}
|
||||||
65
apps/web/e2e/parcours-demo.spec.ts
Normal file
65
apps/web/e2e/parcours-demo.spec.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LE parcours de recette R0 : connexion démo 1 clic, coquille fidèle,
|
||||||
|
* bascule de rôle < 3 s (critère d'acceptation ADR-002), /design accessible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
test('sans jeton, l’application renvoie sur la connexion (fermée par défaut)', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page).toHaveURL(/\/connexion$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connexion démo → coquille → bascule de rôle < 3 s → /design', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto('/connexion');
|
||||||
|
|
||||||
|
// Mode démonstration : 7 comptes seedés, chip d'avertissement
|
||||||
|
await expect(page.locator('.sep')).toHaveText('Mode démonstration');
|
||||||
|
await expect(page.locator('.demo-liste button')).toHaveCount(7);
|
||||||
|
await expect(page.locator('.chip-demo')).toContainText('DEMO_MODE=true');
|
||||||
|
|
||||||
|
// Connexion 1 clic (Dispatcher)
|
||||||
|
await page.getByRole('button', { name: /Salma Idrissi/ }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: 'Tableau de bord' }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.locator('.topbar')).toContainText('Salma Idrissi');
|
||||||
|
await expect(page.locator('.topbar .chip-demo')).toHaveText('DÉMO');
|
||||||
|
// Rail safran : l'entrée active de la sidebar
|
||||||
|
await expect(page.locator('.nav a.actif')).toHaveText(/Tableau de bord/);
|
||||||
|
|
||||||
|
// Bascule de rôle via la topbar — critère ADR-002 : < 3 s
|
||||||
|
const debut = Date.now();
|
||||||
|
await page.locator('.compte').click();
|
||||||
|
await page.getByRole('menuitem', { name: /Ahmed Benali/ }).click();
|
||||||
|
await expect(page.locator('.topbar')).toContainText('Ahmed Benali');
|
||||||
|
await expect(page.locator('.topbar')).toContainText('Technicien');
|
||||||
|
expect(Date.now() - debut).toBeLessThan(3000);
|
||||||
|
|
||||||
|
// Référence vivante du design
|
||||||
|
await page.goto('/design');
|
||||||
|
await expect(
|
||||||
|
page.getByRole('heading', { name: /Design — référence vivante/ }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.locator('.puce-c').first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('la déconnexion ramène à la connexion et ferme la session', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto('/connexion');
|
||||||
|
await page.getByRole('button', { name: /Yasmine Alaoui/ }).click();
|
||||||
|
await expect(page.locator('.topbar')).toContainText('Yasmine Alaoui');
|
||||||
|
|
||||||
|
await page.locator('.compte').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Se déconnecter' }).click();
|
||||||
|
await expect(page).toHaveURL(/\/connexion$/);
|
||||||
|
|
||||||
|
// Retour direct sur / : toujours redirigé (le jeton est bien purgé)
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page).toHaveURL(/\/connexion$/);
|
||||||
|
});
|
||||||
@@ -9,7 +9,9 @@
|
|||||||
"build": "tsc -p tsconfig.json --noEmit && vite build",
|
"build": "tsc -p tsconfig.json --noEmit && vite build",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"generate:client": "openapi-typescript ../../docs/openapi.json -o src/api/schema.d.ts"
|
"generate:client": "openapi-typescript ../../docs/openapi.json -o src/api/schema.d.ts",
|
||||||
|
"e2e": "playwright test",
|
||||||
|
"lint": "eslint src e2e"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/manrope": "^5.1.0",
|
"@fontsource/manrope": "^5.1.0",
|
||||||
@@ -26,6 +28,7 @@
|
|||||||
"tailwind-merge": "^2.6.0"
|
"tailwind-merge": "^2.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.61.1",
|
||||||
"@tailwindcss/vite": "^4.1.0",
|
"@tailwindcss/vite": "^4.1.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
|
|||||||
46
apps/web/playwright.config.ts
Normal file
46
apps/web/playwright.config.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parcours e2e (R0.12) — démarre l'API construite (dist) et Vite, puis joue
|
||||||
|
* le parcours démo. Localement : PW_CHANNEL=chrome évite le téléchargement
|
||||||
|
* de Chromium (utilise le Chrome installé) ; en CI, Chromium Playwright.
|
||||||
|
* Prérequis : PostgreSQL/Redis joignables, `pnpm --filter @siop/api build`.
|
||||||
|
*/
|
||||||
|
const API_ENV = {
|
||||||
|
NODE_ENV: 'test',
|
||||||
|
PORT: '3000',
|
||||||
|
DEMO_MODE: 'true',
|
||||||
|
JWT_SECRET: process.env.JWT_SECRET ?? 'e2e-secret-0123456789abcdef',
|
||||||
|
DATABASE_URL:
|
||||||
|
process.env.DATABASE_URL ?? 'postgresql://siop:siop@localhost:5432/siop',
|
||||||
|
REDIS_URL: process.env.REDIS_URL ?? 'redis://localhost:6379',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './e2e',
|
||||||
|
globalSetup: './e2e/global-setup.ts',
|
||||||
|
timeout: 30_000,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 1 : 0,
|
||||||
|
use: {
|
||||||
|
baseURL: 'http://localhost:5173',
|
||||||
|
trace: 'retain-on-failure',
|
||||||
|
...(process.env.PW_CHANNEL ? { channel: process.env.PW_CHANNEL } : {}),
|
||||||
|
},
|
||||||
|
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
||||||
|
webServer: [
|
||||||
|
{
|
||||||
|
command: 'node ../api/dist/main.js',
|
||||||
|
url: 'http://localhost:3000/health',
|
||||||
|
env: API_ENV,
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
timeout: 30_000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: 'pnpm dev',
|
||||||
|
url: 'http://localhost:5173',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
timeout: 60_000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -14,5 +14,5 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": { "@/*": ["src/*"] }
|
"paths": { "@/*": ["src/*"] }
|
||||||
},
|
},
|
||||||
"include": ["src", "vite.config.ts"]
|
"include": ["src", "e2e", "vite.config.ts", "playwright.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/// <reference types="vitest/config" />
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
@@ -5,6 +6,10 @@ import { defineConfig } from 'vite';
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
|
test: {
|
||||||
|
// e2e/ appartient à Playwright — vitest ne joue que les tests unitaires
|
||||||
|
include: ['src/**/*.test.{ts,tsx}'],
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: { '@': path.resolve(__dirname, 'src') },
|
alias: { '@': path.resolve(__dirname, 'src') },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,22 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook**
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.12 (2/2) : ESLint + parcours Playwright — R0.12 CLOS
|
||||||
|
|
||||||
|
**Actions**
|
||||||
|
|
||||||
|
- **ESLint 10** (flat config unique à la racine, typescript-eslint, react-hooks sur le web) + scripts `lint` par paquet et job CI dédié. La règle d'architecture ADR-001 est codée : `no-restricted-imports` interdit `minio` partout **sauf** `minio-storage.service.ts` — violation vérifiée par un fichier-test (détectée puis retiré). Monorepo lint : 0 erreur.
|
||||||
|
- **Playwright** (`apps/web/e2e`) : 3 tests — redirection sans jeton (fermée par défaut), parcours complet connexion démo → coquille (rail actif, chip DÉMO) → **bascule de rôle chronométrée < 3 s** (ADR-002) → /design, et déconnexion avec purge de session. `webServer` démarre l'API construite + Vite ; seed idempotent en globalSetup ; localement `PW_CHANNEL=chrome` (pas de téléchargement).
|
||||||
|
- CI : jobs `lint` et `e2e` (services PostgreSQL/Redis, artefact playwright-report en cas d'échec). Vitest restreint à `src/` (e2e appartient à Playwright).
|
||||||
|
|
||||||
|
**Décisions**
|
||||||
|
|
||||||
|
- Une seule config ESLint à la racine (pas une par app) : les règles d'architecture sont transversales et la config est le lieu du cours.
|
||||||
|
|
||||||
|
**Prochaine étape** : R0.13 — Dockerfiles (api, web) + runbook Dokploy (`docs/04-*`), puis recette R0 avec le référent (revue pixel écrans ↔ maquettes).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.12 (1/2) : pipeline GitHub Actions
|
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.12 (1/2) : pipeline GitHub Actions
|
||||||
|
|
||||||
**Actions**
|
**Actions**
|
||||||
|
|||||||
56
eslint.config.mjs
Normal file
56
eslint.config.mjs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// ESLint (R0.12) — config unique du monorepo (flat config, ESLint 9).
|
||||||
|
// Porte notamment la règle d'architecture ADR-001 : le SDK MinIO ne
|
||||||
|
// s'importe QUE dans l'implémentation de FileStorage.
|
||||||
|
import eslint from '@eslint/js';
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'**/dist/**',
|
||||||
|
'**/node_modules/**',
|
||||||
|
'**/*.d.ts',
|
||||||
|
'**/coverage/**',
|
||||||
|
'docs/**',
|
||||||
|
'**/*.config.{js,mjs,ts}',
|
||||||
|
'apps/api/jest.config.js',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
||||||
|
],
|
||||||
|
// ADR-001 : MinIO derrière l'interface FileStorage, jamais en direct
|
||||||
|
'no-restricted-imports': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
name: 'minio',
|
||||||
|
message:
|
||||||
|
'Import direct du SDK MinIO interdit (ADR-001) : passez par l’interface FileStorage (apps/api/src/files).',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Seul fichier autorisé : l'implémentation MinIO de FileStorage
|
||||||
|
files: ['apps/api/src/files/minio-storage.service.ts'],
|
||||||
|
rules: { 'no-restricted-imports': 'off' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['apps/web/**/*.{ts,tsx}'],
|
||||||
|
plugins: { 'react-hooks': reactHooks },
|
||||||
|
rules: {
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -19,6 +19,10 @@
|
|||||||
"infra:reset": "docker compose -f infra/docker-compose.yml down -v"
|
"infra:reset": "docker compose -f infra/docker-compose.yml down -v"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"turbo": "^2.5.0"
|
"@eslint/js": "^10.0.1",
|
||||||
|
"eslint": "^10.7.0",
|
||||||
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
|
"turbo": "^2.5.0",
|
||||||
|
"typescript-eslint": "^8.64.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"contract": "tsx scripts/generate-openapi.ts"
|
"contract": "tsx scripts/generate-openapi.ts",
|
||||||
|
"lint": "eslint src scripts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "^4.0.0"
|
"zod": "^4.0.0"
|
||||||
|
|||||||
736
pnpm-lock.yaml
generated
736
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user