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.
- 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 +63 -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 +101 -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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// features/scaffold-gef.js — Application de la surcouche GEF au projet (Moteur de Templates)
|
|
2
|
+
// Réf. Playbook §6 : Documentation Diátaxis. §1 : SRP
|
|
3
|
+
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
const DIATAXIS_DIRS = [
|
|
9
|
+
'docs/tutorials',
|
|
10
|
+
'docs/how-to',
|
|
11
|
+
'docs/reference',
|
|
12
|
+
'docs/explanation/adr',
|
|
13
|
+
'docs/research',
|
|
14
|
+
'src',
|
|
15
|
+
'tests',
|
|
16
|
+
'scripts',
|
|
17
|
+
'assets',
|
|
18
|
+
'infra',
|
|
19
|
+
'database',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
function createDirectories(includeCI) {
|
|
23
|
+
const dirs = includeCI ? [...DIATAXIS_DIRS, '.github/workflows'] : DIATAXIS_DIRS;
|
|
24
|
+
dirs.forEach((d) => fs.mkdirSync(d, { recursive: true }));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Applique les règles de Hard Limits selon la sévérité choisie.
|
|
29
|
+
*/
|
|
30
|
+
function applyTemplating(content, strictness, language) {
|
|
31
|
+
let maxLines = '30';
|
|
32
|
+
let maxParams = '3';
|
|
33
|
+
let maxComplexity = '10';
|
|
34
|
+
let maxPayload = '1 Mo';
|
|
35
|
+
let isEnglish = language === 'English';
|
|
36
|
+
|
|
37
|
+
if (strictness.includes('Startup')) {
|
|
38
|
+
maxLines = '50';
|
|
39
|
+
maxParams = '4';
|
|
40
|
+
maxComplexity = '15';
|
|
41
|
+
maxPayload = '5 Mo';
|
|
42
|
+
} else if (strictness.includes('Mission Critical')) {
|
|
43
|
+
maxLines = '15';
|
|
44
|
+
maxParams = '2';
|
|
45
|
+
maxComplexity = '5';
|
|
46
|
+
maxPayload = '100 Ko';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let result = content
|
|
50
|
+
.replace(/{{MAX_LINES}}/g, maxLines)
|
|
51
|
+
.replace(/{{MAX_PARAMS}}/g, maxParams)
|
|
52
|
+
.replace(/{{MAX_COMPLEXITY}}/g, maxComplexity)
|
|
53
|
+
.replace(/{{MAX_PAYLOAD}}/g, maxPayload);
|
|
54
|
+
|
|
55
|
+
if (isEnglish) {
|
|
56
|
+
result = result.replace(/Ce prompt est à fournir/g, 'This prompt is to be provided');
|
|
57
|
+
result = result.replace(/Tu es une IA d'assistance/g, 'You are an AI coding assistant');
|
|
58
|
+
// Simple traduction rudimentaire pour les instructions racines
|
|
59
|
+
result = `[ENGLISH MODE ACTIVATED - ALL RESPONSES MUST BE IN ENGLISH]\n\n${result}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Copie le Playbook et les prompts IA avec le templating dynamique.
|
|
67
|
+
*/
|
|
68
|
+
export function copyAndTemplateGefAssets(gefDir, strictness, language) {
|
|
69
|
+
console.log(chalk.cyan('📚 Ajout du Playbook et des Prompts IA dynamiques...'));
|
|
70
|
+
fs.mkdirSync('.gef/prompts', { recursive: true });
|
|
71
|
+
|
|
72
|
+
const playbookSrc = path.join(gefDir, 'ENGINEERING_PLAYBOOK.md');
|
|
73
|
+
if (fs.existsSync(playbookSrc)) {
|
|
74
|
+
const raw = fs.readFileSync(playbookSrc, 'utf8');
|
|
75
|
+
fs.writeFileSync('.gef/ENGINEERING_PLAYBOOK.md', applyTemplating(raw, strictness, language));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const promptsSrc = path.join(gefDir, 'prompts');
|
|
79
|
+
if (fs.existsSync(promptsSrc)) {
|
|
80
|
+
fs.readdirSync(promptsSrc).forEach((p) => {
|
|
81
|
+
const raw = fs.readFileSync(path.join(promptsSrc, p), 'utf8');
|
|
82
|
+
fs.writeFileSync(path.join('.gef/prompts', p), applyTemplating(raw, strictness, language));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createAdrTemplate(gefDir) {
|
|
88
|
+
const templateSrc = path.join(gefDir, 'generator', 'templates', 'adr-template.md');
|
|
89
|
+
const dest = 'docs/explanation/adr/0000-template.md';
|
|
90
|
+
if (fs.existsSync(templateSrc)) fs.copyFileSync(templateSrc, dest);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createResearchLog(language) {
|
|
94
|
+
const isEn = language === 'English';
|
|
95
|
+
const dest = 'docs/research/RESEARCH_LOG.md';
|
|
96
|
+
if (fs.existsSync(dest)) return;
|
|
97
|
+
fs.writeFileSync(
|
|
98
|
+
dest,
|
|
99
|
+
isEn
|
|
100
|
+
? `# Research Log\n> Document bug fixes here.\n\n## 1. <Title>\n- **Context:** \n- **Root Cause:** \n- **Resolution:** \n- **Lesson Learned:** \n`
|
|
101
|
+
: `# Research Log — Journal de bord scientifique\n> Documentez ici la résolution des bugs bloquants.\n\n## 1. <Titre>\n- **Contexte :** \n- **Cause Racine :** \n- **Résolution :** \n- **Leçon apprise :** \n`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createProjectConfig(answers, gefDir) {
|
|
106
|
+
const templatePath = path.join(gefDir, 'PROJECT_CONFIG.template.md');
|
|
107
|
+
if (!fs.existsSync(templatePath)) return;
|
|
108
|
+
|
|
109
|
+
const dateStr = new Date().toLocaleString(answers.language === 'English' ? 'en-US' : 'fr-FR', { month: 'long', year: 'numeric' });
|
|
110
|
+
const content = fs.readFileSync(templatePath, 'utf-8')
|
|
111
|
+
.replace(/{{PROJECT_NAME}}/g, answers.projectName)
|
|
112
|
+
.replace(/{{STACK}}/g, answers.stack)
|
|
113
|
+
.replace(/{{PHASE}}/g, answers.phase)
|
|
114
|
+
.replace(/{{DATABASE}}/g, answers.database)
|
|
115
|
+
.replace(/{{CLOUD}}/g, answers.cloud)
|
|
116
|
+
.replace(/{{GIT_WORKFLOW}}/g, answers.gitWorkflow)
|
|
117
|
+
.replace(/{{LINTER}}/g, answers.linter)
|
|
118
|
+
.replace(/{{STRICTNESS}}/g, answers.strictness)
|
|
119
|
+
.replace(/{{LANGUAGE}}/g, answers.language)
|
|
120
|
+
.replace(/{{DATE}}/g, dateStr);
|
|
121
|
+
|
|
122
|
+
fs.writeFileSync('PROJECT_CONFIG.md', content);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function createReadme({ projectName, stack, cloud, database, gitWorkflow, strictness }) {
|
|
126
|
+
const header = `# ${projectName}\n\n## Fonctionnalités\n<À COMPLÉTER>\n\n## Architecture\n- Stack: ${stack}\n- Cloud: ${cloud}\n- DB: ${database}\n- Git: ${gitWorkflow}\n- Sévérité: ${strictness}\n`;
|
|
127
|
+
if (fs.existsSync('README.md')) {
|
|
128
|
+
const existing = fs.readFileSync('README.md', 'utf8');
|
|
129
|
+
fs.writeFileSync('README.md', header + '\n---\n*Généré initialement par le framework GEF:*\n' + existing);
|
|
130
|
+
} else {
|
|
131
|
+
fs.writeFileSync('README.md', header);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function createGitignore(stack) {
|
|
136
|
+
let content = '\n# GEF Standard\n.env\n.DS_Store\n';
|
|
137
|
+
if (stack.includes('Python')) content += '__pycache__/\nvenv/\n.venv/\n.pytest_cache/\n';
|
|
138
|
+
|
|
139
|
+
if (fs.existsSync('.gitignore')) {
|
|
140
|
+
fs.appendFileSync('.gitignore', content);
|
|
141
|
+
} else {
|
|
142
|
+
fs.writeFileSync('.gitignore', content);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function scaffoldGef(answers, gefDir) {
|
|
147
|
+
console.log(chalk.yellow('\n📁 Application de la surcouche GEF...'));
|
|
148
|
+
createDirectories(answers.includeCI);
|
|
149
|
+
copyAndTemplateGefAssets(gefDir, answers.strictness, answers.language);
|
|
150
|
+
createAdrTemplate(gefDir);
|
|
151
|
+
createResearchLog(answers.language);
|
|
152
|
+
createProjectConfig(answers, gefDir);
|
|
153
|
+
createReadme(answers);
|
|
154
|
+
createGitignore(answers.stack);
|
|
155
|
+
|
|
156
|
+
if (!fs.existsSync('CHANGELOG.md')) fs.writeFileSync('CHANGELOG.md', '');
|
|
157
|
+
if (!fs.existsSync('LICENSE')) fs.writeFileSync('LICENSE', '');
|
|
158
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// features/scaffold-git.js — Initialisation Git et installation des hooks dynamiques
|
|
2
|
+
// Réf. Playbook : TBD ou GitHub Flow
|
|
3
|
+
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Initialise le dépôt Git.
|
|
10
|
+
*/
|
|
11
|
+
function initGitRepo() {
|
|
12
|
+
if (fs.existsSync('.git')) return;
|
|
13
|
+
execSync('git init && git branch -M main', { stdio: 'ignore' });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Génère le script pre-push de manière dynamique selon le choix du workflow Git.
|
|
18
|
+
*/
|
|
19
|
+
function generatePrePush(gitWorkflow) {
|
|
20
|
+
const isGitHubFlow = gitWorkflow.includes('GitHub Flow');
|
|
21
|
+
let script = '#!/bin/bash\n# Hook: pre-push\n\nCURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)\n\n';
|
|
22
|
+
|
|
23
|
+
if (isGitHubFlow) {
|
|
24
|
+
script += `# Sécurité GitHub Flow : Interdire les pushes directs sur main
|
|
25
|
+
if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "master" ]; then
|
|
26
|
+
echo -e "\\033[31mErreur: Push direct sur '$CURRENT_BRANCH' interdit.\\033[0m"
|
|
27
|
+
echo "Le projet utilise GitHub Flow. Utilisez des branches et des Pull Requests."
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
\n`;
|
|
31
|
+
} else {
|
|
32
|
+
script += `# Sécurité TBD : Pushes sur main autorisés.
|
|
33
|
+
echo -e "\\033[34mInfo: Mode TBD, push sur $CURRENT_BRANCH autorisé.\\033[0m"\n\n`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
script += `if [ -f "package.json" ] && grep -q '"test"' package.json; then
|
|
37
|
+
npm test || { echo -e "\\033[31mErreur: Tests en échec. Push annulé.\\033[0m"; exit 1; }
|
|
38
|
+
elif [ -f "Makefile" ] && grep -q "^test:" Makefile; then
|
|
39
|
+
make test || { echo -e "\\033[31mErreur: Tests en échec. Push annulé.\\033[0m"; exit 1; }
|
|
40
|
+
fi\n\nexit 0\n`;
|
|
41
|
+
return script;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Génère le script pre-commit de manière dynamique.
|
|
46
|
+
*/
|
|
47
|
+
function generatePreCommit(linter, strictness) {
|
|
48
|
+
let fileLimit = 400;
|
|
49
|
+
let payloadLimitKb = 1000; // 1 Mo par défaut
|
|
50
|
+
if (strictness.includes('Startup')) {
|
|
51
|
+
fileLimit = 500;
|
|
52
|
+
payloadLimitKb = 5000; // 5 Mo
|
|
53
|
+
}
|
|
54
|
+
if (strictness.includes('Mission Critical')) {
|
|
55
|
+
fileLimit = 200;
|
|
56
|
+
payloadLimitKb = 100; // 100 Ko
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let linterCmd = '';
|
|
60
|
+
if (linter.includes('ESLint')) linterCmd = 'npm run lint || { echo "Erreur Lint"; exit 1; }';
|
|
61
|
+
else if (linter.includes('Biome')) linterCmd = 'npx biome check . || { echo "Erreur Lint"; exit 1; }';
|
|
62
|
+
else if (linter.includes('Ruff')) linterCmd = 'ruff check . || { echo "Erreur Lint"; exit 1; }';
|
|
63
|
+
|
|
64
|
+
return `#!/bin/bash
|
|
65
|
+
# Hook: pre-commit
|
|
66
|
+
|
|
67
|
+
SECRETS=$(git diff --cached -G"(api_key|secret|token|password)[ ]*=[ ]*['\\\"][a-zA-Z0-9_\\\\-]+['\\\"]" --name-only)
|
|
68
|
+
if [ -n "$SECRETS" ]; then
|
|
69
|
+
echo "Erreur: Potentiel secret en clair."
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
DEBUG_FILES=$(git diff --cached --name-only | grep -E "(^|/)(debug_|test_)" | grep -v "^tests/")
|
|
74
|
+
if [ -n "$DEBUG_FILES" ]; then
|
|
75
|
+
echo "Erreur: Fichier de debug détecté hors de tests/."
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
${linterCmd ? `echo "Exécution du linter..."\n${linterCmd}` : ''}
|
|
80
|
+
|
|
81
|
+
for file in $(git diff --cached --name-only); do
|
|
82
|
+
if [ -f "$file" ]; then
|
|
83
|
+
LINES=$(wc -l < "$file")
|
|
84
|
+
if [ "$LINES" -gt ${fileLimit} ]; then
|
|
85
|
+
echo "Avertissement: $file dépasse ${fileLimit} lignes."
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# Vérification du Payload (Taille Max)
|
|
89
|
+
SIZE_KB=$(du -k "$file" | cut -f1)
|
|
90
|
+
if [ "$SIZE_KB" -gt ${payloadLimitKb} ]; then
|
|
91
|
+
echo "Erreur: $file ($SIZE_KB Ko) dépasse la limite de taille autorisée (${payloadLimitKb} Ko) pour ce niveau de sévérité."
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
done
|
|
96
|
+
|
|
97
|
+
exit 0
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Génère et installe les hooks dynamiques dans le projet.
|
|
103
|
+
*/
|
|
104
|
+
function installDynamicHooks(gitWorkflow, linter, strictness) {
|
|
105
|
+
fs.mkdirSync('.git/hooks', { recursive: true });
|
|
106
|
+
|
|
107
|
+
const commitMsgScript = `#!/bin/bash
|
|
108
|
+
COMMIT_MSG=$(cat $1)
|
|
109
|
+
PATTERN="^(feat|fix|docs|chore|refactor|style|perf|test|release)(\\([a-zA-Z0-9_.-]+\\))?: (.*) \\(#[0-9]+\\)$"
|
|
110
|
+
if [[ ! $COMMIT_MSG =~ $PATTERN ]]; then
|
|
111
|
+
echo "Erreur: Le message doit suivre Conventional Commits et inclure (#Ticket)."
|
|
112
|
+
exit 1
|
|
113
|
+
fi
|
|
114
|
+
exit 0
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
fs.writeFileSync('.git/hooks/commit-msg', commitMsgScript);
|
|
118
|
+
fs.writeFileSync('.git/hooks/pre-push', generatePrePush(gitWorkflow));
|
|
119
|
+
fs.writeFileSync('.git/hooks/pre-commit', generatePreCommit(linter, strictness));
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
execSync('chmod +x .git/hooks/commit-msg .git/hooks/pre-commit .git/hooks/pre-push', { stdio: 'ignore' });
|
|
123
|
+
} catch (_) { /* Ignoré sur Windows */ }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Orchestre l'initialisation Git et l'installation des hooks.
|
|
128
|
+
*/
|
|
129
|
+
export function scaffoldGit(gefDir, gitWorkflow, linter, strictness) {
|
|
130
|
+
console.log(chalk.yellow('🔗 Initialisation Git et installation des hooks...'));
|
|
131
|
+
try {
|
|
132
|
+
initGitRepo();
|
|
133
|
+
installDynamicHooks(gitWorkflow, linter, strictness);
|
|
134
|
+
console.log(chalk.green('✅ Git initialisé et Hooks dynamiques générés.'));
|
|
135
|
+
} catch (_) {
|
|
136
|
+
console.log(chalk.red("Erreur lors de l'installation des hooks Git."));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// features/scaffold-linter.js — Génération des configurations de linter dynamiques
|
|
2
|
+
// Réf. Playbook : Code Quality
|
|
3
|
+
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configure ESLint et Prettier.
|
|
10
|
+
*/
|
|
11
|
+
function configureEslint(stack) {
|
|
12
|
+
console.log(chalk.yellow('🧹 Configuration de ESLint + Prettier...'));
|
|
13
|
+
const eslintConfig = {
|
|
14
|
+
env: { browser: true, es2021: true, node: true },
|
|
15
|
+
extends: ['eslint:recommended', 'prettier'],
|
|
16
|
+
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
|
17
|
+
rules: {},
|
|
18
|
+
};
|
|
19
|
+
fs.writeFileSync('.eslintrc.json', JSON.stringify(eslintConfig, null, 2));
|
|
20
|
+
fs.writeFileSync('.prettierrc', '{\n "semi": true,\n "singleQuote": true,\n "printWidth": 100\n}\n');
|
|
21
|
+
|
|
22
|
+
if (fs.existsSync('package.json')) {
|
|
23
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
24
|
+
pkg.scripts = pkg.scripts || {};
|
|
25
|
+
pkg.scripts.lint = 'eslint . && prettier --check .';
|
|
26
|
+
pkg.scripts['lint:fix'] = 'eslint . --fix && prettier --write .';
|
|
27
|
+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configure Biome.
|
|
33
|
+
*/
|
|
34
|
+
function configureBiome() {
|
|
35
|
+
console.log(chalk.yellow('⚡ Configuration de Biome...'));
|
|
36
|
+
const biomeConfig = {
|
|
37
|
+
$schema: "https://biomejs.dev/schemas/1.8.3/schema.json",
|
|
38
|
+
formatter: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
formatWithErrors: false,
|
|
41
|
+
indentStyle: "space",
|
|
42
|
+
indentWidth: 2,
|
|
43
|
+
lineWidth: 100
|
|
44
|
+
},
|
|
45
|
+
linter: {
|
|
46
|
+
enabled: true,
|
|
47
|
+
rules: { recommended: true }
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
fs.writeFileSync('biome.json', JSON.stringify(biomeConfig, null, 2));
|
|
51
|
+
|
|
52
|
+
if (fs.existsSync('package.json')) {
|
|
53
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
54
|
+
pkg.scripts = pkg.scripts || {};
|
|
55
|
+
pkg.scripts.lint = 'biome check .';
|
|
56
|
+
pkg.scripts['lint:fix'] = 'biome check --write .';
|
|
57
|
+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Configure Ruff pour Python.
|
|
63
|
+
*/
|
|
64
|
+
function configureRuff() {
|
|
65
|
+
console.log(chalk.yellow('🐍 Configuration de Ruff...'));
|
|
66
|
+
const ruffToml = `[lint]
|
|
67
|
+
select = ["E", "F", "I"]
|
|
68
|
+
ignore = []
|
|
69
|
+
|
|
70
|
+
[format]
|
|
71
|
+
quote-style = "double"
|
|
72
|
+
indent-style = "space"
|
|
73
|
+
`;
|
|
74
|
+
fs.writeFileSync('ruff.toml', ruffToml);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Orchestre la génération de la configuration du linter.
|
|
79
|
+
*/
|
|
80
|
+
export function scaffoldLinter(linterChoice, stack) {
|
|
81
|
+
if (linterChoice === 'Aucun' || !linterChoice) return;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
if (linterChoice.includes('ESLint')) configureEslint(stack);
|
|
85
|
+
else if (linterChoice.includes('Biome')) configureBiome();
|
|
86
|
+
else if (linterChoice.includes('Ruff')) configureRuff();
|
|
87
|
+
console.log(chalk.green('✅ Linter configuré.'));
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.log(chalk.red('Erreur lors de la configuration du linter.'));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// features/scaffold-stack.js — Installation du framework principal
|
|
2
|
+
// Réf. Playbook §1 : SRP — une fonction par stack, Guard Clauses, Early Return
|
|
3
|
+
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
const EXPRESS_ENTRY = (port = 3000) => `const express = require('express');
|
|
10
|
+
const cors = require('cors');
|
|
11
|
+
require('dotenv').config();
|
|
12
|
+
|
|
13
|
+
const app = express();
|
|
14
|
+
const PORT = process.env.PORT || ${port};
|
|
15
|
+
|
|
16
|
+
app.use(cors());
|
|
17
|
+
app.use(express.json({ limit: '1mb' })); // Hard Limit Playbook §4 : 1Mo max
|
|
18
|
+
|
|
19
|
+
app.get('/', (req, res) => res.json({ message: 'API prête.' }));
|
|
20
|
+
|
|
21
|
+
app.listen(PORT, () => console.log(\`Serveur lancé sur http://localhost:\${PORT}\`));
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
const FASTAPI_ENTRY = (projectName) => `from fastapi import FastAPI
|
|
25
|
+
|
|
26
|
+
app = FastAPI(title="${projectName}")
|
|
27
|
+
|
|
28
|
+
@app.get("/")
|
|
29
|
+
def read_root():
|
|
30
|
+
return {"message": "API prête."}
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Lance le scaffolding du framework choisi.
|
|
35
|
+
* @param {string} stack
|
|
36
|
+
* @param {string} projectName
|
|
37
|
+
* @param {string} projectPath
|
|
38
|
+
*/
|
|
39
|
+
export function scaffoldStack(stack, projectName, projectPath) {
|
|
40
|
+
if (stack === 'Projet vide') return;
|
|
41
|
+
|
|
42
|
+
const handlers = {
|
|
43
|
+
'Next.js (React)': () => scaffoldNext(projectName, projectPath),
|
|
44
|
+
'React (Vite)': () => scaffoldVite(projectName, projectPath),
|
|
45
|
+
'API Node.js (Express)': () => scaffoldExpress(),
|
|
46
|
+
'API Python (FastAPI)': () => scaffoldFastapi(projectName),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handler = handlers[stack];
|
|
50
|
+
if (!handler) return;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
handler();
|
|
54
|
+
console.log(chalk.green('\n✅ Framework installé.'));
|
|
55
|
+
} catch (_) {
|
|
56
|
+
console.log(chalk.red('\nErreur lors du scaffolding. Continuation avec la structure de base GEF.'));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function scaffoldNext(projectName, projectPath) {
|
|
61
|
+
console.log(chalk.magenta('\n▲ Lancement de create-next-app :\n'));
|
|
62
|
+
process.chdir(path.resolve(projectPath, '..'));
|
|
63
|
+
fs.rmdirSync(projectPath);
|
|
64
|
+
execSync(`npx create-next-app@latest ${projectName}`, { stdio: 'inherit' });
|
|
65
|
+
process.chdir(projectPath);
|
|
66
|
+
console.log(chalk.yellow('\n🎭 Installation de Playwright (E2E)...'));
|
|
67
|
+
execSync('npm init playwright@latest --yes -- --quiet --browser=chromium', { stdio: 'inherit' });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function scaffoldVite(projectName, projectPath) {
|
|
71
|
+
console.log(chalk.magenta('\n⚡ Lancement de create-vite :\n'));
|
|
72
|
+
process.chdir(path.resolve(projectPath, '..'));
|
|
73
|
+
fs.rmdirSync(projectPath);
|
|
74
|
+
execSync(`npm create vite@latest ${projectName}`, { stdio: 'inherit' });
|
|
75
|
+
process.chdir(projectPath);
|
|
76
|
+
console.log(chalk.yellow('\nInstallation des dépendances...'));
|
|
77
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
78
|
+
console.log(chalk.yellow('\n🎭 Installation de Playwright (E2E)...'));
|
|
79
|
+
execSync('npm init playwright@latest --yes -- --quiet --browser=chromium', { stdio: 'inherit' });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function scaffoldExpress() {
|
|
83
|
+
console.log(chalk.magenta('\n📦 Initialisation de l\'API Node.js...\n'));
|
|
84
|
+
execSync('npm init -y', { stdio: 'ignore' });
|
|
85
|
+
execSync('npm install express cors dotenv', { stdio: 'inherit' });
|
|
86
|
+
fs.mkdirSync('src', { recursive: true });
|
|
87
|
+
fs.writeFileSync('src/index.js', EXPRESS_ENTRY());
|
|
88
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
89
|
+
pkg.scripts = { start: 'node src/index.js', dev: 'node --watch src/index.js' };
|
|
90
|
+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function scaffoldFastapi(projectName) {
|
|
94
|
+
console.log(chalk.magenta('\n🐍 Initialisation de l\'API Python (FastAPI)...\n'));
|
|
95
|
+
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
|
96
|
+
execSync(`${pythonCmd} -m venv venv`, { stdio: 'inherit' });
|
|
97
|
+
fs.writeFileSync('requirements.txt', 'fastapi\nuvicorn[standard]\npython-dotenv\n');
|
|
98
|
+
fs.mkdirSync('src', { recursive: true });
|
|
99
|
+
fs.writeFileSync('src/__init__.py', '');
|
|
100
|
+
fs.writeFileSync('src/main.py', FASTAPI_ENTRY(projectName));
|
|
101
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// features/update.js — Mise à jour d'un projet GEF existant
|
|
2
|
+
// Réf. Playbook §1 : SRP — cette fonction met à jour les règles et hooks dynamiques.
|
|
3
|
+
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { copyAndTemplateGefAssets } from './scaffold-gef.js';
|
|
8
|
+
import { scaffoldGit } from './scaffold-git.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Lit le fichier de configuration pour en extraire une valeur avec Regex.
|
|
12
|
+
*/
|
|
13
|
+
function extractFromConfig(content, regex, fallback) {
|
|
14
|
+
const match = content.match(regex);
|
|
15
|
+
return match ? match[1].trim() : fallback;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Met à jour le Playbook, les prompts IA et les hooks Git
|
|
20
|
+
* d'un projet GEF existant vers la dernière version du framework,
|
|
21
|
+
* tout en respectant ses paramètres (Sévérité, Git Flow, etc.)
|
|
22
|
+
* @param {string} gefDir - Chemin absolu vers la racine du GEF
|
|
23
|
+
*/
|
|
24
|
+
export function runUpdate(gefDir) {
|
|
25
|
+
if (!fs.existsSync('.git')) {
|
|
26
|
+
console.log(chalk.red('Erreur: Ce dossier ne semble pas être un dépôt Git valide.'));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 1. Trouver le fichier de configuration
|
|
31
|
+
const configPath = fs.existsSync('PROJECT_CONFIG.md')
|
|
32
|
+
? 'PROJECT_CONFIG.md'
|
|
33
|
+
: 'PROJECT_CONFIG.template.md';
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(configPath)) {
|
|
36
|
+
console.log(chalk.red('\nErreur: Impossible de trouver PROJECT_CONFIG.md ou PROJECT_CONFIG.template.md.'));
|
|
37
|
+
console.log(chalk.yellow('Astuce: Le GEF a besoin de ce fichier pour connaître le niveau de sévérité et la stratégie Git de ce projet afin de le mettre à jour sans tout casser.'));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2. Extraire l'environnement du projet
|
|
42
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
43
|
+
const gitWorkflow = extractFromConfig(content, /- \*\*Workflow Git \*\*: (.*)/, 'GitHub Flow');
|
|
44
|
+
const strictness = extractFromConfig(content, /- \*\*Sévérité \(Hard Limits\) \*\*: (.*)/, 'Standard / Enterprise');
|
|
45
|
+
const linter = extractFromConfig(content, /- \*\*Linter \/ Formatter \*\*: (.*)/, 'Aucun');
|
|
46
|
+
const language = extractFromConfig(content, /- \*\*Langue par défaut \*\*: (.*)/, 'Français');
|
|
47
|
+
|
|
48
|
+
console.log(chalk.blue(`\nLecture de la configuration actuelle du projet :`));
|
|
49
|
+
console.log(` - Sévérité : ${chalk.yellow(strictness)}`);
|
|
50
|
+
console.log(` - Git Flow : ${chalk.yellow(gitWorkflow)}`);
|
|
51
|
+
console.log(` - Linter : ${chalk.yellow(linter)}`);
|
|
52
|
+
console.log(` - Langue : ${chalk.yellow(language)}\n`);
|
|
53
|
+
|
|
54
|
+
// 3. Regénérer les assets dynamiquement
|
|
55
|
+
console.log(chalk.yellow('🔄 Mise à jour dynamique des ressources GEF...'));
|
|
56
|
+
|
|
57
|
+
copyAndTemplateGefAssets(gefDir, strictness, language);
|
|
58
|
+
scaffoldGit(gefDir, gitWorkflow, linter, strictness);
|
|
59
|
+
|
|
60
|
+
console.log(chalk.green.bold('\n🎉 Projet GEF mis à jour avec succès !'));
|
|
61
|
+
}
|