Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Quality (lint + typecheck)

# Garde-fous statiques du frontend. Introduits en amont de l'upgrade Next 16
# pour rendre visibles les regressions de typage et de lint.
#
# Etat actuel : NON BLOQUANT. Le depot demarre avec un passif d'erreurs de
# typage et de lint ; les etapes sont donc en `continue-on-error` le temps de
# resorber ce passif. Le passage en bloquant est le sujet de TASK-12.

on:
push:
branches:
- main
pull_request:

jobs:
frontend-quality:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up pnpm
uses: pnpm/action-setup@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile --filter frontend

- name: Typecheck
continue-on-error: true
run: pnpm --filter frontend run typecheck

- name: Lint
continue-on-error: true
run: pnpm --filter frontend run lint
10 changes: 7 additions & 3 deletions docker/frontend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,14 @@ COPY --from=builder /app/frontend/.next/static /app/frontend/.next/static
COPY --from=builder /app/frontend/public /app/frontend/public
COPY --from=builder /app/frontend/messages /app/frontend/messages

# Copy the server wrapper that ensures env vars are available
COPY frontend/server-wrapper.js /app/frontend/server-wrapper.js
# Note : aucun wrapper Node n'est necessaire. Le serveur standalone de Next lit
# les variables d'environnement au moment de la requete (seules les variables
# NEXT_PUBLIC_* sont figees a la compilation). Le fichier server-wrapper.js qui
# se contentait de re-affecter process.env.X = process.env.X a ete supprime :
# ces auto-affectations n'avaient aucun effet. Verifie en executant directement
# `node frontend/server.js` avec les variables fournies au runtime.

EXPOSE 3000
ENV PORT=3000

CMD ["node", "frontend/server-wrapper.js"]
CMD ["node", "frontend/server.js"]
172 changes: 85 additions & 87 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,105 +1,103 @@
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
import tsParser from '@typescript-eslint/parser';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import antfu from '@antfu/eslint-config';
import nextPlugin from '@next/eslint-plugin-next';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import storybook from 'eslint-plugin-storybook';

