Skip to content
Open
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
4 changes: 3 additions & 1 deletion webapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ Plus d'infos ici : [https://feature-sliced.github.io/documentation/docs/get-star
- Avoir Node.js installé sur votre machine [voir comment faire](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm#using-a-node-version-manager-to-install-nodejs-and-npm)

## Installer les packages

```
npm ci
```

## Démarrer le serveur

```
npm run dev
```
Expand All @@ -22,4 +24,4 @@ npm run dev
- `npm run build` : package l'application pour la mise en production (dossier [dist](./dist))
- `npm run preview` : Démarre le serveur à partir du build pour la production (dans le dist)
- `npm run check:arch` : Vérifier si les imports du projet respectent le principe de l'architecture FSD.
- `npm run arch:tree` : Génère un fichier text contenant l'arborescence du projet.
- `npm run arch:tree` : Génère un fichier text contenant l'arborescence du projet.
2 changes: 1 addition & 1 deletion webapp/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
"hooks": "@/hooks"
},
"registries": {}
}
}
38 changes: 27 additions & 11 deletions webapp/eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import js from "@eslint/js";
import pluginReact from "eslint-plugin-react";
import globals from "globals";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";

import js from "@eslint/js";
import json from "@eslint/json";
import { defineConfig } from "eslint/config";
import pluginPrettier from "eslint-config-prettier/flat";
import { defineConfig } from "eslint/config";

export default defineConfig([
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } },
{
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
plugins: { js },
extends: ["js/recommended"],
languageOptions: { globals: globals.browser },
},
tseslint.configs.recommended,
{
...pluginReact.configs.flat.recommended,
Expand All @@ -16,17 +22,27 @@ export default defineConfig([
* Disable react/display-name since it's currently broken
* TypeError: Error while loading rule 'react/display-name': sourceCode.getAllComments is not a function
*/
"react/display-name": ["off"]
"react/display-name": ["off"],
},
settings: {
react: {
version: "detect"
}
version: "detect",
},
},
},
/** @ts-expect-error json plugin not typed well*/
{ files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] },
/** @ts-expect-error json plugin not typed well*/
{ files: ["**/*.jsonc"], plugins: { json }, language: "json/jsonc", extends: ["json/recommended"] },
{
files: ["**/*.json"],
/** @ts-expect-error json plugin not typed well*/
plugins: { json },
language: "json/json",
extends: ["json/recommended"],
},
{
files: ["**/*.jsonc"],
/** @ts-expect-error json plugin not typed well*/
plugins: { json },
language: "json/jsonc",
extends: ["json/recommended"],
},
pluginPrettier,
]);
16 changes: 13 additions & 3 deletions webapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="icon"
type="image/svg+xml"
href="/vite.svg"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>frontend</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script
type="module"
src="/src/main.tsx"
></script>
</body>
</html>
56 changes: 36 additions & 20 deletions webapp/scripts/check-architecture.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
* Vérifie que les imports respectent les règles de l'architecture
*/

const fs = require('fs');
const path = require('path');
const fs = require("fs");
const path = require("path");

const LAYERS = ['app', 'pages', 'widgets', 'features', 'entities', 'shared'];
const LAYERS = ["app", "pages", "widgets", "features", "entities", "shared"];
const LAYER_HIERARCHY = {
'shared': [],
'entities': ['shared'],
'features': ['entities', 'shared'],
'widgets': ['features', 'entities', 'shared'],
'pages': ['widgets', 'features', 'entities', 'shared'],
'app': ['pages', 'widgets', 'features', 'entities', 'shared']
shared: [],
entities: ["shared"],
features: ["entities", "shared"],
widgets: ["features", "entities", "shared"],
pages: ["widgets", "features", "entities", "shared"],
app: ["pages", "widgets", "features", "entities", "shared"],
};

