UNPKG

cli-nexus

Version:

CLI pour générer instantanément des projets Node.js avec architecture professionnelle

492 lines (409 loc) 11.8 kB
const { BaseArchitecture } = require('../core/base-architecture'); const path = require('path'); class HexagonalArchitecture extends BaseArchitecture { constructor() { super(); this.name = 'hexagonal'; this.displayName = 'Hexagonal Architecture'; this.description = 'Architecture modulaire avec ports et adapters pour une forte modularité'; this.features = [ 'Séparation Ports/Adapters', 'Indépendance des frameworks', 'Testabilité élevée', 'Modularité forte' ]; } /** * Génère la structure Hexagonale complète */ async generate(targetDir, config) { this.validateConfig(config); // Création de la structure des dossiers await this.createDirectoryStructure(targetDir); // Génération des fichiers de base await this.generateBaseFiles(targetDir, config); // Génération des fichiers spécifiques Hexagonale await this.generateArchitectureFiles(targetDir, config); } /** * Crée la structure des dossiers Hexagonale */ async createDirectoryStructure(targetDir) { const directories = [ 'src/domain/entities', 'src/domain/services', 'src/domain/ports', 'src/application/use-cases', 'src/application/ports', 'src/infrastructure/adapters/primary', 'src/infrastructure/adapters/secondary', 'src/infrastructure/config', 'src/infrastructure/database', 'src/shared/utils', 'src/shared/errors' ]; for (const dir of directories) { await this.ensureDirectory(path.join(targetDir, dir)); } } /** * Génère les fichiers de base du projet */ async generateBaseFiles(targetDir, config) { // package.json const packageJson = this.getPackageJsonTemplate(config); await this.writeFile(path.join(targetDir, 'package.json'), packageJson); // README.md const readme = this.getReadmeTemplate(config); await this.writeFile(path.join(targetDir, 'README.md'), readme); // .gitignore const gitignore = this.getGitignoreTemplate(); await this.writeFile(path.join(targetDir, '.gitignore'), gitignore); } /** * Génère les fichiers spécifiques à l'architecture Hexagonale */ async generateArchitectureFiles(targetDir, config) { // Point d'entrée principal const appJs = this.getAppJsTemplate(config); await this.writeFile(path.join(targetDir, 'src/app.js'), appJs); // Serveur const serverJs = this.getServerJsTemplate(config); await this.writeFile(path.join(targetDir, 'src/server.js'), serverJs); // Entité de base const entityBase = this.getEntityBaseTemplate(config); await this.writeFile(path.join(targetDir, 'src/domain/entities/base.entity.js'), entityBase); // Port de base const portBase = this.getPortBaseTemplate(config); await this.writeFile(path.join(targetDir, 'src/domain/ports/base.port.js'), portBase); // Use Case de base const useCaseBase = this.getUseCaseBaseTemplate(config); await this.writeFile(path.join(targetDir, 'src/application/use-cases/base.use-case.js'), useCaseBase); // Adapter primaire de base const primaryAdapterBase = this.getPrimaryAdapterBaseTemplate(config); await this.writeFile(path.join(targetDir, 'src/infrastructure/adapters/primary/base.adapter.js'), primaryAdapterBase); // Adapter secondaire de base const secondaryAdapterBase = this.getSecondaryAdapterBaseTemplate(config); await this.writeFile(path.join(targetDir, 'src/infrastructure/adapters/secondary/base.adapter.js'), secondaryAdapterBase); // Routes principales const routesIndex = this.getRoutesIndexTemplate(config); await this.writeFile(path.join(targetDir, 'src/infrastructure/adapters/primary/routes.js'), routesIndex); } // Templates des fichiers getPackageJsonTemplate(config) { return JSON.stringify({ name: config.projectName, version: "1.0.0", description: config.description, main: "src/server.js", scripts: { start: "node src/server.js", dev: "nodemon src/server.js", test: "jest" }, keywords: ["nodejs", "hexagonal-architecture", "ports-adapters", "api"], author: config.author, license: "MIT", dependencies: { express: "^4.18.2", cors: "^2.8.5", helmet: "^7.1.0", morgan: "^1.10.0", dotenv: "^16.3.1" }, devDependencies: { nodemon: "^3.0.2", jest: "^29.7.0" }, engines: { node: ">=16.0.0" } }, null, 2); } getReadmeTemplate(config) { return `# ${config.projectName} ${config.description} ## 🏗️ Architecture Hexagonale Ce projet utilise **Hexagonal Architecture (Ports & Adapters)** pour une forte modularité. ### Structure des dossiers \`\`\` src/ ├── domain/ # Entités et services métier ├── application/ # Cas d'usage et ports ├── infrastructure/ # Adapters (primaire et secondaire) └── shared/ # Utilitaires et erreurs partagées \`\`\` ## 🚀 Installation \`\`\`bash npm install \`\`\` ## 🔧 Configuration 1. Copiez \`.env.example\` vers \`.env\` 2. Configurez vos variables d'environnement ## 🏃‍♂️ Démarrage \`\`\`bash # Développement npm run dev # Production npm start \`\`\` ## 👨‍💻 Auteur ${config.author} --- *Généré avec ❤️ par Nexus CLI* `; } getGitignoreTemplate() { return `# Dependencies node_modules/ npm-debug.log* # Environment variables .env # Logs logs *.log # Coverage directory coverage/ # IDE .vscode/ .idea/ # OS .DS_Store Thumbs.db `; } getAppJsTemplate(config) { const className = this.generateClassName(config.projectName); return `const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const morgan = require('morgan'); require('dotenv').config(); const routes = require('./infrastructure/adapters/primary/routes'); class ${className}App { constructor() { this.app = express(); this.port = process.env.PORT || 3000; this.setupMiddleware(); this.setupRoutes(); } setupMiddleware() { this.app.use(helmet()); this.app.use(cors()); this.app.use(morgan('combined')); this.app.use(express.json()); this.app.use(express.urlencoded({ extended: true })); } setupRoutes() { this.app.use('/api', routes); this.app.get('/health', (req, res) => { res.status(200).json({ status: 'OK', timestamp: new Date().toISOString() }); }); } start() { this.app.listen(this.port, () => { console.log(\`🚀 ${className} (Hexagonal Architecture) démarré sur le port \${this.port}\`); }); } } module.exports = ${className}App; `; } getServerJsTemplate(config) { const className = this.generateClassName(config.projectName); return `const ${className}App = require('./app'); const app = new ${className}App(); app.start(); `; } getEntityBaseTemplate(config) { return `/** * Entité de base pour le domaine métier */ class BaseEntity { constructor(id) { if (this.constructor === BaseEntity) { throw new Error('BaseEntity est une classe abstraite'); } this.id = id; this.createdAt = new Date(); this.updatedAt = new Date(); } /** * Valide l'entité */ validate() { throw new Error('La méthode validate() doit être implémentée'); } /** * Met à jour l'entité */ update(data) { Object.assign(this, data); this.updatedAt = new Date(); } /** * Convertit l'entité en objet simple */ toJSON() { return Object.assign({}, this); } } module.exports = BaseEntity; `; } getPortBaseTemplate(config) { return `/** * Port de base pour l'architecture hexagonale */ class BasePort { constructor() { if (this.constructor === BasePort) { throw new Error('BasePort est une classe abstraite'); } } /** * Exécute l'opération du port */ async execute(input) { throw new Error('La méthode execute() doit être implémentée'); } /** * Valide les données d'entrée */ validateInput(input) { return { isValid: true, errors: [] }; } } module.exports = BasePort; `; } getUseCaseBaseTemplate(config) { return `/** * Cas d'usage de base */ class BaseUseCase { constructor() { if (this.constructor === BaseUseCase) { throw new Error('BaseUseCase est une classe abstraite'); } } /** * Exécute le cas d'usage */ async execute(input) { throw new Error('La méthode execute() doit être implémentée'); } /** * Valide les données d'entrée */ validateInput(input) { return { isValid: true, errors: [] }; } /** * Gère les erreurs */ handleError(error) { console.error('Erreur dans le cas d\'usage:', error); throw error; } } module.exports = BaseUseCase; `; } getPrimaryAdapterBaseTemplate(config) { return `/** * Adapter primaire de base (entrée) */ class BasePrimaryAdapter { constructor() { if (this.constructor === BasePrimaryAdapter) { throw new Error('BasePrimaryAdapter est une classe abstraite'); } } /** * Traite la requête entrante */ async handleRequest(req, res) { throw new Error('La méthode handleRequest() doit être implémentée'); } /** * Valide les données de la requête */ validateRequest(req) { return { isValid: true, errors: [] }; } /** * Réponse de succès */ success(res, data, message = 'Opération réussie', statusCode = 200) { return res.status(statusCode).json({ success: true, message, data, timestamp: new Date().toISOString() }); } /** * Réponse d'erreur */ error(res, message = 'Une erreur est survenue', statusCode = 500) { return res.status(statusCode).json({ success: false, message, timestamp: new Date().toISOString() }); } } module.exports = BasePrimaryAdapter; `; } getSecondaryAdapterBaseTemplate(config) { return `/** * Adapter secondaire de base (sortie) */ class BaseSecondaryAdapter { constructor() { if (this.constructor === BaseSecondaryAdapter) { throw new Error('BaseSecondaryAdapter est une classe abstraite'); } } /** * Exécute l'opération de l'adapter */ async execute(input) { throw new Error('La méthode execute() doit être implémentée'); } /** * Valide les données d'entrée */ validateInput(input) { return { isValid: true, errors: [] }; } /** * Gère les erreurs */ handleError(error) { console.error('Erreur dans l\'adapter secondaire:', error); throw error; } } module.exports = BaseSecondaryAdapter; `; } getRoutesIndexTemplate(config) { return `const express = require('express'); const router = express.Router(); router.get('/', (req, res) => { res.json({ message: 'API ${config.projectName} - Hexagonal Architecture', architecture: 'Hexagonal Architecture', concept: 'Ports & Adapters', adapters: ['Primary (Entrée)', 'Secondary (Sortie)'] }); }); module.exports = router; `; } } module.exports = { HexagonalArchitecture };