nestgen-cli 0.2.1 → 0.2.2
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/dist/cli/doctor-output.js +45 -0
- package/dist/cli/output.js +65 -0
- package/examples/store-api/README.md +3 -3
- package/examples/store-api/nestgen.example.json +1 -1
- package/nestgen.js +55 -30
- package/nestjs-generator/features/docker.sh +6 -4
- package/nestjs-generator/features/git.sh +1 -1
- package/nestjs-generator/features/inject_module_to_app.sh +8 -6
- package/nestjs-generator/features/logger.sh +8 -4
- package/nestjs-generator/features/operations.sh +4 -2
- package/nestjs-generator/features/prisma.sh +3 -3
- package/nestjs-generator/features/typeorm.sh +3 -3
- package/nestjs-generator/features/utils.sh +5 -3
- package/nestjs-generator/generate_project.sh +9 -9
- package/package.json +1 -1
- package/readme.md +7 -7
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
/** Human-readable presentation only; structured diagnostics remain unchanged. */
|
|
3
|
+
export function formatDoctorOutput(diagnostics, options = {}) {
|
|
4
|
+
const groups = [
|
|
5
|
+
{ severity: 'ERROR', title: 'À corriger', marker: '✖', color: chalk.red },
|
|
6
|
+
{ severity: 'WARNING', title: 'À vérifier', marker: '⚠', color: chalk.yellow },
|
|
7
|
+
{ severity: 'INFO', title: 'Vérifications', marker: '✓', color: chalk.green },
|
|
8
|
+
];
|
|
9
|
+
const lines = options.quiet
|
|
10
|
+
? []
|
|
11
|
+
: [
|
|
12
|
+
'',
|
|
13
|
+
` ${chalk.cyan.bold('◆ NestGen')} ${chalk.bold('Doctor')}`,
|
|
14
|
+
chalk.dim(' Diagnostic de votre environnement'),
|
|
15
|
+
'',
|
|
16
|
+
];
|
|
17
|
+
for (const group of groups) {
|
|
18
|
+
if (options.quiet && group.severity === 'INFO')
|
|
19
|
+
continue;
|
|
20
|
+
const entries = diagnostics.filter((diagnostic) => diagnostic.severity === group.severity);
|
|
21
|
+
if (!entries.length)
|
|
22
|
+
continue;
|
|
23
|
+
if (!options.quiet)
|
|
24
|
+
lines.push(` ${group.color.bold(group.title)} ${chalk.dim(`(${entries.length})`)}`, '');
|
|
25
|
+
for (const diagnostic of entries) {
|
|
26
|
+
lines.push(` ${group.color(group.marker)} ${diagnostic.cause}`);
|
|
27
|
+
if (diagnostic.severity !== 'INFO' || (diagnostic.id === 'NESTGEN_CONFIG' && !diagnostic.detail))
|
|
28
|
+
lines.push(` → ${diagnostic.action}`);
|
|
29
|
+
if (options.verbose) {
|
|
30
|
+
lines.push(chalk.dim(` Code : ${diagnostic.id}`));
|
|
31
|
+
if (diagnostic.detail)
|
|
32
|
+
lines.push(chalk.dim(` Détail : ${diagnostic.detail}`));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!options.quiet)
|
|
36
|
+
lines.push('');
|
|
37
|
+
}
|
|
38
|
+
if (!options.quiet) {
|
|
39
|
+
const count = (severity) => diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
40
|
+
const errors = count('ERROR');
|
|
41
|
+
const warnings = count('WARNING');
|
|
42
|
+
lines.push(chalk.dim(' ────────────────────────────────────────'), ` ${errors ? chalk.red.bold('✖ Des corrections sont nécessaires') : warnings ? chalk.yellow.bold('⚠ Quelques points à vérifier') : chalk.green.bold('✓ Votre environnement est prêt')}`, ` ${chalk.red(`${errors} erreur${errors > 1 ? 's' : ''}`)} · ${chalk.yellow(`${warnings} avertissement${warnings > 1 ? 's' : ''}`)} · ${chalk.dim(`${count('INFO')} vérifications`)}`, '');
|
|
43
|
+
}
|
|
44
|
+
return lines.join('\n');
|
|
45
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export function heading(title, subtitle) {
|
|
3
|
+
return `\n ${chalk.cyan.bold('◆ NestGen')} ${chalk.bold(title)}\n${subtitle ? `${chalk.dim(` ${subtitle}`)}\n` : ''}`;
|
|
4
|
+
}
|
|
5
|
+
export function success(message) {
|
|
6
|
+
return `\n ${chalk.green('✓')} ${chalk.bold(message)}\n`;
|
|
7
|
+
}
|
|
8
|
+
export function formatError(message) {
|
|
9
|
+
return `${heading('Erreur')} ${chalk.red('✖')} ${message.split('\n').join('\n ')}\n`;
|
|
10
|
+
}
|
|
11
|
+
export function formatConfig(config, subtitle) {
|
|
12
|
+
const rows = [
|
|
13
|
+
['ORM', config.orm],
|
|
14
|
+
['Architecture', config.profile],
|
|
15
|
+
['Sources', config.sourceRoot],
|
|
16
|
+
['Package manager', config.packageManager],
|
|
17
|
+
['Version configuration', config.version],
|
|
18
|
+
['Version templates', config.templateVersion],
|
|
19
|
+
];
|
|
20
|
+
return `${heading('Configuration', subtitle)}${rows.map(([label, value]) => ` ${chalk.dim(label.padEnd(24))}${value}`).join('\n')}\n`;
|
|
21
|
+
}
|
|
22
|
+
export function formatMigration(migration) {
|
|
23
|
+
return [
|
|
24
|
+
` ${chalk.bold('Migration à préparer')}`,
|
|
25
|
+
` ${chalk.cyan(migration.command)}`,
|
|
26
|
+
chalk.dim(' Cette commande prépare la migration ; aucune migration appliquée.'),
|
|
27
|
+
...migration.risks.map((risk) => ` ${risk.severity === 'danger' ? chalk.red('✖') : chalk.yellow('⚠')} ${risk.message}`),
|
|
28
|
+
'',
|
|
29
|
+
].join('\n');
|
|
30
|
+
}
|
|
31
|
+
export function formatPlan(plan, verbose = false) {
|
|
32
|
+
const lines = [heading('Aperçu', `Génération ${plan.operation} · aucun fichier écrit`)];
|
|
33
|
+
for (const change of plan.changes) {
|
|
34
|
+
const label = change.status === 'create'
|
|
35
|
+
? chalk.green('+ Créer')
|
|
36
|
+
: change.status === 'replace'
|
|
37
|
+
? chalk.cyan('~ Modifier')
|
|
38
|
+
: chalk.red('✖ Conflit');
|
|
39
|
+
lines.push(` ${label} ${change.path}`);
|
|
40
|
+
if (change.reason)
|
|
41
|
+
lines.push(` ${chalk.yellow(change.reason)}`);
|
|
42
|
+
if (verbose && change.diff)
|
|
43
|
+
lines.push(chalk.dim(` ${change.diff.split('\n').join('\n ')}`));
|
|
44
|
+
}
|
|
45
|
+
for (const conflict of plan.conflicts.filter((conflict) => !conflict.path))
|
|
46
|
+
lines.push(` ${chalk.red('✖')} ${conflict.reason}`);
|
|
47
|
+
lines.push('', chalk.dim(' ────────────────────────────────────────'), ` ${plan.changes.length} fichiers · ${plan.conflicts.length} conflits`, '');
|
|
48
|
+
if (plan.migration)
|
|
49
|
+
lines.push(formatMigration(plan.migration));
|
|
50
|
+
return lines.join('\n');
|
|
51
|
+
}
|
|
52
|
+
/** Align help columns from the complete existing command/option descriptions. */
|
|
53
|
+
export function formatHelp(usage) {
|
|
54
|
+
return `${heading('Aide')}${usage
|
|
55
|
+
.split('\n')
|
|
56
|
+
.map((line) => {
|
|
57
|
+
if (!line)
|
|
58
|
+
return '';
|
|
59
|
+
if (!line.startsWith(' '))
|
|
60
|
+
return ` ${chalk.bold(line)}`;
|
|
61
|
+
const [label, description] = line.trim().split(/\s{2,}/);
|
|
62
|
+
return description ? ` ${chalk.cyan(label.padEnd(34))} ${description}` : ` ${line.trim()}`;
|
|
63
|
+
})
|
|
64
|
+
.join('\n')}\n`;
|
|
65
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Exemple Store API — NestGen CLI 0.2.
|
|
1
|
+
# Exemple Store API — NestGen CLI 0.2.2
|
|
2
2
|
|
|
3
|
-
Cette application exemple décrit une API de vente avec des clients, produits, commandes et lignes de commande. Elle est validée avec `nestgen-cli@0.2.
|
|
3
|
+
Cette application exemple décrit une API de vente avec des clients, produits, commandes et lignes de commande. Elle est validée avec `nestgen-cli@0.2.2` et TypeORM, avec le profil d’architecture `advanced`.
|
|
4
4
|
|
|
5
5
|
Les fichiers de `definitions/` sont la source de vérité. Ils restent volontairement déclaratifs : l’application est reconstruite par le CLI au lieu de conserver une copie de fichiers générés qui pourrait diverger du produit.
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ Créez un dossier de travail, installez la version exacte du CLI puis générez
|
|
|
11
11
|
```bash
|
|
12
12
|
mkdir nestgen-store-api && cd nestgen-store-api
|
|
13
13
|
npm init --yes
|
|
14
|
-
npm install nestgen-cli@0.2.
|
|
14
|
+
npm install nestgen-cli@0.2.2
|
|
15
15
|
|
|
16
16
|
./node_modules/.bin/nestgen init store-api \
|
|
17
17
|
--no-interactive \
|
package/nestgen.js
CHANGED
|
@@ -18,6 +18,16 @@ import {
|
|
|
18
18
|
import { configFileName, defaultConfig, loadConfigIfPresent, writeConfig } from './dist/engine/project-config.js';
|
|
19
19
|
import { parseArchitectureProfile } from './dist/engine/architecture-profile.js';
|
|
20
20
|
import { planResourceMigration } from './dist/engine/migration-plan.js';
|
|
21
|
+
import { formatDoctorOutput } from './dist/cli/doctor-output.js';
|
|
22
|
+
import {
|
|
23
|
+
heading,
|
|
24
|
+
success,
|
|
25
|
+
formatError,
|
|
26
|
+
formatConfig,
|
|
27
|
+
formatPlan,
|
|
28
|
+
formatMigration,
|
|
29
|
+
formatHelp,
|
|
30
|
+
} from './dist/cli/output.js';
|
|
21
31
|
|
|
22
32
|
// ────── Resolve __dirname compatible ES Modules
|
|
23
33
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -32,17 +42,7 @@ const MODULE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i;
|
|
|
32
42
|
|
|
33
43
|
// ────── Logo CLI
|
|
34
44
|
function printLogo() {
|
|
35
|
-
console.log(
|
|
36
|
-
chalk.magentaBright(`
|
|
37
|
-
███╗ ██╗███████╗███████╗████████╗ ██████╗ ███████╗███╗ ██╗
|
|
38
|
-
████╗ ██║██╔════╝██╔════╝╚══██╔══╝██╔════╝ ██╔════╝████╗ ██║
|
|
39
|
-
██╔██╗ ██║█████╗ ███████╗ ██║ ██║ ███╗█████╗ ██╔██╗ ██║
|
|
40
|
-
██║╚██╗██║██╔══╝ ╚════██║ ██║ ██║ ██║██╔══╝ ██║╚██╗██║
|
|
41
|
-
██║ ╚████║███████╗███████║ ██║ ╚██████╔╝███████╗██║ ╚████║
|
|
42
|
-
╚═╝ ╚═══╝╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝
|
|
43
|
-
`),
|
|
44
|
-
);
|
|
45
|
-
console.log(chalk.cyan.bold('✨ NestGen CLI — Générateur modulaire NestJS'));
|
|
45
|
+
console.log(heading('CLI', 'Générateur modulaire NestJS'));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// ────── Helpers
|
|
@@ -226,7 +226,7 @@ function dryRunOutput(operation, plan) {
|
|
|
226
226
|
|
|
227
227
|
export function runBashScript(scriptPath, args = [], env = {}, quiet = false) {
|
|
228
228
|
const result = spawnSync('bash', [scriptPath, ...args], {
|
|
229
|
-
env: { ...process.env, ...env },
|
|
229
|
+
env: { ...process.env, ...(chalk.level === 0 ? { NO_COLOR: '1' } : {}), ...env },
|
|
230
230
|
stdio: quiet ? 'pipe' : 'inherit',
|
|
231
231
|
});
|
|
232
232
|
|
|
@@ -370,7 +370,7 @@ async function runInteractiveInit(options) {
|
|
|
370
370
|
MODULES: modules.map(validateModuleName).join(' '),
|
|
371
371
|
};
|
|
372
372
|
|
|
373
|
-
if (!options.quiet) console.log('
|
|
373
|
+
if (!options.quiet) console.log(chalk.cyan(' › Génération du projet…'));
|
|
374
374
|
runBashScript(GENERATE_SCRIPT, [], env, options.quiet);
|
|
375
375
|
return {
|
|
376
376
|
projectName: env.APP_NAME,
|
|
@@ -419,11 +419,11 @@ async function runModuleGeneration(parsed) {
|
|
|
419
419
|
if (parsed.options.dryRun) {
|
|
420
420
|
const plan = await planModuleGeneration(process.cwd(), moduleName, orm, parsed.options.application);
|
|
421
421
|
const output = dryRunOutput('module', plan);
|
|
422
|
-
if (!parsed.options.json) console.log(
|
|
422
|
+
if (!parsed.options.json) console.log(formatPlan(output, parsed.options.verbose));
|
|
423
423
|
return output;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
-
if (!parsed.options.quiet) console.log(chalk.cyan(
|
|
426
|
+
if (!parsed.options.quiet) console.log(chalk.cyan(` › Génération du module ${moduleName}…`));
|
|
427
427
|
if (process.env.NESTGEN_ROOT) {
|
|
428
428
|
runBashScript(ADD_MODULE_SCRIPT, [moduleName, orm], {}, parsed.options.quiet);
|
|
429
429
|
return { module: moduleName, orm, generated: true };
|
|
@@ -548,7 +548,7 @@ async function runResourceGeneration(parsed) {
|
|
|
548
548
|
if (parsed.options.dryRun) {
|
|
549
549
|
const plan = await planResourceGeneration(process.cwd(), resourceOptions);
|
|
550
550
|
const output = { ...dryRunOutput('resource', plan), ...(migration ? { migration } : {}) };
|
|
551
|
-
if (!parsed.options.json) console.log(
|
|
551
|
+
if (!parsed.options.json) console.log(formatPlan(output, parsed.options.verbose));
|
|
552
552
|
return output;
|
|
553
553
|
}
|
|
554
554
|
await generateResource(process.cwd(), resourceOptions);
|
|
@@ -564,14 +564,14 @@ async function runResourceGeneration(parsed) {
|
|
|
564
564
|
|
|
565
565
|
function printUsage() {
|
|
566
566
|
console.log(
|
|
567
|
-
`Usage: nestgen <commande> [options]\n\nCommandes:\n init [nom] Génère un projet NestJS\n module <nom> [--orm <orm>] Génère un module\n resource <nom> --fields ... Génère un CRUD TypeORM ou Prisma
|
|
567
|
+
formatHelp(`Usage: nestgen <commande> [options]\n\nCommandes:\n init [nom] Génère un projet NestJS\n module <nom> [--orm <orm>] Génère un module\n resource <nom> --fields ... Génère un CRUD TypeORM ou Prisma
|
|
568
568
|
config init|show Gère nestgen.config.json
|
|
569
569
|
doctor Vérifie l'installation\n\nOptions:\n -h, --help Affiche cette aide\n -V, --version Affiche la version\n --no-interactive Refuse les prompts\n --quiet Supprime les sorties non essentielles\n --verbose Active les diagnostics\n --no-color Désactive les couleurs\n --profile <simple|advanced> Choisit le profil d'architecture
|
|
570
570
|
--package-manager <pm> Définit npm, pnpm ou yarn
|
|
571
571
|
--project-path <chemin> Définit le dossier parent du projet init
|
|
572
572
|
--docker, --no-docker Active ou désactive Docker pour init
|
|
573
573
|
--swagger, --no-swagger Active ou désactive Swagger pour init
|
|
574
|
-
--operations, --no-operations
|
|
574
|
+
--operations, --no-operations Active ou désactive le socle d’exploitation pour init
|
|
575
575
|
--git, --no-git Active ou désactive Git pour init
|
|
576
576
|
--modules <a,b> Définit les modules init, séparés par des virgules
|
|
577
577
|
--application <nom> Cible une application d’un workspace Nest
|
|
@@ -579,18 +579,19 @@ function printUsage() {
|
|
|
579
579
|
--migration-name <nom> Prépare une commande de migration officielle avec --update, sans l’appliquer
|
|
580
580
|
--dry-run Affiche le plan sans écrire
|
|
581
581
|
--json Retourne un résultat JSON versionné
|
|
582
|
-
--indexes <a+b,c> Ajoute des index composites ou simples\n\nChamps resource : string, number, integer, decimal(precision;scale), enum(VALEUR|VALEUR), boolean, date, uuid
|
|
582
|
+
--indexes <a+b,c> Ajoute des index composites ou simples\n\nChamps resource : string, number, integer, decimal(precision;scale), enum(VALEUR|VALEUR), boolean, date, uuid.`),
|
|
583
583
|
);
|
|
584
584
|
}
|
|
585
585
|
|
|
586
|
-
function runDoctor(
|
|
586
|
+
function runDoctor(options = {}) {
|
|
587
587
|
const diagnostics = [];
|
|
588
588
|
const add = (id, severity, cause, action, detail) => diagnostics.push({ id, severity, cause, action, detail });
|
|
589
589
|
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(Number);
|
|
590
|
+
const supportedNode = (nodeMajor === 24 && nodeMinor >= 15) || nodeMajor === 25 || nodeMajor === 26;
|
|
590
591
|
add(
|
|
591
592
|
'NODE_VERSION',
|
|
592
|
-
|
|
593
|
-
|
|
593
|
+
supportedNode ? 'INFO' : 'ERROR',
|
|
594
|
+
supportedNode ? 'Version Node.js compatible.' : 'Node.js doit être >=24.15.0 et <27.',
|
|
594
595
|
'Installe une version Node.js supportée puis relance doctor.',
|
|
595
596
|
process.versions.node,
|
|
596
597
|
);
|
|
@@ -722,9 +723,10 @@ function runDoctor(machine = false) {
|
|
|
722
723
|
error.message,
|
|
723
724
|
);
|
|
724
725
|
}
|
|
725
|
-
if (!
|
|
726
|
-
|
|
727
|
-
|
|
726
|
+
if (!options.json) {
|
|
727
|
+
const output = formatDoctorOutput(diagnostics, options);
|
|
728
|
+
if (output) console.log(output);
|
|
729
|
+
}
|
|
728
730
|
return { diagnostics, project };
|
|
729
731
|
}
|
|
730
732
|
|
|
@@ -785,7 +787,15 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
785
787
|
const action = parsed.positionals[0];
|
|
786
788
|
if (action === 'show') {
|
|
787
789
|
result = loadConfigIfPresent(process.cwd()) ?? defaultConfig;
|
|
788
|
-
if (!machine)
|
|
790
|
+
if (!machine)
|
|
791
|
+
console.log(
|
|
792
|
+
formatConfig(
|
|
793
|
+
result,
|
|
794
|
+
fs.existsSync(path.join(process.cwd(), configFileName))
|
|
795
|
+
? configFileName
|
|
796
|
+
: 'Valeurs par défaut',
|
|
797
|
+
),
|
|
798
|
+
);
|
|
789
799
|
break;
|
|
790
800
|
}
|
|
791
801
|
if (action === 'init') {
|
|
@@ -799,25 +809,37 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
799
809
|
throw new Error('Package manager invalide.');
|
|
800
810
|
if (parsed.options.dryRun) {
|
|
801
811
|
result = { operation: 'config-init', file: configFileName, config };
|
|
802
|
-
if (!
|
|
812
|
+
if (!parsed.options.quiet) console.log(formatConfig(config, 'Aperçu · aucun fichier écrit'));
|
|
803
813
|
break;
|
|
804
814
|
}
|
|
805
815
|
writeConfig(process.cwd(), config);
|
|
806
816
|
result = { file: configFileName, created: true, config };
|
|
807
|
-
if (!
|
|
817
|
+
if (!parsed.options.quiet) console.log(success(`Configuration créée : ${configFileName}`));
|
|
808
818
|
break;
|
|
809
819
|
}
|
|
810
820
|
throw new Error('config requiert init ou show.');
|
|
811
821
|
}
|
|
812
822
|
|
|
813
823
|
case 'doctor':
|
|
814
|
-
result = runDoctor(
|
|
824
|
+
result = runDoctor(parsed.options);
|
|
815
825
|
break;
|
|
816
826
|
|
|
817
827
|
default:
|
|
818
828
|
throw new Error(`Commande inconnue : ${parsed.command}. Utilise --help.`);
|
|
819
829
|
}
|
|
820
830
|
if (machine) console.log(JSON.stringify(machineResult(parsed.command, result)));
|
|
831
|
+
else if (!parsed.options.quiet && result) {
|
|
832
|
+
if (parsed.command === 'module' && result.generated)
|
|
833
|
+
console.log(success(`Module ${result.module} généré (${result.orm}).`));
|
|
834
|
+
if (parsed.command === 'resource' && result.generated) {
|
|
835
|
+
console.log(
|
|
836
|
+
success(
|
|
837
|
+
`Ressource ${result.resource} ${result.updated ? 'mise à jour' : 'générée'} (${result.orm} · ${result.profile}).`,
|
|
838
|
+
),
|
|
839
|
+
);
|
|
840
|
+
if (result.migration) console.log(formatMigration(result.migration));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
821
843
|
return result;
|
|
822
844
|
}
|
|
823
845
|
|
|
@@ -826,7 +848,10 @@ if (process.argv[1] && fs.realpathSync(process.argv[1]) === __filename) {
|
|
|
826
848
|
if (process.argv.includes('--json')) {
|
|
827
849
|
const message = error instanceof Error ? error.message : String(error);
|
|
828
850
|
console.error(JSON.stringify({ version: 1, ok: false, error: { code: errorCode(error), message } }));
|
|
829
|
-
} else
|
|
851
|
+
} else {
|
|
852
|
+
if (process.argv.includes('--no-color')) chalk.level = 0;
|
|
853
|
+
console.error(formatError(error.message));
|
|
854
|
+
}
|
|
830
855
|
process.exitCode = 1;
|
|
831
856
|
});
|
|
832
857
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
+
source "$(dirname "${BASH_SOURCE[0]}")/logger.sh"
|
|
4
|
+
|
|
3
5
|
set -euo pipefail
|
|
4
6
|
|
|
5
7
|
APP_NAME=${1:?Nom_application_requis}
|
|
@@ -8,14 +10,14 @@ PM=${2:-npm}
|
|
|
8
10
|
case "$PM" in
|
|
9
11
|
npm|pnpm|yarn) ;;
|
|
10
12
|
*)
|
|
11
|
-
|
|
13
|
+
log_error "Package manager non supporté pour Docker : $PM."
|
|
12
14
|
exit 1
|
|
13
15
|
;;
|
|
14
16
|
esac
|
|
15
17
|
|
|
16
18
|
for file in Dockerfile compose.yaml .dockerignore; do
|
|
17
19
|
if [ -e "$file" ]; then
|
|
18
|
-
|
|
20
|
+
log_error "$file existe déjà. Aucune configuration Docker n'a été remplacée."
|
|
19
21
|
exit 1
|
|
20
22
|
fi
|
|
21
23
|
done
|
|
@@ -186,7 +188,7 @@ POSTGRES_PORT=5432
|
|
|
186
188
|
PORT=3000
|
|
187
189
|
ENV
|
|
188
190
|
else
|
|
189
|
-
|
|
191
|
+
log_info ".env.example existe déjà et a été conservé."
|
|
190
192
|
fi
|
|
191
193
|
|
|
192
|
-
|
|
194
|
+
log_success "Docker de développement et image de production générés pour $APP_NAME ($PM)."
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
+
source "$(dirname "${BASH_SOURCE[0]}")/logger.sh"
|
|
4
|
+
|
|
3
5
|
set -euo pipefail
|
|
4
6
|
|
|
5
7
|
RAW_NAME=${1:-}
|
|
@@ -9,31 +11,31 @@ MODULE_FILE="src/app/${RAW_NAME}/${RAW_NAME}.module.ts"
|
|
|
9
11
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
12
|
|
|
11
13
|
if [[ -z "$RAW_NAME" ]]; then
|
|
12
|
-
|
|
14
|
+
log_error "Tu dois fournir un nom de module."
|
|
13
15
|
exit 1
|
|
14
16
|
fi
|
|
15
17
|
|
|
16
18
|
if [[ "$ORM" != "typeorm" && "$ORM" != "prisma" ]]; then
|
|
17
|
-
|
|
19
|
+
log_error "ORM non supporté : ${ORM:-vide}."
|
|
18
20
|
exit 1
|
|
19
21
|
fi
|
|
20
22
|
|
|
21
23
|
if [[ ! -f "$MODULE_FILE" ]]; then
|
|
22
|
-
|
|
24
|
+
log_error "Le fichier $MODULE_FILE est introuvable."
|
|
23
25
|
exit 1
|
|
24
26
|
fi
|
|
25
27
|
|
|
26
28
|
MODULE_CLASS=$(grep -oE 'export class [A-Za-z0-9_]+' "$MODULE_FILE" | awk '{print $3}')
|
|
27
29
|
|
|
28
30
|
if [[ -z "$MODULE_CLASS" ]]; then
|
|
29
|
-
|
|
31
|
+
log_error "Impossible de détecter la classe du module dans $MODULE_FILE"
|
|
30
32
|
exit 1
|
|
31
33
|
fi
|
|
32
34
|
|
|
33
35
|
MODULE_PATH="./app/${RAW_NAME}/${RAW_NAME}.module"
|
|
34
36
|
|
|
35
37
|
if [[ ! -f "$APP_MODULE" ]]; then
|
|
36
|
-
|
|
38
|
+
log_info "Création de $APP_MODULE"
|
|
37
39
|
mkdir -p src
|
|
38
40
|
cat > "$APP_MODULE" <<'EOF'
|
|
39
41
|
import { Module } from '@nestjs/common';
|
|
@@ -49,4 +51,4 @@ fi
|
|
|
49
51
|
# avoir validé le décorateur @Module et son tableau imports.
|
|
50
52
|
node "$SCRIPT_DIR/update_app_module.mjs" "$APP_MODULE" "$MODULE_CLASS" "$MODULE_PATH" "$ORM"
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
log_success "Module $MODULE_CLASS injecté dans app.module.ts avec succès"
|
|
@@ -7,18 +7,22 @@ YELLOW='\033[1;33m'
|
|
|
7
7
|
BLUE='\033[1;34m'
|
|
8
8
|
NC='\033[0m' # No Color
|
|
9
9
|
|
|
10
|
+
if [ ! -t 1 ] || [ -n "${NO_COLOR+x}" ] || [ "${TERM:-}" = "dumb" ]; then
|
|
11
|
+
RED='' GREEN='' YELLOW='' BLUE='' NC=''
|
|
12
|
+
fi
|
|
13
|
+
|
|
10
14
|
log_info() {
|
|
11
|
-
|
|
15
|
+
printf ' %b›%b %s\n' "$BLUE" "$NC" "$1"
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
log_success() {
|
|
15
|
-
|
|
19
|
+
printf ' %b✓%b %s\n' "$GREEN" "$NC" "$1"
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
log_warn() {
|
|
19
|
-
|
|
23
|
+
printf ' %b⚠%b %s\n' "$YELLOW" "$NC" "$1"
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
log_error() {
|
|
23
|
-
|
|
27
|
+
printf ' %b✖%b %s\n' "$RED" "$NC" "$1" >&2
|
|
24
28
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
+
source "$(dirname "${BASH_SOURCE[0]}")/logger.sh"
|
|
4
|
+
|
|
3
5
|
set -euo pipefail
|
|
4
6
|
|
|
5
7
|
ORM=${1:?ORM requis}
|
|
6
8
|
FEATURES_PATH="$(dirname "$0")"
|
|
7
9
|
|
|
8
10
|
if [[ "$ORM" != "typeorm" && "$ORM" != "prisma" ]]; then
|
|
9
|
-
|
|
11
|
+
log_error "ORM non supporté : $ORM" >&2
|
|
10
12
|
exit 1
|
|
11
13
|
fi
|
|
12
14
|
|
|
13
15
|
node "$FEATURES_PATH/operational_foundation.mjs" src "$ORM"
|
|
14
16
|
node "$FEATURES_PATH/update_app_module.mjs" src/app.module.ts OperationsModule './operations/operations.module' "$ORM"
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
log_success "Socle d’exploitation configuré : /health/live et /health/ready."
|
|
@@ -12,7 +12,7 @@ PM=${1:?Package manager requis}
|
|
|
12
12
|
APP_NAME=${2:?Nom_application_requis}
|
|
13
13
|
|
|
14
14
|
if [ -e prisma/schema.prisma ]; then
|
|
15
|
-
|
|
15
|
+
log_error "prisma/schema.prisma existe déjà. Aucune configuration Prisma n'a été remplacée."
|
|
16
16
|
exit 1
|
|
17
17
|
fi
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@ cat > .env <<EOF
|
|
|
25
25
|
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/$APP_NAME"
|
|
26
26
|
EOF
|
|
27
27
|
else
|
|
28
|
-
|
|
28
|
+
log_info ".env existe déjà et a été conservé."
|
|
29
29
|
fi
|
|
30
30
|
|
|
31
31
|
cat > prisma/schema.prisma <<EOF
|
|
@@ -70,4 +70,4 @@ EOF
|
|
|
70
70
|
|
|
71
71
|
pm_exec "$PM" prisma generate
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
log_success "Prisma initialisé avec succès !"
|
|
@@ -10,7 +10,7 @@ FEATURES_PATH="$(dirname "$0")"
|
|
|
10
10
|
source "$FEATURES_PATH/utils.sh"
|
|
11
11
|
source "$FEATURES_PATH/logger.sh"
|
|
12
12
|
|
|
13
|
-
log_info "
|
|
13
|
+
log_info "Installation de TypeORM et PostgreSQL..."
|
|
14
14
|
|
|
15
15
|
pm_add "$PM" @nestjs/typeorm@12 typeorm@0.3 pg@8 dotenv@16
|
|
16
16
|
pm_add_dev "$PM" ts-node@10
|
|
@@ -75,6 +75,6 @@ DATABASE_NAME=appdb
|
|
|
75
75
|
EOF
|
|
76
76
|
fi
|
|
77
77
|
|
|
78
|
-
log_success "
|
|
78
|
+
log_success "TypeORM installé avec succès"
|
|
79
79
|
|
|
80
|
-
log_info "
|
|
80
|
+
log_info "TypeORM utilise src/database/typeorm.config.ts et src/database/data-source.ts pour des migrations explicites."
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
+
source "$(dirname "${BASH_SOURCE[0]}")/logger.sh"
|
|
4
|
+
|
|
3
5
|
NEST_CLI_VERSION="12.0.0"
|
|
4
6
|
|
|
5
7
|
assert_package_manager() {
|
|
@@ -7,7 +9,7 @@ assert_package_manager() {
|
|
|
7
9
|
case "$package_manager" in
|
|
8
10
|
npm|pnpm|yarn) assert_command_exists "$package_manager" ;;
|
|
9
11
|
*)
|
|
10
|
-
|
|
12
|
+
log_error "Package manager non supporté : $package_manager. Valeurs acceptées : npm, pnpm, yarn."
|
|
11
13
|
return 1
|
|
12
14
|
;;
|
|
13
15
|
esac
|
|
@@ -57,8 +59,8 @@ nest_new() {
|
|
|
57
59
|
assert_command_exists() {
|
|
58
60
|
local CMD=$1
|
|
59
61
|
if ! command -v "$CMD" &> /dev/null; then
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
log_error "Commande introuvable : $CMD"
|
|
63
|
+
log_info "Installe-la ou vérifie ton PATH"
|
|
62
64
|
return 1
|
|
63
65
|
fi
|
|
64
66
|
}
|
|
@@ -108,12 +108,12 @@ done
|
|
|
108
108
|
|
|
109
109
|
# ────── Résumé final ──────
|
|
110
110
|
echo ""
|
|
111
|
-
log_success "
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if [ "$
|
|
116
|
-
if [ "$
|
|
117
|
-
if [ "$
|
|
118
|
-
if [ "$
|
|
119
|
-
|
|
111
|
+
log_success "Projet NestJS \"$APP_NAME\" généré avec succès"
|
|
112
|
+
printf '\n %-24s %s\n' 'Localisation' "$FULL_PATH"
|
|
113
|
+
printf ' %-24s %s\n' 'Package manager' "$PM" 'ORM' "$ORM"
|
|
114
|
+
if [ "$WITH_DOCKER" = "y" ]; then printf ' %-24s %s\n' 'Docker' 'activé'; fi
|
|
115
|
+
if [ "$WITH_SWAGGER" = "y" ]; then printf ' %-24s %s\n' 'Swagger' 'activé'; fi
|
|
116
|
+
if [ "$WITH_OPERATIONS" = "y" ]; then printf ' %-24s %s\n' 'Socle d’exploitation' 'activé'; fi
|
|
117
|
+
if [ "$WITH_GIT" = "y" ]; then printf ' %-24s %s\n' 'Git' 'initialisé'; fi
|
|
118
|
+
if [ -n "$MODULES" ]; then printf ' %-24s %s\n' 'Modules générés' "$MODULES"; fi
|
|
119
|
+
printf '\n'
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
NestGen CLI generates NestJS project scaffolding and TypeORM or Prisma resources. The canonical package is [`nestgen-cli`](https://www.npmjs.com/package/nestgen-cli); the canonical repository is [`el-hack/nestgen-cli`](https://github.com/el-hack/nestgen-cli).
|
|
8
8
|
|
|
9
|
-
## Published in 0.2.
|
|
9
|
+
## Published in 0.2.2 / Disponible dans 0.2.2
|
|
10
10
|
|
|
11
11
|
| Available now / Disponible maintenant | Evidence / Preuve |
|
|
12
12
|
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
-
| Nest project, module and TypeORM or Prisma resource generation | The commands are shipped by `nestgen-cli@0.2.
|
|
13
|
+
| Nest project, module and TypeORM or Prisma resource generation | The commands are shipped by `nestgen-cli@0.2.2` and covered by the CLI contract tests. |
|
|
14
14
|
| Declarative resource relations, safe updates and migration plans | The generated manifest protects manual edits; migrations remain explicitly reviewed and applied by the developer. |
|
|
15
15
|
| Operational foundation and authorization metadata | Both are optional extension points; NestGen does not configure an identity provider, a global guard or an external monitoring service. |
|
|
16
16
|
| Store API example | The package ships the [version-pinned example](examples/store-api/README.md), which CI reconstructs from its resource definitions. |
|
|
17
17
|
|
|
18
18
|
## Planned (not released) / Prévu (non publié)
|
|
19
19
|
|
|
20
|
-
The [roadmap](roadmap.md) contains topics under consideration after 0.2.
|
|
20
|
+
The [roadmap](roadmap.md) contains topics under consideration after 0.2.2. They have no release date and are not part of the npm contract until they appear in a versioned guide and `CHANGELOG.md`. La roadmap recense des pistes après 0.2.2 ; elles n’ont ni date de sortie ni engagement de compatibilité avant leur publication dans un guide versionné et le changelog.
|
|
21
21
|
|
|
22
22
|
## Supported contract
|
|
23
23
|
|
|
@@ -52,11 +52,11 @@ For Prisma, run `npx prisma generate` and apply the schema with `npx prisma db p
|
|
|
52
52
|
Install the announced release and verify the exact binary before generating anything. Installez la version annoncée et vérifiez le binaire exact avant toute génération.
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
npm install --global nestgen-cli@0.2.
|
|
56
|
-
nestgen --version # 0.2.
|
|
55
|
+
npm install --global nestgen-cli@0.2.2
|
|
56
|
+
nestgen --version # 0.2.2
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Then follow the [Store API example](examples/store-api/README.md): it generates customers, products, orders and order items; starts PostgreSQL; creates an explicit migration; and runs generated tests. The versioned [guide](docs/versions/0.2.
|
|
59
|
+
Then follow the [Store API example](examples/store-api/README.md): it generates customers, products, orders and order items; starts PostgreSQL; creates an explicit migration; and runs generated tests. The versioned [guide](docs/versions/0.2.2.md) documents the commands and constraints used by the demo. Ensuite, suivez l’exemple Store API : il couvre la génération, PostgreSQL, une migration explicite, les tests générés et la documentation de la version.
|
|
60
60
|
|
|
61
61
|
The CI verifies the public commands against the announced package version, reconstructs the example and installs the npm tarball in a clean consumer. It validates reproducibility of these flows, not a production certification / pas une certification de production.
|
|
62
62
|
|
|
@@ -65,7 +65,7 @@ The CI verifies the public commands against the announced package version, recon
|
|
|
65
65
|
The following flow is the supported end-to-end TypeORM path. Prerequisites: Node.js `>=24.15.0 <27`, npm 11 and Docker with Compose. It uses Docker for PostgreSQL and creates an explicit migration.
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
npm install --global nestgen-cli@0.2.
|
|
68
|
+
npm install --global nestgen-cli@0.2.2
|
|
69
69
|
nestgen --version
|
|
70
70
|
nestgen init
|
|
71
71
|
```
|