create-gef 1.0.0 → 1.1.1
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.
- package/.gef/ENGINEERING_PLAYBOOK.md +141 -0
- package/.gef/prompts/adr_writing.md +50 -0
- package/.gef/prompts/bugfix.md +31 -0
- package/.gef/prompts/code_review.md +40 -0
- package/.gef/prompts/feature_development.md +37 -0
- package/.gef/prompts/new_project_kickoff.md +41 -0
- package/.gef/prompts/system_prompt.md +41 -0
- package/.github/workflows/release-please.yml +19 -0
- package/CHANGELOG.md +71 -0
- package/ENGINEERING_PLAYBOOK.md +88 -341
- package/PROJECT_CONFIG.template.md +15 -28
- package/README.md +87 -20
- package/generator/cli/help.js +65 -0
- package/generator/cli/questions.js +98 -0
- package/generator/features/scaffold-ci.js +441 -0
- package/generator/features/scaffold-docker.js +159 -0
- package/generator/features/scaffold-gef.js +158 -0
- package/generator/features/scaffold-git.js +138 -0
- package/generator/features/scaffold-linter.js +91 -0
- package/generator/features/scaffold-stack.js +102 -0
- package/generator/features/update.js +61 -0
- package/generator/index.js +37 -668
- package/generator/templates/adr-template.md +22 -0
- package/hooks/commit-msg +4 -3
- package/hooks/pre-commit +2 -2
- package/package.json +1 -1
- package/prompts/adr_writing.md +45 -9
- package/prompts/bugfix.md +24 -8
- package/prompts/code_review.md +34 -8
- package/prompts/feature_development.md +10 -1
- package/prompts/new_project_kickoff.md +33 -6
- package/prompts/system_prompt.md +30 -13
- package/website/.oxlintrc.json +8 -0
- package/website/README.md +16 -0
- package/website/index.html +13 -0
- package/website/package-lock.json +1372 -0
- package/website/package.json +25 -0
- package/website/public/favicon.svg +1 -0
- package/website/public/icons.svg +24 -0
- package/website/src/App.css +1 -0
- package/website/src/App.tsx +167 -0
- package/website/src/assets/hero.png +0 -0
- package/website/src/assets/react.svg +1 -0
- package/website/src/assets/vite.svg +1 -0
- package/website/src/components/FeatureCard.tsx +28 -0
- package/website/src/components/TerminalDemo.tsx +84 -0
- package/website/src/index.css +152 -0
- package/website/src/main.tsx +10 -0
- package/website/tsconfig.app.json +26 -0
- package/website/tsconfig.json +7 -0
- package/website/tsconfig.node.json +23 -0
- package/website/vite.config.ts +7 -0
- package/hooks/pre-push +0 -25
package/generator/index.js
CHANGED
|
@@ -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 {
|
|
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
|
|
12
|
-
|
|
19
|
+
const GEF_DIR = path.resolve(path.dirname(__filename), '..');
|
|
20
|
+
|
|
21
|
+
const arg = process.argv[2];
|
|
13
22
|
|
|
14
23
|
async function run() {
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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, 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
|
-
|
|
199
|
-
|
|
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
|
});
|