create-gef 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.gef/ENGINEERING_PLAYBOOK.md +141 -0
  2. package/.gef/prompts/adr_writing.md +50 -0
  3. package/.gef/prompts/bugfix.md +31 -0
  4. package/.gef/prompts/code_review.md +40 -0
  5. package/.gef/prompts/feature_development.md +37 -0
  6. package/.gef/prompts/new_project_kickoff.md +41 -0
  7. package/.gef/prompts/system_prompt.md +41 -0
  8. package/.github/workflows/release-please.yml +19 -0
  9. package/CHANGELOG.md +63 -0
  10. package/ENGINEERING_PLAYBOOK.md +88 -341
  11. package/PROJECT_CONFIG.template.md +15 -28
  12. package/README.md +87 -20
  13. package/generator/cli/help.js +65 -0
  14. package/generator/cli/questions.js +98 -0
  15. package/generator/features/scaffold-ci.js +441 -0
  16. package/generator/features/scaffold-docker.js +159 -0
  17. package/generator/features/scaffold-gef.js +158 -0
  18. package/generator/features/scaffold-git.js +138 -0
  19. package/generator/features/scaffold-linter.js +91 -0
  20. package/generator/features/scaffold-stack.js +101 -0
  21. package/generator/features/update.js +61 -0
  22. package/generator/index.js +37 -668
  23. package/generator/templates/adr-template.md +22 -0
  24. package/hooks/commit-msg +4 -3
  25. package/hooks/pre-commit +2 -2
  26. package/package.json +1 -1
  27. package/prompts/adr_writing.md +45 -9
  28. package/prompts/bugfix.md +24 -8
  29. package/prompts/code_review.md +34 -8
  30. package/prompts/feature_development.md +10 -1
  31. package/prompts/new_project_kickoff.md +33 -6
  32. package/prompts/system_prompt.md +30 -13
  33. package/website/.oxlintrc.json +8 -0
  34. package/website/README.md +16 -0
  35. package/website/index.html +13 -0
  36. package/website/package-lock.json +1372 -0
  37. package/website/package.json +25 -0
  38. package/website/public/favicon.svg +1 -0
  39. package/website/public/icons.svg +24 -0
  40. package/website/src/App.css +1 -0
  41. package/website/src/App.tsx +167 -0
  42. package/website/src/assets/hero.png +0 -0
  43. package/website/src/assets/react.svg +1 -0
  44. package/website/src/assets/vite.svg +1 -0
  45. package/website/src/components/FeatureCard.tsx +28 -0
  46. package/website/src/components/TerminalDemo.tsx +84 -0
  47. package/website/src/index.css +152 -0
  48. package/website/src/main.tsx +10 -0
  49. package/website/tsconfig.app.json +26 -0
  50. package/website/tsconfig.json +7 -0
  51. package/website/tsconfig.node.json +23 -0
  52. package/website/vite.config.ts +7 -0
  53. package/hooks/pre-push +0 -25
@@ -5,109 +5,36 @@ import chalk from 'chalk';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import { fileURLToPath } from 'url';
8
- import { execSync, spawnSync } from 'child_process';
8
+ import { PROJECT_QUESTIONS } from './cli/questions.js';
9
+ import { printHelp, printVersion } from './cli/help.js';
10
+ import { runUpdate } from './features/update.js';
11
+ import { scaffoldStack } from './features/scaffold-stack.js';
12
+ import { scaffoldDocker } from './features/scaffold-docker.js';
13
+ import { scaffoldCI } from './features/scaffold-ci.js';
14
+ import { scaffoldGit } from './features/scaffold-git.js';
15
+ import { scaffoldGef } from './features/scaffold-gef.js';
16
+ import { scaffoldLinter } from './features/scaffold-linter.js';
9
17
 
10
18
  const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = path.dirname(__filename);
12
- const GEF_DIR = path.resolve(__dirname, '..');
19
+ const GEF_DIR = path.resolve(path.dirname(__filename), '..');
20
+
21
+ const arg = process.argv[2];
13
22
 