function getLayerFromPath(filePath) {
const relativePath = path.relative(path.join(__dirname, 'src'), filePath);
const relativePath = path.relative(path.join(__dirname, "src"), filePath);
const firstDir = relativePath.split(path.sep)[0];
return LAYERS.includes(firstDir) ? firstDir : null;
}
Expand All @@ -33,12 +33,15 @@ function checkImports(filePath, fileContent) {
while ((match = importRegex.exec(fileContent)) !== null) {
const importedLayer = match[1];

if (LAYERS.includes(importedLayer) && !LAYER_HIERARCHY[layer].includes(importedLayer)) {
if (
LAYERS.includes(importedLayer) &&
!LAYER_HIERARCHY[layer].includes(importedLayer)
) {
violations.push({
file: filePath,
layer,
importedLayer,
line: fileContent.substring(0, match.index).split('\n').length
line: fileContent.substring(0, match.index).split("\n").length,
});
}
}
Expand All @@ -54,10 +57,17 @@ function scanDirectory(dir) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);

if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') {
if (
stat.isDirectory() &&
!file.startsWith(".") &&
file !== "node_modules"
) {
violations.push(...scanDirectory(filePath));
} else if (stat.isFile() && (file.endsWith('.ts') || file.endsWith('.tsx'))) {
const content = fs.readFileSync(filePath, 'utf8');
} else if (
stat.isFile() &&
(file.endsWith(".ts") || file.endsWith(".tsx"))
) {
const content = fs.readFileSync(filePath, "utf8");
violations.push(...checkImports(filePath, content));
}
}
Expand All @@ -66,21 +76,27 @@ function scanDirectory(dir) {
}

// Exécution
console.log('🔍 Vérification de l\'architecture FSD...\n');
console.log("🔍 Vérification de l'architecture FSD...\n");

const srcDir = path.join(__dirname, '../src');
const srcDir = path.join(__dirname, "../src");
const violations = scanDirectory(srcDir);