export default [
/**
* Configuration ESLint du monorepo (flat config, ESLint 9).
*
* Base : @antfu/eslint-config (config de reference de l'equipe, cf. decision-1).
* Par-dessus : regles specifiques Next.js, accessibilite (jsx-a11y) et Storybook,
* qui etaient absentes alors que les plugins etaient deja en dependance.
*/
export default antfu(
{
type: 'app',
react: true,
typescript: true,
// Le depot n'a jamais eu de formateur et son style est heterogene : les
// regles de forme (style/*) et de tri (perfectionist) produiraient environ
// 2900 erreurs de formatage, qui noieraient le signal utile. Le formatage
// est un sujet distinct, a traiter avec son propre outil et son propre diff.
stylistic: false,
jsonc: false,
yaml: false,
markdown: false,
toml: false,
ignores: [
'migrations/**/*',
'next-env.d.ts',
'**/contentTypes.d.ts',
'**/storybook-static/**/*',
'.next/**/*',
'**/.next/**/*',
'**/.next/types/**/*',
'**/.next/server/**/*',
'**/.next/static/**/*',
'**/.next/trace/**/*',
'**/.next/cache/**/*',
'**/.next/standalone/**/*',
'**/.next/swc/**/*',
'**/.next/webpack/**/*',
'**/.next/on-demand-entries/**/*',
'**/.next/prerender-manifest.json',
'**/.next/routes-manifest.json',
'**/.next/build-manifest.json',
'**/.next/required-server-files-manifest.json',
'**/.next/static/chunks/**/*',
'**/.next/static/css/**/*',
'**/.next/static/media/**/*',
'**/.next/static/webpack/**/*',
'node_modules/**/*',
'**/dist/**/*',
'**/build/**/*',
'**/coverage/**/*',
'**/.turbo/**/*',
'**/storybook-static/**/*',
'**/.storybook/**/*',
'**/public/**/*',
'**/backend/**/*',
'**/docker/**/*',
'**/migrations/**/*',
'**/*.min.js',
'**/*.bundle.js',
'**/vendor/**/*',
'**/chunks/**/*',
'**/webpack-runtime.js',
'**/polyfills.js',
'**/fallback/**/*',
'**/vendor-chunks/**/*',
'**/types/**/*.d.ts',
'**/generated/**/*',
// Le backend Strapi a ses propres regles.
'backend/**',
'docker/**',
'migrations/**',
'**/.next/**',
'**/storybook-static/**',
'**/.storybook/**',
'**/coverage/**',
// Types generes, jamais edites a la main.
'**/*.d.ts',
'**/generated/**',
'**/strapi-types.d.ts',
'**/auto-imports.d.ts',
'**/components.d.ts',
'**/nuxt.d.ts',
'**/imports.d.ts',
'**/composables.d.ts',
'**/utils.d.ts'
],
},
{
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
// Regles specifiques a l'application Next : elles ne visent que le frontend.
files: ['frontend/**/*.{js,jsx,ts,tsx}'],
plugins: {
'@next/next': nextPlugin,
'jsx-a11y': jsxA11y,
},
settings: {
// Sans cela, le plugin Next cherche un dossier `pages/` a la racine du
// monorepo et avertit a chaque execution.
next: { rootDir: 'frontend' },
},
rules: {
// Basic rules
'no-unused-vars': 'warn',
'no-console': 'warn',
'prefer-const': 'error',
'no-var': 'error',
// Indentation rules
'indent': ['error', 2],
'no-mixed-spaces-and-tabs': 'error',
...nextPlugin.configs.recommended.rules,
...nextPlugin.configs['core-web-vitals'].rules,
...jsxA11y.flatConfigs.recommended.rules,
},
},
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
// Le tri des imports/exports est purement cosmetique : il produirait plus
// de 140 erreurs sans rapport avec la correction du code. A traiter avec le
// formateur, dans un diff dedie.
rules: {
'perfectionist/sort-imports': 'off',
'perfectionist/sort-named-imports': 'off',
'perfectionist/sort-exports': 'off',
'perfectionist/sort-named-exports': 'off',
'import/consistent-type-specifier-style': 'off',
},
plugins: {
'@typescript-eslint': tsPlugin,
},
{
// Regles utiles mais dont le volume depasse l'urgence : signalees sans
// bloquer, en attendant une passe de nettoyage dediee.
rules: {
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': 'warn',
'react/no-array-index-key': 'warn',
'react-dom/no-dangerously-set-innerhtml': 'warn',
},
},
{
// Ces regles ont besoin de l'information de type : elles ne peuvent viser
// que les fichiers TypeScript (sinon `middleware.js` fait echouer ESLint).
files: ['**/*.{ts,tsx}'],
rules: {
'ts/consistent-type-imports': 'warn',
'ts/consistent-type-definitions': 'warn',
},
},
{
// Les stories sont des fixtures : elles ont le droit d'etre verbeuses et de
// manipuler des donnees incompletes.
files: ['**/*.stories.@(ts|tsx|js|jsx)', '**/*.story.@(ts|tsx|js|jsx)'],
rules: {
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-var-requires': 'error',
// TypeScript indentation rules
'@typescript-eslint/indent': ['error', 2],
'no-mixed-spaces-and-tabs': 'error',
'react-hooks/rules-of-hooks': 'off',
'no-console': 'off',
},
},
];
...storybook.configs['flat/recommended'],
);
5 changes: 5 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ next-env.d.ts

*storybook.log
storybook-static

# fichiers generes automatiquement par Next 16 a chaque "next dev"
# (desactivable avec agentRules: false dans next.config.mjs)
AGENTS.md
CLAUDE.md
16 changes: 12 additions & 4 deletions frontend/.storybook/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview';
import { setProjectAnnotations } from '@storybook/nextjs-vite';
import nextIntlAnnotations from 'storybook-next-intl/preview';
import * as projectAnnotations from './preview';

// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
// Ce fichier est indispensable : sans lui, les tests echouent sur
// « NoRenderFunctionError: No render function available », car les annotations
// de preview ne sont pas appliquees automatiquement dans cette configuration.
//
// `storybook-next-intl` doit y etre liste EXPLICITEMENT : `setProjectAnnotations`
// remplace les annotations fournies par les addons au lieu de les completer.
// Sans cette ligne, 67 tests echouent sur
// « Failed to call `useTranslations` because the context from
// `NextIntlClientProvider` was not found ».
setProjectAnnotations([a11yAddonAnnotations, nextIntlAnnotations, projectAnnotations]);
24 changes: 24 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,31 @@ pnpm run storybook

# Construire Storybook pour la production
pnpm run build-storybook

# Lancer les tests de composants (Vitest + navigateur, voir plus bas)
pnpm run test
pnpm run test:watch
```

#### Tests de composants

Les stories servent de tests : chaque story est rendue dans un vrai navigateur (Vitest +
Playwright) et vérifiée. Lancer `pnpm run test` depuis `frontend/`.

Si l'exécution échoue avec un message du type :

```
Error: browserType.launch: Host system is missing dependencies to run browsers.
```

c'est que le Chromium embarqué par Playwright ne trouve pas ses bibliothèques système (fréquent
sur NixOS, ou dans une image de CI minimale). Dans ce cas, pointer vers un Chromium déjà installé :

```bash
PLAYWRIGHT_EXECUTABLE_PATH=/chemin/vers/chromium pnpm run test
```

Ce chemin est lu par `vitest.config.ts` et transmis à Playwright.

#### Structure des composants

Expand Down
51 changes: 38 additions & 13 deletions frontend/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,59 @@
const nextConfig = {
output: 'standalone',
images: {
domains: [
'localhost',
'backend',
'dataforgood.fr',
'strapi.services.dataforgood.fr',
's3.fr-par.scw.cloud',
'images.pexels.com',
// `domains` est deprecie : remplace par `remotePatterns` (equivalent exact).
remotePatterns: [
{ protocol: 'http', hostname: 'localhost' },
{ protocol: 'http', hostname: 'backend' },
{ protocol: 'https', hostname: 'dataforgood.fr' },
{ protocol: 'https', hostname: 'strapi.services.dataforgood.fr' },
{ protocol: 'https', hostname: 's3.fr-par.scw.cloud' },
{ protocol: 'https', hostname: 'images.pexels.com' },
],
},
redirects: () => getRedirects(),
// VRAIMENT PAS OUF
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
};

export async function getRedirects() {
const apiUrl = process.env.STRAPI_API_URL;

Check failure on line 24 in frontend/next.config.mjs

View workflow job for this annotation

GitHub Actions / frontend-quality

Unexpected use of the global variable 'process'. Use 'require("process")' instead
const token = process.env.STRAPI_API_TOKEN;

Check failure on line 25 in frontend/next.config.mjs

View workflow job for this annotation

GitHub Actions / frontend-quality

Unexpected use of the global variable 'process'. Use 'require("process")' instead

// Sans jeton, /redirects repond 403 avec un corps d'erreur JSON. L'ancien code
// appelait data.map() sur ce corps et levait « data.map is not a function »,
// ce qui masquait la vraie cause (absence de jeton) derriere un TypeError.
if (!apiUrl || !token) {
console.warn(
'[redirects] STRAPI_API_URL ou STRAPI_API_TOKEN absent : aucune redirection du CMS ne sera chargee.',
);
return [];
}

try {
const res = await fetch(`${process.env.STRAPI_API_URL}/redirects`, {
const res = await fetch(`${apiUrl}/redirects`, {
headers: {
Authorization: `Bearer ${process.env.STRAPI_API_TOKEN}`,
Authorization: `Bearer ${token}`,
},
});

if (!res.ok) {
console.warn(
`[redirects] /redirects a repondu ${res.status} ${res.statusText} : aucune redirection du CMS ne sera chargee.`,
);
return [];
}

const data = await res.json();

if (!Array.isArray(data)) {
console.warn(
'[redirects] reponse inattendue de /redirects (un tableau etait attendu) : aucune redirection du CMS ne sera chargee.',
);
return [];
}

return data.map((redirect) => ({
source: redirect.source,
destination: redirect.destination,
Expand Down
Loading
Loading