14
23
  async function run() {
15
- console.log(chalk.cyan.bold('\n🚀 Bienvenue dans le générateur GEF Intelligent\n'));
16
- if (process.argv[2] === 'update') {
17
- console.log(chalk.blue('Mise à jour du projet courant vers la dernière version du GEF...'));
18
- if (!fs.existsSync('.git')) {
19
- console.log(chalk.red('Erreur: Ce dossier ne semble pas être un dépôt Git valide.'));
20
- process.exit(1);
21
- }
22
-
23
- // MAJ Playbook
24
- if (fs.existsSync(path.join(GEF_DIR, 'ENGINEERING_PLAYBOOK.md'))) {
25
- fs.mkdirSync('.gef', { recursive: true });
26
- fs.copyFileSync(path.join(GEF_DIR, 'ENGINEERING_PLAYBOOK.md'), '.gef/ENGINEERING_PLAYBOOK.md');
27
- console.log(chalk.green('✅ ENGINEERING_PLAYBOOK.md mis à jour.'));
28
- }
29
-
30
- // MAJ Prompts
31
- if (fs.existsSync(path.join(GEF_DIR, 'prompts'))) {
32
- fs.mkdirSync('.gef/prompts', { recursive: true });
33
- fs.readdirSync(path.join(GEF_DIR, 'prompts')).forEach(p => {
34
- fs.copyFileSync(path.join(GEF_DIR, 'prompts', p), path.join('.gef/prompts', p));
35
- });
36
- console.log(chalk.green('✅ Prompts IA mis à jour.'));
37
- }
38
-
39
- // MAJ Hooks
40
- const hooksSrc = path.join(GEF_DIR, 'hooks');
41
- if (fs.existsSync(hooksSrc)) {
42
- const hooksDest = path.join(process.cwd(), '.git', 'hooks');
43
- fs.cpSync(hooksSrc, hooksDest, { recursive: true });
44
- try {
45
- execSync(`chmod +x .git/hooks/commit-msg .git/hooks/pre-commit .git/hooks/pre-push`, { stdio: 'ignore' });
46
- } catch(e) {}
47
- console.log(chalk.green('✅ Hooks Git mis à jour.'));
48
- }
49
-
50
- console.log(chalk.green.bold('\n🎉 Projet mis à jour avec succès !'));
51
- return;
52
- }
24
+ // Gestion des commandes et flags
25
+ if (arg === '--help' || arg === '-h') return printHelp();
26
+ if (arg === '--version' || arg === '-v') return printVersion();
27
+ if (arg === 'update') return runUpdate(GEF_DIR);
53
28
 
54
- const answers = await inquirer.prompt([
55
- {
56
- type: 'input',
57
- name: 'projectName',
58
- message: 'Quel est le nom de votre nouveau projet ?',
59
- validate: input => input ? true : 'Le nom du projet est requis.'
60
- },
61
- {
62
- type: 'list',
63
- name: 'phase',
64
- message: 'Dans quelle phase se situe ce projet ?',
65
- choices: ['Prototype (R&D)', 'Développement contractuel / Production']
66
- },
67
- {
68
- type: 'list',
69
- name: 'stack',
70
- message: 'Quel Framework principal voulez-vous installer ?',
71
- choices: [
72
- 'Next.js (React)',
73
- 'React (Vite)',
74
- 'API Node.js (Express)',
75
- 'API Python (FastAPI)',
76
- 'Projet vide'
77
- ]
78
- },
79
- {
80
- type: 'list',
81
- name: 'database',
82
- message: 'Quelle Base de données principale ?',
83
- choices: ['PostgreSQL', 'MongoDB', 'Supabase', 'Aucune']
84
- },
85
- {
86
- type: 'list',
87
- name: 'cloud',
88
- message: 'Quel Cloud Provider principal ?',
89
- choices: ['AWS', 'GCP', 'Azure', 'Vercel', 'Aucun']
90
- },
91
- {
92
- type: 'confirm',
93
- name: 'includeDocker',
94
- message: 'Voulez-vous préparer un dossier Docker ?',
95
- default: true,
96
- when: (ans) => ans.cloud !== 'Vercel'
97
- },
98
- {
99
- type: 'confirm',
100
- name: 'includeCI',
101
- message: 'Voulez-vous inclure le template de CI/CD GEF ?',
102
- default: true
103
- }
104
- ]);
29
+ // Mode interactif par défaut
30
+ console.log(chalk.cyan.bold('\n🚀 Bienvenue dans le générateur GEF Intelligent\n'));
31
+ console.log(chalk.dim(' Tapez `npx create-gef --help` pour voir toutes les commandes.\n'));
105
32
 
106
- const { projectName, phase, stack, database, cloud, includeDocker, includeCI } = answers;
107
- const projectPath = path.resolve(process.cwd(), projectName);
33
+ const answers = await inquirer.prompt(PROJECT_QUESTIONS);
34
+ const projectPath = path.resolve(process.cwd(), answers.projectName);
108
35
 
109
36
  if (fs.existsSync(projectPath)) {
110
- console.log(chalk.red(`\nErreur: Le dossier "${projectName}" existe déjà.`));
37
+ console.log(chalk.red(`\nErreur: Le dossier "${answers.projectName}" existe déjà.`));
111
38
  process.exit(1);
112
39
  }
113
40
 
@@ -115,585 +42,27 @@ async function run() {
115
42
  fs.mkdirSync(projectPath, { recursive: true });
116
43
  process.chdir(projectPath);
117
44
 
118
- // --- SCAFFOLDING INTELLIGENT ---
119
- if (stack !== 'Projet vide') {
120
- try {
121
- if (stack === 'Next.js (React)') {
122
- console.log(chalk.magenta(`\n▲ Lancement de create-next-app — répondez aux questions ci-dessous :\n`));
123
- // On sort du dossier créé : create-next-app crée lui-même le sous-dossier
124
- process.chdir(path.resolve(projectPath, '..'));
125
- fs.rmdirSync(projectPath); // on enlève le dossier vide pour laisser create-next-app le créer
126
- execSync(`npx create-next-app@latest ${projectName}`, { stdio: 'inherit' });
127
- process.chdir(projectPath);
128
- }
129
- else if (stack === 'React (Vite)') {
130
- console.log(chalk.magenta(`\n⚡ Lancement de create-vite — répondez aux questions ci-dessous :\n`));
131
- process.chdir(path.resolve(projectPath, '..'));
132
- fs.rmdirSync(projectPath);
133
- execSync(`npm create vite@latest ${projectName}`, { stdio: 'inherit' });
134
- process.chdir(projectPath);
135
- // Vite ne lance pas npm install automatiquement
136
- console.log(chalk.yellow('\nInstallation des dépendances...'));
137
- execSync('npm install', { stdio: 'inherit' });
138
- }
139
- else if (stack === 'API Node.js (Express)') {
140
- console.log(chalk.magenta(`\n📦 Initialisation de l'API Node.js...\n`));
141
- execSync('npm init -y', { stdio: 'ignore' });
142
- execSync('npm install express cors dotenv', { stdio: 'inherit' });
143
- if (!fs.existsSync('src')) fs.mkdirSync('src');
144
- fs.writeFileSync('src/index.js', `const express = require('express');
145
- const cors = require('cors');
146
- require('dotenv').config();
147
-
148
- const app = express();
149
- const PORT = process.env.PORT || 3000;
150
-
151
- app.use(cors());
152
- app.use(express.json());
153
-
154
- app.get('/', (req, res) => res.json({ message: 'API prête.' }));
155
-
156
- app.listen(PORT, () => console.log(\`Serveur lancé sur http://localhost:\${PORT}\`));
157
- `);
158
- const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
159
- pkg.scripts = { start: 'node src/index.js', dev: 'node --watch src/index.js' };
160
- fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
161
- }
162
- else if (stack === 'API Python (FastAPI)') {
163
- console.log(chalk.magenta(`\n🐍 Initialisation de l'API Python (FastAPI)...\n`));
164
- const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
165
- execSync(`${pythonCmd} -m venv venv`, { stdio: 'inherit' });
166
- fs.writeFileSync('requirements.txt', 'fastapi\nuvicorn[standard]\npython-dotenv\n');
167
- if (!fs.existsSync('src')) fs.mkdirSync('src');
168
- fs.writeFileSync('src/__init__.py', '');
169
- fs.writeFileSync('src/main.py', `from fastapi import FastAPI
170
-
171
- app = FastAPI(title="${projectName}")
172
-
173
- @app.get("/")
174
- def read_root():
175
- return {"message": "API prête."}
176
- `);
177
- }
178
- console.log(chalk.green('\n✅ Framework installé.'));
179
- } catch (err) {
180
- console.log(chalk.red('\nErreur lors du scaffolding. Continuation avec la structure de base GEF.'));
181
- }
182
- }
183
-
184
- // --- SURCOUCHE GEF ---
185
- console.log(chalk.yellow('\n📁 Application de la surcouche GEF (Architecture & Config)...'));
186
-
187
- // Création des dossiers manquants
188
- const dirs = [
189
- 'docs/adr', 'docs/research', 'src', 'tests', 'scripts',
190
- 'assets', 'infra', 'database'
191
- ];
192
- if (includeCI) dirs.push('.github/workflows');
193
-
194
- dirs.forEach(d => {
195
- if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
45
+ scaffoldStack(answers.stack, answers.projectName, projectPath);
46
+ scaffoldLinter(answers.linter, answers.stack);
47
+ scaffoldGef(answers, GEF_DIR);
48
+ if (answers.includeDocker) scaffoldDocker(answers.stack, answers.database, answers.projectName);
49
+ if (answers.includeCI) scaffoldCI(answers.stack, answers.cloud, answers.projectName, {
50
+ database: answers.database,
51
+ strictness: answers.strictness,
52
+ linter: answers.linter,
53
+ gitWorkflow: answers.gitWorkflow,
54
+ containerRegistry: answers.containerRegistry,
55
+ includeDocker: answers.includeDocker,
196
56
  });
57
+ scaffoldGit(GEF_DIR, answers.gitWorkflow, answers.linter, answers.strictness);
197
58
 
198
- // Template ADR
199
- const adrTemplatePath = 'docs/adr/0000-template.md';
200
- if (!fs.existsSync(adrTemplatePath)) {
201
- fs.writeFileSync(adrTemplatePath, `# Titre de l'Architecture Decision Record\n\n## Contexte\nPourquoi prenons-nous cette décision ?\n\n## Décision\nQu'avons-nous décidé de faire ?\n\n## Conséquences\nQuels sont les impacts positifs et négatifs ?`);
202
- }
203
-
204
- // Playwright natif (Test E2E)
205
- if (stack.includes('React') || stack.includes('Next')) {
206
- console.log(chalk.yellow('🎭 Installation de Playwright pour le TDD E2E...'));
207
- try {
208
- execSync('npm init playwright@latest --yes -- --quiet --browser=chromium --browser=firefox --browser=webkit', { stdio: 'ignore' });
209
- console.log(chalk.green('✅ Playwright configuré.'));
210
- } catch(err) {
211
- console.log(chalk.red('Impossible d\'installer Playwright automatiquement.'));
212
- }
213
- }
214
-
215
- // --- DOCKER INTELLIGENT ---
216
- if (includeDocker) {
217
- console.log(chalk.yellow('🐳 Génération des fichiers Docker adaptés à votre stack...'));
218
- if (!fs.existsSync('docker')) fs.mkdirSync('docker');
219
-
220
- // .dockerignore (toujours présent)
221
- const dockerIgnoreLines = [
222
- 'node_modules', 'npm-debug.log', '.git', '.gitignore',
223
- '.env', '*.log', 'dist', 'build', '__pycache__', 'venv', '.venv'
224
- ];
225
- fs.writeFileSync('.dockerignore', dockerIgnoreLines.join('\n') + '\n');
226
-
227
- if (stack === 'Next.js (React)') {
228
- fs.writeFileSync('docker/Dockerfile', `# Étape 1 : Build
229
- FROM node:20-alpine AS builder
230
- WORKDIR /app
231
- COPY package*.json ./
232
- RUN npm ci
233
- COPY . .
234
- RUN npm run build
235
-
236
- # Étape 2 : Production
237
- FROM node:20-alpine AS runner
238
- WORKDIR /app
239
- ENV NODE_ENV=production
240
- COPY --from=builder /app/.next/standalone ./
241
- COPY --from=builder /app/.next/static ./.next/static
242
- COPY --from=builder /app/public ./public
243
- EXPOSE 3000
244
- CMD ["node", "server.js"]
245
- `);
246
- fs.writeFileSync('docker/docker-compose.yml', `version: '3.8'
247
- services:
248
- app:
249
- build:
250
- context: ..
251
- dockerfile: docker/Dockerfile
252
- ports:
253
- - "3000:3000"
254
- environment:
255
- - NODE_ENV=production
256
- `);
257
- }
258
-
259
- else if (stack === 'React (Vite)') {
260
- fs.writeFileSync('docker/Dockerfile', `# Étape 1 : Build
261
- FROM node:20-alpine AS builder
262
- WORKDIR /app
263
- COPY package*.json ./
264
- RUN npm ci
265
- COPY . .
266
- RUN npm run build
267
-
268
- # Étape 2 : Serveur statique Nginx
269
- FROM nginx:alpine AS runner
270
- COPY --from=builder /app/dist /usr/share/nginx/html
271
- EXPOSE 80
272
- CMD ["nginx", "-g", "daemon off;"]
273
- `);
274
- fs.writeFileSync('docker/docker-compose.yml', `version: '3.8'
275
- services:
276
- app:
277
- build:
278
- context: ..
279
- dockerfile: docker/Dockerfile
280
- ports:
281
- - "80:80"
282
- `);
283
- }
284
-
285
- else if (stack === 'API Node.js (Express)') {
286
- fs.writeFileSync('docker/Dockerfile', `FROM node:20-alpine
287
- WORKDIR /app
288
- COPY package*.json ./
289
- RUN npm ci --only=production
290
- COPY . .
291
- EXPOSE 3000
292
- CMD ["node", "src/index.js"]
293
- `);
294
- // docker-compose avec DB optionnelle
295
- let dbService = '';
296
- if (database === 'PostgreSQL') {
297
- dbService = `
298
- db:
299
- image: postgres:16-alpine
300
- environment:
301
- POSTGRES_USER: user
302
- POSTGRES_PASSWORD: password
303
- POSTGRES_DB: ${projectName.toLowerCase().replace(/\s+/g, '_')}
304
- ports:
305
- - "5432:5432"
306
- volumes:
307
- - db_data:/var/lib/postgresql/data
308
-
309
- volumes:
310
- db_data:`;
311
- } else if (database === 'MongoDB') {
312
- dbService = `
313
- db:
314
- image: mongo:7
315
- ports:
316
- - "27017:27017"
317
- volumes:
318
- - db_data:/data/db
319
-
320
- volumes:
321
- db_data:`;
322
- }
323
- fs.writeFileSync('docker/docker-compose.yml', `version: '3.8'
324
- services:
325
- app:
326
- build:
327
- context: ..
328
- dockerfile: docker/Dockerfile
329
- ports:
330
- - "3000:3000"
331
- env_file:
332
- - ../.env
333
- depends_on:
334
- - db${dbService}
335
- `);
336
- }
337
-
338
- else if (stack === 'API Python (FastAPI)') {
339
- fs.writeFileSync('docker/Dockerfile', `FROM python:3.12-slim
340
- WORKDIR /app
341
- COPY requirements.txt ./
342
- RUN pip install --no-cache-dir -r requirements.txt
343
- COPY . .
344
- EXPOSE 8000
345
- CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
346
- `);
347
- let dbService = '';
348
- if (database === 'PostgreSQL') {
349
- dbService = `
350
- db:
351
- image: postgres:16-alpine
352
- environment:
353
- POSTGRES_USER: user
354
- POSTGRES_PASSWORD: password
355
- POSTGRES_DB: ${projectName.toLowerCase().replace(/\s+/g, '_')}
356
- ports:
357
- - "5432:5432"
358
- volumes:
359
- - db_data:/var/lib/postgresql/data
360
-
361
- volumes:
362
- db_data:`;
363
- } else if (database === 'MongoDB') {
364
- dbService = `
365
- db:
366
- image: mongo:7
367
- ports:
368
- - "27017:27017"
369
- volumes:
370
- - db_data:/data/db
371
-
372
- volumes:
373
- db_data:`;
374
- }
375
- fs.writeFileSync('docker/docker-compose.yml', `version: '3.8'
376
- services:
377
- app:
378
- build:
379
- context: ..
380
- dockerfile: docker/Dockerfile
381
- ports:
382
- - "8000:8000"
383
- env_file:
384
- - ../.env
385
- depends_on:
386
- - db${dbService}
387
- `);
388
- }
389
-
390
- else {
391
- // Projet vide — Dockerfile générique
392
- fs.writeFileSync('docker/Dockerfile', `FROM alpine:latest
393
- WORKDIR /app
394
- COPY . .
395
- CMD ["sh"]
396
- `);
397
- }
398
- console.log(chalk.green('✅ Fichiers Docker générés.'));
399
- }
400
-
401
- // --- VERCEL ---
402
- if (cloud === 'Vercel') {
403
- console.log(chalk.yellow('▲ Génération de la configuration Vercel...'));
404
- const isApi = stack.includes('Node') || stack.includes('Python');
405
- const vercelConfig = {
406
- version: 2,
407
- name: projectName,
408
- ...(isApi ? {
409
- builds: [{ src: 'src/index.js', use: '@vercel/node' }],
410
- routes: [{ src: '/(.*)', dest: 'src/index.js' }]
411
- } : {}),
412
- regions: ['cdg1']
413
- };
414
- fs.writeFileSync('vercel.json', JSON.stringify(vercelConfig, null, 2));
415
- console.log(chalk.green('✅ vercel.json généré.'));
416
- }
417
-
418
- // --- SUPABASE ---
419
- if (database === 'Supabase') {
420
- console.log(chalk.yellow('⚡ Génération de la configuration Supabase...'));
421
- fs.mkdirSync('supabase/migrations', { recursive: true });
422
- fs.writeFileSync('supabase/config.toml', `[api]
423
- enabled = true
424
- port = 54321
425
- schemas = ["public", "storage", "graphql_public"]
426
-
427
- [db]
428
- port = 54322
429
- shadow_port = 54320
430
- major_version = 15
431
-
432
- [studio]
433
- enabled = true
434
- port = 54323
435
-
436
- [auth]
437
- enabled = true
438
- site_url = "http://localhost:3000"
439
-
440
- [storage]
441
- enabled = true
442
- `);
443
- fs.writeFileSync('supabase/migrations/.gitkeep', '');
444
- fs.writeFileSync('supabase/seed.sql', `-- Seed data pour ${projectName}
445
- -- Ajoutez ici vos données initiales
446
- `);
447
- console.log(chalk.green('✅ Dossier supabase/ généré (config.toml + migrations/).'));
448
- }
449
-
450
- // Mise à jour du README existant ou création
451
- const readmeHeader = `# ${projectName}\n\n## Fonctionnalités\n<À COMPLÉTER>\n\n## Installation\n<À COMPLÉTER>\n\n## Architecture\nStack: ${stack}\nCloud: ${cloud}\nDB: ${database}\n`;
452
- if (fs.existsSync('README.md')) {
453
- const existing = fs.readFileSync('README.md', 'utf8');
454
- fs.writeFileSync('README.md', readmeHeader + '\n---\n*Généré initialement par le framework:*\n' + existing);
455
- } else {
456
- fs.writeFileSync('README.md', readmeHeader);
457
- }
458
-
459
- // PROJECT_CONFIG.md
460
- let configContent = `# PROJECT_CONFIG.md\n- Projet : ${projectName}\n- Langage : ${stack}\n`;
461
- const templatePath = path.join(GEF_DIR, 'PROJECT_CONFIG.template.md');
462
- if (fs.existsSync(templatePath)) {
463
- let tpl = fs.readFileSync(templatePath, 'utf-8');
464
- tpl = tpl.replace(/{{PROJECT_NAME}}/g, projectName);
465
- tpl = tpl.replace(/{{STACK}}/g, stack);
466
- tpl = tpl.replace(/{{PHASE}}/g, phase);
467
- tpl = tpl.replace(/{{DATABASE}}/g, database);
468
- tpl = tpl.replace(/{{CLOUD}}/g, cloud);
469
- const dateStr = new Date().toLocaleString('fr-FR', { month: 'long', year: 'numeric' });
470
- tpl = tpl.replace(/{{DATE}}/g, dateStr);
471
- configContent = tpl;
472
- }
473
- fs.writeFileSync('PROJECT_CONFIG.md', configContent);
474
-
475
- // RESEARCH_LOG.md
476
- if (!fs.existsSync('docs/research/RESEARCH_LOG.md')) {
477
- fs.writeFileSync('docs/research/RESEARCH_LOG.md', `# Research Log — Journal de bord scientifique\n> Documentez ici la résolution des bugs bloquants.\n\n## 1. <Titre>\n- **Contexte :** \n- **Cause :** \n- **Résolution :** \n`);
478
- }
479
-
480
- // .gitignore dynamique additionnel
481
- let gitignoreAdditions = `\n# GEF Standard\n.env\n.DS_Store\n`;
482
- if (stack.includes('Python')) gitignoreAdditions += `__pycache__/\nvenv/\n.venv/\n.pytest_cache/\n`;
483
- if (fs.existsSync('.gitignore')) {
484
- fs.appendFileSync('.gitignore', gitignoreAdditions);
485
- } else {
486
- fs.writeFileSync('.gitignore', gitignoreAdditions);
487
- }
488
-
489
- // Copie de la documentation GEF locale (.gef/)
490
- console.log(chalk.cyan('📚 Ajout du Playbook et des Prompts IA locaux...'));
491
- fs.mkdirSync('.gef/prompts', { recursive: true });
492
- const rootDir = path.resolve(__dirname, '..');
493
- if (fs.existsSync(path.join(rootDir, 'ENGINEERING_PLAYBOOK.md'))) {
494
- fs.copyFileSync(path.join(rootDir, 'ENGINEERING_PLAYBOOK.md'), '.gef/ENGINEERING_PLAYBOOK.md');
495
- }
496
- if (fs.existsSync(path.join(rootDir, 'prompts'))) {
497
- const prompts = fs.readdirSync(path.join(rootDir, 'prompts'));
498
- prompts.forEach(p => {
499
- fs.copyFileSync(path.join(rootDir, 'prompts', p), path.join('.gef/prompts', p));
500
- });
501
- }
502
-
503
- // Git et Hooks
504
- console.log(chalk.yellow('🔗 Initialisation Git et installation des hooks de sécurité...'));
505
- try {
506
- if (!fs.existsSync('.git')) {
507
- execSync('git init', { stdio: 'ignore' });
508
- }
509
- const hooksSrc = path.join(GEF_DIR, 'hooks');
510
- if (fs.existsSync(hooksSrc)) {
511
- const hooksDest = path.join(process.cwd(), '.git', 'hooks');
512
- fs.cpSync(hooksSrc, hooksDest, { recursive: true });
513
- try {
514
- execSync(`chmod +x .git/hooks/commit-msg .git/hooks/pre-commit .git/hooks/pre-push`, { stdio: 'ignore' });
515
- } catch (e) { /* ignore on windows */ }
516
- }
517
- } catch (err) {
518
- console.log(chalk.red('Erreur lors de l\'installation des hooks Git.'));
519
- }
520
-
521
- // CI/CD — Généré selon la stack
522
- if (includeCI) {
523
- console.log(chalk.yellow('🤖 Génération du pipeline CI/CD adapté à votre stack...'));
524
- if (!fs.existsSync('.github/workflows')) fs.mkdirSync('.github/workflows', { recursive: true });
525
-
526
- const isNode = stack.includes('Node') || stack.includes('React') || stack.includes('Next');
527
- const isPython = stack.includes('Python');
528
-
529
- // Bloc setup runtime
530
- const setupRuntime = isNode
531
- ? ` - name: Setup Node.js
532
- uses: actions/setup-node@v4
533
- with:
534
- node-version: '20'
535
- cache: 'npm'
536
-
537
- - name: Installer les dépendances
538
- run: npm ci`
539
- : isPython
540
- ? ` - name: Setup Python
541
- uses: actions/setup-python@v5
542
- with:
543
- python-version: '3.12'
544
- cache: 'pip'
545
-
546
- - name: Installer les dépendances
547
- run: pip install -r requirements.txt`
548
- : ` - name: Setup
549
- run: echo "Configurez votre runtime ici"`;
550
-
551
- // Bloc tests
552
- const testBlock = isNode
553
- ? ` - name: Lint
554
- run: npm run lint --if-present
555
-
556
- - name: Tests
557
- run: npm test --if-present`
558
- : isPython
559
- ? ` - name: Lint (flake8)
560
- run: |
561
- pip install flake8
562
- flake8 src/ --max-line-length=120 --ignore=E501 || true
563
-
564
- - name: Tests (pytest)
565
- run: |
566
- pip install pytest
567
- pytest tests/ -v || true`
568
- : ` - name: Tests
569
- run: echo "Ajoutez vos commandes de tests ici"`;
570
-
571
- // Bloc déploiement
572
- let deployBlock = '';
573
- if (cloud === 'Vercel') {
574
- deployBlock = `
575
- deploy:
576
- name: Déploiement Vercel
577
- needs: quality-gate
578
- if: github.ref == 'refs/heads/main'
579
- runs-on: ubuntu-latest
580
- steps:
581
- - uses: actions/checkout@v4
582
- - name: Deploy to Vercel
583
- uses: amondnet/vercel-action@v25
584
- with:
585
- vercel-token: \${{ secrets.VERCEL_TOKEN }}
586
- vercel-org-id: \${{ secrets.VERCEL_ORG_ID }}
587
- vercel-project-id: \${{ secrets.VERCEL_PROJECT_ID }}
588
- vercel-args: '--prod'`;
589
- } else if (cloud === 'AWS') {
590
- deployBlock = `
591
- deploy:
592
- name: Déploiement AWS
593
- needs: quality-gate
594
- if: startsWith(github.ref, 'refs/tags/v')
595
- runs-on: ubuntu-latest
596
- steps:
597
- - uses: actions/checkout@v4
598
- - name: Configure AWS credentials
599
- uses: aws-actions/configure-aws-credentials@v4
600
- with:
601
- aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
602
- aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
603
- aws-region: eu-west-3
604
- - name: Deploy
605
- run: echo "Ajoutez ici votre commande de déploiement AWS (ex: eb deploy, ecs update, etc.)"`;
606
- } else {
607
- deployBlock = `
608
- release:
609
- name: Release Automatique
610
- needs: quality-gate
611
- if: startsWith(github.ref, 'refs/tags/v')
612
- runs-on: ubuntu-latest
613
- steps:
614
- - uses: actions/checkout@v4
615
- - name: Create GitHub Release
616
- uses: softprops/action-gh-release@v1
617
- with:
618
- generate_release_notes: true`;
619
- }
620
-
621
- const ciContent = `name: GEF CI/CD — ${projectName}
622
-
623
- on:
624
- push:
625
- branches:
626
- - main
627
- - 'feat/**'
628
- - 'fix/**'
629
- tags:
630
- - 'v*.*.*'
631
- pull_request:
632
- branches:
633
- - main
634
-
635
- jobs:
636
- quality-gate:
637
- name: Contrôle Qualité
638
- runs-on: ubuntu-latest
639
- steps:
640
- - name: Checkout code
641
- uses: actions/checkout@v4
642
-
643
- ${setupRuntime}
644
-
645
- ${testBlock}
646
-
647
- - name: Analyse de sécurité
648
- run: echo "Ajoutez ici Trivy, Gitleaks ou équivalent"
649
- ${deployBlock}
650
- `;
651
- fs.writeFileSync('.github/workflows/main.yml', ciContent);
652
- console.log(chalk.green('✅ Pipeline CI/CD généré.'));
653
-
654
- console.log(chalk.yellow('📦 Intégration native de release-please...'));
655
- const releasePleaseContent = `on:
656
- push:
657
- branches:
658
- - main
659
-
660
- permissions:
661
- contents: write
662
- pull-requests: write
663
-
664
- name: release-please
665
-
666
- jobs:
667
- release-please:
668
- runs-on: ubuntu-latest
669
- steps:
670
- - uses: googleapis/release-please-action@v4
671
- with:
672
- release-type: node
673
- `;
674
- fs.writeFileSync('.github/workflows/release-please.yml', releasePleaseContent);
675
- console.log(chalk.green('✅ Workflow release-please.yml généré.'));
676
- }
677
-
678
- if (!fs.existsSync('CHANGELOG.md')) fs.writeFileSync('CHANGELOG.md', '');
679
- if (!fs.existsSync('LICENSE')) fs.writeFileSync('LICENSE', '');
680
-
681
- console.log(chalk.green.bold(`\n✅ Projet "${projectName}" scaffoldé avec succès !`));
682
- console.log(chalk.cyan(`\nPour commencer :`));
683
- console.log(chalk.white(` cd ${projectName}`));
684
- if (stack.includes('React') || stack.includes('Node')) {
685
- console.log(chalk.white(` npm run dev`));
686
- } else if (stack.includes('Python')) {
687
- console.log(chalk.white(` .\\venv\\Scripts\\activate (ou source venv/bin/activate)`));
688
- console.log(chalk.white(` uvicorn src.main:app --reload`));
689
- }
690
- console.log(chalk.white(`\nN'oubliez pas d'éditer PROJECT_CONFIG.md et de faire votre premier commit !`));
59
+ console.log(chalk.green.bold(`\n✅ Projet "${answers.projectName}" scaffoldé avec succès !`));
60
+ console.log(chalk.dim(`\n cd ${answers.projectName} && git status\n`));
691
61
  }
692
62
 
693
- // Verrou anti-double-exécution (npm charge parfois le fichier via 'main' ET via 'bin')
694
63
  if (!process.env._GEF_RUNNING) {
695
64
  process.env._GEF_RUNNING = '1';
696
- run().catch(err => {
65
+ run().catch((err) => {
697
66
  console.error(chalk.red('Une erreur est survenue :'), err);
698
67
  process.exit(1);
699
68
  });