if (violations.length === 0) {
console.log('✅ Aucune violation détectée ! L\'architecture FSD est respectée.');
console.log(
"✅ Aucune violation détectée ! L'architecture FSD est respectée.",
);
} else {
console.log(`❌ ${violations.length} violation(s) détectée(s):\n`);

violations.forEach(({ file, layer, importedLayer, line }) => {
const relativeFile = path.relative(__dirname, file);
console.log(` ${relativeFile}:${line}`);
console.log(` ↳ La couche "${layer}" ne peut pas importer depuis "${importedLayer}"`);
console.log(` ↳ Imports autorisés: ${LAYER_HIERARCHY[layer].join(', ') || 'aucun'}\n`);
console.log(
` ↳ La couche "${layer}" ne peut pas importer depuis "${importedLayer}"`,
);
console.log(
` ↳ Imports autorisés: ${LAYER_HIERARCHY[layer].join(", ") || "aucun"}\n`,
);
});

process.exit(1);
Expand Down
110 changes: 57 additions & 53 deletions webapp/scripts/generate-tree.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,10 @@
* @example node scripts/generate-tree.cjs ./frontend/src docs/frontend-archi.md
*/

const fs = require('fs');
const path = require('path');

const IGNORE_LIST = [
'node_modules',
'.git',
'dist',
'coverage',
'.vscode',
];
const fs = require("fs");
const path = require("path");

const IGNORE_LIST = ["node_modules", ".git", "dist", "coverage", ".vscode"];

// Caractères Unicode (maintenant commentés)
// const PREFIX_BRANCH = '├── ';
Expand All @@ -24,36 +18,36 @@ const IGNORE_LIST = [
// const PREFIX_EMPTY = ' ';

// Alternative avec des caractères ASCII-safe pour une compatibilité maximale
const PREFIX_BRANCH = '|-- ';
const PREFIX_LAST_BRANCH = '`-- '; // Utilise un accent grave pour simuler la branche finale
const PREFIX_CHILD = '| ';
const PREFIX_EMPTY = ' ';

const PREFIX_BRANCH = "|-- ";
const PREFIX_LAST_BRANCH = "`-- "; // Utilise un accent grave pour simuler la branche finale
const PREFIX_CHILD = "| ";
const PREFIX_EMPTY = " ";

/**
* Fonction récursive qui génère l'arborescence d'un dossier.
* @param {string} directory - Le chemin du dossier à analyser.
* @param {string} prefix - Le préfixe de ligne pour l'indentation.
* @returns {string[]} Un tableau de chaînes, chaque chaîne étant une ligne de l'arbre.
*/
function generateTree(directory, prefix = '') {
const lines = [];
const files = fs.readdirSync(directory)
.filter(file => !IGNORE_LIST.includes(file));

files.forEach((file, index) => {
const filePath = path.join(directory, file);
const isLast = index === files.length - 1;
const isDirectory = fs.statSync(filePath).isDirectory();

lines.push(prefix + (isLast ? PREFIX_LAST_BRANCH : PREFIX_BRANCH) + file);

if (isDirectory) {
const childPrefix = prefix + (isLast ? PREFIX_EMPTY : PREFIX_CHILD);
lines.push(...generateTree(filePath, childPrefix));
}
});
return lines;
function generateTree(directory, prefix = "") {
const lines = [];
const files = fs
.readdirSync(directory)
.filter((file) => !IGNORE_LIST.includes(file));

files.forEach((file, index) => {
const filePath = path.join(directory, file);
const isLast = index === files.length - 1;
const isDirectory = fs.statSync(filePath).isDirectory();

lines.push(prefix + (isLast ? PREFIX_LAST_BRANCH : PREFIX_BRANCH) + file);

if (isDirectory) {
const childPrefix = prefix + (isLast ? PREFIX_EMPTY : PREFIX_CHILD);
lines.push(...generateTree(filePath, childPrefix));
}
});
return lines;
}

// --- Point d'entrée du script ---
Expand All @@ -62,35 +56,45 @@ const targetDir = process.argv[2];
const outputFile = process.argv[3];

if (!targetDir) {
console.error('❌ Erreur : Veuillez spécifier le chemin du dossier à analyser.');
console.error('Usage: node scripts/generate-tree.cjs <chemin_dossier> [fichier_sortie]');
process.exit(1);
console.error(
"❌ Erreur : Veuillez spécifier le chemin du dossier à analyser.",
);
console.error(
"Usage: node scripts/generate-tree.cjs <chemin_dossier> [fichier_sortie]",
);
process.exit(1);
}

if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
console.error(`❌ Erreur : Le dossier "${targetDir}" n'est pas un dossier valide.`);
process.exit(1);
console.error(
`❌ Erreur : Le dossier "${targetDir}" n'est pas un dossier valide.`,
);
process.exit(1);
}

const treeLines = [
`\`\`\`text`,
path.basename(targetDir),
...generateTree(targetDir),
`\`\`\``
`\`\`\`text`,
path.basename(targetDir),
...generateTree(targetDir),
`\`\`\``,
];

const outputString = treeLines.join('\n');
const outputString = treeLines.join("\n");

console.log('🌳 Arborescence générée :\n');
console.log("🌳 Arborescence générée :\n");
console.log(outputString);

if (outputFile) {
try {
// Le BOM n'est plus nécessaire avec des caractères ASCII purs.
fs.writeFileSync(outputFile, outputString, 'utf8');
console.log(`\n✅ Arborescence sauvegardée avec succès dans : ${outputFile}`);
} catch (error) {
console.error(`\n❌ Erreur lors de l'écriture du fichier : ${error.message}`);
process.exit(1);
}
}
try {
// Le BOM n'est plus nécessaire avec des caractères ASCII purs.
fs.writeFileSync(outputFile, outputString, "utf8");
console.log(
`\n✅ Arborescence sauvegardée avec succès dans : ${outputFile}`,
);
} catch (error) {
console.error(
`\n❌ Erreur lors de l'écriture du fichier : ${error.message}`,
);
process.exit(1);
}
}
11 changes: 7 additions & 4 deletions webapp/src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { ThemeProvider, ApiProvider, AuthProvider } from "@providers";
import { AppRouter } from "./routes/AppRouter";
import { ApiProvider, AuthProvider, ThemeProvider } from "@providers";

import { AppRouter } from "./routes/AppRouter";
import "./styles/all4trees.css";
import "./styles/globals.css";

function App() {
return (
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<ThemeProvider
defaultTheme="dark"
storageKey="vite-ui-theme"
>
<AuthProvider>
<ApiProvider>
<AppRouter />
Expand All @@ -16,4 +19,4 @@ function App() {
);
}

export default App;
export default App;
Loading