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.
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 +71 -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 +102 -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
@@ -0,0 +1,441 @@
1
+ // features/scaffold-ci.js — Pipeline CI/CD complet et progressif selon la sévérité
2
+ // Réf. Playbook §1 : SRP. §4 : OWASP. §6 : CI/CD
3
+
4
+ import fs from 'fs';
5
+ import chalk from 'chalk';
6
+
7
+ // ─────────────────────────────────────────────
8
+ // BLOC : Trigger / On
9
+ // ─────────────────────────────────────────────
10
+ function buildTriggers(gitWorkflow) {
11
+ const branchFilter = gitWorkflow?.includes('Trunk')
12
+ ? ` branches:\n - main`
13
+ : ` branches:\n - main\n - 'feat/**'\n - 'fix/**'`;
14
+
15
+ return `on:
16
+ push:
17
+ ${branchFilter}
18
+ tags:
19
+ - 'v*.*.*'
20
+ pull_request:
21
+ branches:
22
+ - main`;
23
+ }
24
+
25
+ // ─────────────────────────────────────────────
26
+ // BLOC : Services DB éphémères pour les tests
27
+ // ─────────────────────────────────────────────
28
+ function buildServiceBlock(database) {
29
+ if (database === 'PostgreSQL') {
30
+ return ` services:
31
+ postgres:
32
+ image: postgres:16-alpine
33
+ env:
34
+ POSTGRES_USER: test
35
+ POSTGRES_PASSWORD: test
36
+ POSTGRES_DB: test_db
37
+ ports:
38
+ - 5432:5432
39
+ options: >-
40
+ --health-cmd pg_isready
41
+ --health-interval 10s
42
+ --health-timeout 5s
43
+ --health-retries 5`;
44
+ }
45
+ if (database === 'MongoDB') {
46
+ return ` services:
47
+ mongo:
48
+ image: mongo:7
49
+ ports:
50
+ - 27017:27017
51
+ options: >-
52
+ --health-cmd "mongosh --eval 'db.runCommand({ ping: 1 })'"
53
+ --health-interval 10s
54
+ --health-timeout 5s
55
+ --health-retries 5`;
56
+ }
57
+ return '';
58
+ }
59
+
60
+ // ─────────────────────────────────────────────
61
+ // BLOC : Setup runtime
62
+ // ─────────────────────────────────────────────
63
+ function buildRuntimeBlock(isNode, isPython) {
64
+ if (isNode) {
65
+ return ` - name: Setup Node.js
66
+ uses: actions/setup-node@v4
67
+ with:
68
+ node-version: '20'
69
+ cache: 'npm'
70
+
71
+ - name: Installer les dépendances
72
+ run: npm ci`;
73
+ }
74
+ if (isPython) {
75
+ return ` - name: Setup Python
76
+ uses: actions/setup-python@v5
77
+ with:
78
+ python-version: '3.12'
79
+ cache: 'pip'
80
+
81
+ - name: Installer les dépendances
82
+ run: pip install -r requirements.txt`;
83
+ }
84
+ return ` - name: Setup\n run: echo "Configurez votre runtime ici"`;
85
+ }
86
+
87
+ // ─────────────────────────────────────────────
88
+ // BLOC : Lint
89
+ // ─────────────────────────────────────────────
90
+ function buildLintBlock(isNode, isPython, linter) {
91
+ if (isNode) {
92
+ const cmd = linter?.includes('Biome') ? 'npx biome check .' : 'npm run lint --if-present';
93
+ return ` - name: Lint\n run: ${cmd}`;
94
+ }
95
+ if (isPython) {
96
+ const cmd = linter?.includes('Ruff') ? 'ruff check .' : 'flake8 src/ --max-line-length=120 || true';
97
+ return ` - name: Lint\n run: |\n pip install ${linter?.includes('Ruff') ? 'ruff' : 'flake8'}\n ${cmd}`;
98
+ }
99
+ return '';
100
+ }
101
+
102
+ // ─────────────────────────────────────────────
103
+ // BLOC : Audit de dépendances
104
+ // ─────────────────────────────────────────────
105
+ function buildAuditBlock(isNode, isPython) {
106
+ if (isNode) {
107
+ return ` - name: Audit des dépendances
108
+ run: npm audit --audit-level=high`;
109
+ }
110
+ if (isPython) {
111
+ return ` - name: Audit des dépendances
112
+ run: |
113
+ pip install pip-audit
114
+ pip-audit`;
115
+ }
116
+ return '';
117
+ }
118
+
119
+ // ─────────────────────────────────────────────
120
+ // BLOC : Tests + coverage
121
+ // ─────────────────────────────────────────────
122
+ function buildTestBlock(isNode, isPython, database) {
123
+ const dbEnv = database === 'PostgreSQL'
124
+ ? `\n env:\n DATABASE_URL: postgresql://test:test@localhost:5432/test_db`
125
+ : database === 'MongoDB'
126
+ ? `\n env:\n MONGODB_URI: mongodb://localhost:27017/test_db`
127
+ : '';
128
+
129
+ if (isNode) {
130
+ return ` - name: Tests (avec couverture)
131
+ run: npm test -- --coverage --if-present${dbEnv}
132
+
133
+ - name: Upload Coverage
134
+ uses: codecov/codecov-action@v4
135
+ with:
136
+ fail_ci_if_error: false`;
137
+ }
138
+ if (isPython) {
139
+ return ` - name: Tests (pytest + coverage)
140
+ run: |
141
+ pip install pytest pytest-cov
142
+ pytest tests/ -v --cov=src --cov-report=xml${dbEnv}
143
+
144
+ - name: Upload Coverage
145
+ uses: codecov/codecov-action@v4
146
+ with:
147
+ fail_ci_if_error: false`;
148
+ }
149
+ return ` - name: Tests\n run: echo "Ajoutez vos commandes de tests ici"`;
150
+ }
151
+
152
+ // ─────────────────────────────────────────────
153
+ // BLOC : Scan de sécurité (Gitleaks + Trivy FS)
154
+ // ─────────────────────────────────────────────
155
+ function buildSecurityScanBlock() {
156
+ return ` - name: Scan secrets (Gitleaks)
157
+ uses: gitleaks/gitleaks-action@v2
158
+ env:
159
+ GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
160
+
161
+ - name: Scan filesystem (Trivy)
162
+ uses: aquasecurity/trivy-action@master
163
+ with:
164
+ scan-type: 'fs'
165
+ scan-ref: '.'
166
+ format: 'table'
167
+ exit-code: '1'
168
+ severity: 'CRITICAL,HIGH'`;
169
+ }
170
+
171
+ // ─────────────────────────────────────────────
172
+ // BLOC : Build & Push image Docker
173
+ // ─────────────────────────────────────────────
174
+ function buildDockerImageBlock(projectName, containerRegistry, cloud) {
175
+ if (!containerRegistry || containerRegistry.includes('Aucun')) return '';
176
+
177
+ let loginStep = '';
178
+ let imageTag = '';
179
+
180
+ if (containerRegistry.includes('Docker Hub')) {
181
+ loginStep = ` - name: Login Docker Hub
182
+ uses: docker/login-action@v3
183
+ with:
184
+ username: \${{ secrets.DOCKERHUB_USERNAME }}
185
+ password: \${{ secrets.DOCKERHUB_TOKEN }}`;
186
+ imageTag = `\${{ secrets.DOCKERHUB_USERNAME }}/${projectName}:\${{ github.sha }}`;
187
+ } else if (containerRegistry.includes('GHCR')) {
188
+ loginStep = ` - name: Login GitHub Container Registry
189
+ uses: docker/login-action@v3
190
+ with:
191
+ registry: ghcr.io
192
+ username: \${{ github.actor }}
193
+ password: \${{ secrets.GITHUB_TOKEN }}`;
194
+ imageTag = `ghcr.io/\${{ github.repository_owner }}/${projectName}:\${{ github.sha }}`;
195
+ } else if (containerRegistry.includes('ECR')) {
196
+ loginStep = ` - name: Login AWS ECR
197
+ uses: aws-actions/amazon-ecr-login@v2
198
+ with:
199
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
200
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
201
+ aws-region: eu-west-3`;
202
+ imageTag = `\${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.eu-west-3.amazonaws.com/${projectName}:\${{ github.sha }}`;
203
+ }
204
+
205
+ return `
206
+ build-image:
207
+ name: Build & Push Image Docker
208
+ needs: quality-gate
209
+ runs-on: ubuntu-latest
210
+ outputs:
211
+ image: \${{ steps.meta.outputs.tags }}
212
+ steps:
213
+ - uses: actions/checkout@v4
214
+
215
+ ${loginStep}
216
+
217
+ - name: Setup Docker Buildx
218
+ uses: docker/setup-buildx-action@v3
219
+
220
+ - name: Build et Push l'image
221
+ id: meta
222
+ uses: docker/build-push-action@v5
223
+ with:
224
+ context: ./docker
225
+ push: \${{ github.event_name != 'pull_request' }}
226
+ tags: ${imageTag}
227
+ cache-from: type=gha
228
+ cache-to: type=gha,mode=max
229
+
230
+ - name: Scan image Docker (Trivy)
231
+ uses: aquasecurity/trivy-action@master
232
+ with:
233
+ image-ref: ${imageTag}
234
+ format: 'table'
235
+ exit-code: '1'
236
+ severity: 'CRITICAL'`;
237
+ }
238
+
239
+ // ─────────────────────────────────────────────
240
+ // BLOC : Deploy Staging + Health Check (Mission Critical)
241
+ // ─────────────────────────────────────────────
242
+ function buildStagingBlock(isMissionCritical, hasDockerImage) {
243
+ if (!isMissionCritical) return '';
244
+ const needs = hasDockerImage ? 'build-image' : 'quality-gate';
245
+
246
+ return `
247
+ deploy-staging:
248
+ name: Déploiement Staging
249
+ needs: ${needs}
250
+ if: github.ref == 'refs/heads/main'
251
+ runs-on: ubuntu-latest
252
+ environment: staging
253
+ steps:
254
+ - uses: actions/checkout@v4
255
+
256
+ - name: Deploy to Staging
257
+ run: |
258
+ echo "Ajoutez ici votre commande de déploiement vers l'environnement staging"
259
+ # Exemple : kubectl set image deployment/app app=IMAGE_TAG
260
+
261
+ - name: Health Check Staging
262
+ run: |
263
+ echo "Attente du démarrage de l'application..."
264
+ sleep 15
265
+ for i in {1..5}; do
266
+ STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://staging.votre-domaine.com/health || true)
267
+ if [ "$STATUS" = "200" ]; then
268
+ echo "✅ Health check OK (tentative $i)"
269
+ exit 0
270
+ fi
271
+ echo "⏳ Tentative $i/5 échouée (status: $STATUS), nouvel essai dans 10s..."
272
+ sleep 10
273
+ done
274
+ echo "❌ Health check staging échoué après 5 tentatives"
275
+ exit 1
276
+
277
+ rollback-staging:
278
+ name: Rollback si Staging échoue
279
+ needs: deploy-staging
280
+ if: failure()
281
+ runs-on: ubuntu-latest
282
+ steps:
283
+ - name: Rollback
284
+ run: |
285
+ echo "⚠️ Déploiement staging en échec. Rollback vers la version précédente."
286
+ echo "Ajoutez ici votre commande de rollback (ex: kubectl rollout undo deployment/app)"`;
287
+ }
288
+
289
+ // ─────────────────────────────────────────────
290
+ // BLOC : Deploy Production
291
+ // ─────────────────────────────────────────────
292
+ function buildDeployBlock(cloud, isMissionCritical, hasDockerImage) {
293
+ const needs = isMissionCritical ? 'deploy-staging' : (hasDockerImage ? 'build-image' : 'quality-gate');
294
+ const condition = isMissionCritical
295
+ ? `github.ref == 'refs/heads/main' && needs.deploy-staging.result == 'success'`
296
+ : `github.ref == 'refs/heads/main'`;
297
+
298
+ if (cloud === 'Vercel') {
299
+ return `
300
+ deploy-production:
301
+ name: Déploiement Production (Vercel)
302
+ needs: ${needs}
303
+ if: ${condition}
304
+ runs-on: ubuntu-latest
305
+ environment: production
306
+ steps:
307
+ - uses: actions/checkout@v4
308
+ - name: Deploy to Vercel
309
+ uses: amondnet/vercel-action@v25
310
+ with:
311
+ vercel-token: \${{ secrets.VERCEL_TOKEN }}
312
+ vercel-org-id: \${{ secrets.VERCEL_ORG_ID }}
313
+ vercel-project-id: \${{ secrets.VERCEL_PROJECT_ID }}
314
+ vercel-args: '--prod'`;
315
+ }
316
+
317
+ if (cloud === 'AWS') {
318
+ return `
319
+ deploy-production:
320
+ name: Déploiement Production (AWS)
321
+ needs: ${needs}
322
+ if: ${condition}
323
+ runs-on: ubuntu-latest
324
+ environment: production
325
+ steps:
326
+ - uses: actions/checkout@v4
327
+ - name: Configure AWS credentials
328
+ uses: aws-actions/configure-aws-credentials@v4
329
+ with:
330
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
331
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
332
+ aws-region: eu-west-3
333
+ - name: Deploy to AWS
334
+ run: echo "Ajoutez ici votre commande de déploiement AWS (ECS, EKS, Elastic Beanstalk...)"`;
335
+ }
336
+
337
+ return `
338
+ release:
339
+ name: Release Automatique
340
+ needs: ${needs}
341
+ if: startsWith(github.ref, 'refs/tags/v')
342
+ runs-on: ubuntu-latest
343
+ steps:
344
+ - uses: actions/checkout@v4
345
+ - name: Create GitHub Release
346
+ uses: softprops/action-gh-release@v1
347
+ with:
348
+ generate_release_notes: true`;
349
+ }
350
+
351
+ // ─────────────────────────────────────────────
352
+ // EXPORT PRINCIPAL
353
+ // ─────────────────────────────────────────────
354
+ export function scaffoldCI(stack, cloud, projectName, options = {}) {
355
+ console.log(chalk.yellow('🤖 Génération du pipeline CI/CD complet...'));
356
+ fs.mkdirSync('.github/workflows', { recursive: true });
357
+
358
+ const { database = 'Aucune', strictness = 'Standard', linter = 'Aucun',
359
+ gitWorkflow = 'GitHub Flow', containerRegistry = 'Aucun', includeDocker = false } = options;
360
+
361
+ const isNode = stack.includes('Node') || stack.includes('React') || stack.includes('Next');
362
+ const isPython = stack.includes('Python');
363
+ const isMissionCritical = strictness.includes('Mission Critical');
364
+ const isStandard = strictness.includes('Standard') || isMissionCritical;
365
+ const hasDockerImage = includeDocker && !containerRegistry?.includes('Aucun') && cloud !== 'Vercel';
366
+
367
+ const serviceBlock = buildServiceBlock(database);
368
+ const serviceSection = serviceBlock ? `\n${serviceBlock}\n` : '';
369
+
370
+ const securityBlock = isStandard ? `\n${buildSecurityScanBlock()}` : '';
371
+ const auditBlock = isStandard ? `\n${buildAuditBlock(isNode, isPython)}` : '';
372
+
373
+ const ciContent = `name: GEF CI/CD — ${projectName}
374
+
375
+ ${buildTriggers(gitWorkflow)}
376
+
377
+ jobs:
378
+ quality-gate:
379
+ name: Contrôle Qualité
380
+ runs-on: ubuntu-latest
381
+ ${serviceSection}
382
+ steps:
383
+ - name: Checkout code
384
+ uses: actions/checkout@v4
385
+ with:
386
+ fetch-depth: 0
387
+
388
+ ${buildRuntimeBlock(isNode, isPython)}
389
+
390
+ ${buildLintBlock(isNode, isPython, linter)}
391
+ ${auditBlock}
392
+
393
+ ${buildTestBlock(isNode, isPython, database)}
394
+ ${securityBlock}
395
+ ${hasDockerImage ? buildDockerImageBlock(projectName, containerRegistry, cloud) : ''}
396
+ ${isMissionCritical ? buildStagingBlock(true, hasDockerImage) : ''}
397
+ ${buildDeployBlock(cloud, isMissionCritical, hasDockerImage)}
398
+ `;
399
+
400
+ const RELEASE_PLEASE_YML = `on:
401
+ push:
402
+ branches:
403
+ - main
404
+
405
+ permissions:
406
+ contents: write
407
+ pull-requests: write
408
+
409
+ name: release-please
410
+
411
+ jobs:
412
+ release-please:
413
+ runs-on: ubuntu-latest
414
+ steps:
415
+ - uses: googleapis/release-please-action@v4
416
+ id: release
417
+ with:
418
+ release-type: node
419
+
420
+ - uses: actions/checkout@v4
421
+ if: \${{ steps.release.outputs.release_created }}
422
+
423
+ - uses: actions/setup-node@v4
424
+ with:
425
+ node-version: '20'
426
+ registry-url: 'https://registry.npmjs.org'
427
+ if: \${{ steps.release.outputs.release_created }}
428
+
429
+ - run: npm ci
430
+ if: \${{ steps.release.outputs.release_created }}
431
+
432
+ - run: npm publish
433
+ env:
434
+ NODE_AUTH_TOKEN: \${{ secrets.NPM_TOKEN }}
435
+ if: \${{ steps.release.outputs.release_created }}
436
+ `;
437
+
438
+ fs.writeFileSync('.github/workflows/main.yml', ciContent);
439
+ fs.writeFileSync('.github/workflows/release-please.yml', RELEASE_PLEASE_YML);
440
+ console.log(chalk.green('✅ Pipeline CI/CD complet généré.'));
441
+ }
@@ -0,0 +1,159 @@
1
+ // features/scaffold-docker.js — Génération des fichiers Docker
2
+ // Réf. Playbook §1 : SRP — une fonction par stack Docker, nesting <= 3
3
+
4
+ import fs from 'fs';
5
+ import chalk from 'chalk';
6
+
7
+ const DOCKERIGNORE = [
8
+ 'node_modules', 'npm-debug.log', '.git', '.gitignore',
9
+ '.env', '*.log', 'dist', 'build', '__pycache__', 'venv', '.venv',
10
+ ].join('\n') + '\n';
11
+
12
+ const DOCKERFILES = {
13
+ 'Next.js (React)': `# Étape 1 : Build
14
+ FROM node:20-alpine AS builder
15
+ WORKDIR /app
16
+ COPY package*.json ./
17
+ RUN npm ci
18
+ COPY . .
19
+ RUN npm run build
20
+
21
+ # Étape 2 : Production
22
+ FROM node:20-alpine AS runner
23
+ WORKDIR /app
24
+ ENV NODE_ENV=production
25
+ COPY --from=builder /app/.next/standalone ./
26
+ COPY --from=builder /app/.next/static ./.next/static
27
+ COPY --from=builder /app/public ./public
28
+ EXPOSE 3000
29
+ CMD ["node", "server.js"]
30
+ `,
31
+ 'React (Vite)': `# Étape 1 : Build
32
+ FROM node:20-alpine AS builder
33
+ WORKDIR /app
34
+ COPY package*.json ./
35
+ RUN npm ci
36
+ COPY . .
37
+ RUN npm run build
38
+
39
+ # Étape 2 : Serveur statique Nginx
40
+ FROM nginx:alpine AS runner
41
+ COPY --from=builder /app/dist /usr/share/nginx/html
42
+ EXPOSE 80
43
+ CMD ["nginx", "-g", "daemon off;"]
44
+ `,
45
+ 'API Node.js (Express)': `FROM node:20-alpine
46
+ WORKDIR /app
47
+ COPY package*.json ./
48
+ RUN npm ci --only=production
49
+ COPY . .
50
+ EXPOSE 3000
51
+ CMD ["node", "src/index.js"]
52
+ `,
53
+ 'API Python (FastAPI)': `FROM python:3.12-slim
54
+ WORKDIR /app
55
+ COPY requirements.txt ./
56
+ RUN pip install --no-cache-dir -r requirements.txt
57
+ COPY . .
58
+ EXPOSE 8000
59
+ CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
60
+ `,
61
+ default: `FROM alpine:latest
62
+ WORKDIR /app
63
+ COPY . .
64
+ CMD ["sh"]
65
+ `,
66
+ };
67
+
68
+ /**
69
+ * Génère un snippet docker-compose pour un service de base de données.
70
+ * @param {string} database
71
+ * @param {string} projectName
72
+ * @returns {string}
73
+ */
74
+ function buildDbService(database, projectName) {
75
+ const dbName = projectName.toLowerCase().replace(/\s+/g, '_');
76
+ if (database === 'PostgreSQL') {
77
+ return `
78
+ db:
79
+ image: postgres:16-alpine
80
+ environment:
81
+ POSTGRES_USER: user
82
+ POSTGRES_PASSWORD: password
83
+ POSTGRES_DB: ${dbName}
84
+ ports:
85
+ - "5432:5432"
86
+ volumes:
87
+ - db_data:/var/lib/postgresql/data
88
+ - ../database/init.sql:/docker-entrypoint-initdb.d/init.sql
89
+
90
+ volumes:
91
+ db_data:`;
92
+ }
93
+ if (database === 'MongoDB') {
94
+ return `
95
+ db:
96
+ image: mongo:7
97
+ ports:
98
+ - "27017:27017"
99
+ volumes:
100
+ - db_data:/data/db
101
+
102
+ volumes:
103
+ db_data:`;
104
+ }
105
+ return '';
106
+ }
107
+
108
+ /**
109
+ * Génère le contenu docker-compose selon la stack.
110
+ * @param {string} stack
111
+ * @param {string} database
112
+ * @param {string} projectName
113
+ * @returns {string}
114
+ */
115
+ function buildComposeContent(stack, database, projectName) {
116
+ const port = stack.includes('Python') ? '8000' : stack.includes('Vite') ? '80' : '3000';
117
+ const dbService = buildDbService(database, projectName);
118
+ const needsDb = stack.includes('Express') || stack.includes('Python');
119
+
120
+ return `version: '3.8'
121
+ services:
122
+ app:
123
+ build:
124
+ context: ..
125
+ dockerfile: docker/Dockerfile
126
+ ports:
127
+ - "${port}:${port}"
128
+ ${needsDb ? 'env_file:\n - ../.env\n depends_on:\n - db' : ''}${dbService}
129
+ `;
130
+ }
131
+
132
+ /**
133
+ * Génère tous les fichiers Docker pour le projet.
134
+ * @param {string} stack
135
+ * @param {string} database
136
+ * @param {string} projectName
137
+ */
138
+ export function scaffoldDocker(stack, database, projectName) {
139
+ console.log(chalk.yellow('🐳 Génération des fichiers Docker...'));
140
+ fs.mkdirSync('docker', { recursive: true });
141
+ fs.writeFileSync('.dockerignore', DOCKERIGNORE);
142
+
143
+ const dockerfile = DOCKERFILES[stack] ?? DOCKERFILES.default;
144
+ fs.writeFileSync('docker/Dockerfile', dockerfile);
145
+ fs.writeFileSync('docker/docker-compose.yml', buildComposeContent(stack, database, projectName));
146
+
147
+ if (database === 'PostgreSQL') {
148
+ fs.mkdirSync('database', { recursive: true });
149
+ fs.writeFileSync('database/init.sql', `-- Initialisation de la base de données ${projectName}
150
+ CREATE TABLE IF NOT EXISTS users (
151
+ id SERIAL PRIMARY KEY,
152
+ email VARCHAR(255) UNIQUE NOT NULL,
153
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
154
+ );
155
+ `);
156
+ }
157
+
158
+ console.log(chalk.green('✅ Fichiers Docker générés.'));
159
+ }