nestgen-cli 0.2.1 → 0.2.3

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.
@@ -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
1
+ # Exemple Store API — NestGen CLI 0.2.3
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.1` et TypeORM, avec le profil d’architecture `advanced`.
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` 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.1
14
+ npm install nestgen-cli@0.2.3
15
15
 
16
16
  ./node_modules/.bin/nestgen init store-api \
17
17
  --no-interactive \
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "cliVersion": "0.2.1",
3
+ "cliVersion": "0.2.3",
4
4
  "orm": "typeorm",
5
5
  "profile": "advanced",
6
6
  "resources": ["customers", "products", "orders", "order-items"]
package/nestgen.js CHANGED
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from 'child_process';
4
- import inquirer from 'inquirer';
4
+ import input from '@inquirer/input';
5
+ import select from '@inquirer/select';
6
+ import confirm from '@inquirer/confirm';
5
7
  import chalk from 'chalk';
6
8
  import path from 'path';
7
9
  import fs from 'fs';
@@ -18,6 +20,16 @@ import {
18
20
  import { configFileName, defaultConfig, loadConfigIfPresent, writeConfig } from './dist/engine/project-config.js';
19
21
  import { parseArchitectureProfile } from './dist/engine/architecture-profile.js';
20
22
  import { planResourceMigration } from './dist/engine/migration-plan.js';
23
+ import { formatDoctorOutput } from './dist/cli/doctor-output.js';
24
+ import {
25
+ heading,
26
+ success,
27
+ formatError,
28
+ formatConfig,
29
+ formatPlan,
30
+ formatMigration,
31
+ formatHelp,
32
+ } from './dist/cli/output.js';
21
33
 
22
34
  // ────── Resolve __dirname compatible ES Modules
23
35
  const __filename = fileURLToPath(import.meta.url);
@@ -32,17 +44,7 @@ const MODULE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i;
32
44
 
33
45
  // ────── Logo CLI
34
46
  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'));
47
+ console.log(heading('CLI', 'Générateur modulaire NestJS'));
46
48
  }
47
49
 
48
50
  // ────── Helpers
@@ -226,7 +228,7 @@ function dryRunOutput(operation, plan) {
226
228
 
227
229
  export function runBashScript(scriptPath, args = [], env = {}, quiet = false) {
228
230
  const result = spawnSync('bash', [scriptPath, ...args], {
229
- env: { ...process.env, ...env },
231
+ env: { ...process.env, ...(chalk.level === 0 ? { NO_COLOR: '1' } : {}), ...env },
230
232
  stdio: quiet ? 'pipe' : 'inherit',
231
233
  });
232
234
 
@@ -241,72 +243,57 @@ export function runBashScript(scriptPath, args = [], env = {}, quiet = false) {
241
243
 
242
244
  // ────── Commande : INIT
243
245
  async function askInitQuestions() {
244
- return await inquirer.prompt([
245
- {
246
- type: 'input',
247
- name: 'projectName',
248
- message: '📛 Nom du projet :',
249
- default: 'my-app',
250
- validate: validatePromptValue(validateModuleName),
251
- },
252
- {
253
- type: 'input',
254
- name: 'projectPath',
255
- message: '📁 Dossier cible :',
256
- default: './',
257
- validate: validatePromptValue(resolveProjectPath),
258
- },
259
- {
260
- type: 'list',
261
- name: 'packageManager',
262
- message: '📦 Package manager :',
263
- choices: ['pnpm', 'yarn', 'npm'],
264
- default: 'pnpm',
265
- },
266
- {
267
- type: 'list',
268
- name: 'orm',
269
- message: '🧠 ORM :',
270
- choices: ['typeorm', 'prisma'],
271
- default: 'typeorm',
272
- },
273
- {
274
- type: 'confirm',
275
- name: 'withSwagger',
276
- message: '📚 Activer Swagger ?',
277
- default: true,
278
- },
279
- {
280
- type: 'confirm',
281
- name: 'withDocker',
282
- message: '🐳 Activer Docker ?',
283
- default: false,
284
- },
285
- {
286
- type: 'confirm',
287
- name: 'withOperations',
288
- message: '🩺 Activer le socle d’exploitation (santé, logs, arrêt propre) ?',
289
- default: false,
290
- },
291
- {
292
- type: 'confirm',
293
- name: 'withGit',
294
- message: '🔃 Initialiser Git ?',
295
- default: true,
296
- },
297
- {
298
- type: 'input',
299
- name: 'modules',
300
- message: '📦 Modules à générer (séparés par des espaces) :',
301
- default: 'user',
302
- filter: (input) =>
303
- input
304
- .split(' ')
305
- .map((s) => s.trim())
306
- .filter(Boolean),
307
- validate: validatePromptValue((modules) => modules.forEach(validateModuleName)),
308
- },
309
- ]);
246
+ const projectName = await input({
247
+ message: '📛 Nom du projet :',
248
+ default: 'my-app',
249
+ validate: validatePromptValue(validateModuleName),
250
+ });
251
+ const projectPath = await input({
252
+ message: '📁 Dossier cible :',
253
+ default: './',
254
+ validate: validatePromptValue(resolveProjectPath),
255
+ });
256
+ const packageManager = await select({
257
+ message: '📦 Package manager :',
258
+ choices: ['pnpm', 'yarn', 'npm'].map((value) => ({ name: value, value })),
259
+ default: 'pnpm',
260
+ });
261
+ const orm = await select({
262
+ message: '🧠 ORM :',
263
+ choices: ['typeorm', 'prisma'].map((value) => ({ name: value, value })),
264
+ default: 'typeorm',
265
+ });
266
+ const withSwagger = await confirm({ message: '📚 Activer Swagger ?', default: true });
267
+ const withDocker = await confirm({ message: '🐳 Activer Docker ?', default: false });
268
+ const withOperations = await confirm({
269
+ message: '🩺 Activer le socle d’exploitation (santé, logs, arrêt propre) ?',
270
+ default: false,
271
+ });
272
+ const withGit = await confirm({ message: '🔃 Initialiser Git ?', default: true });
273
+ const modulesInput = await input({
274
+ message: '📦 Modules à générer (séparés par des espaces) :',
275
+ default: 'user',
276
+ validate: validatePromptValue((value) => parseModulesInput(value).forEach(validateModuleName)),
277
+ });
278
+
279
+ return {
280
+ projectName,
281
+ projectPath,
282
+ packageManager,
283
+ orm,
284
+ withSwagger,
285
+ withDocker,
286
+ withOperations,
287
+ withGit,
288
+ modules: parseModulesInput(modulesInput),
289
+ };
290
+ }
291
+
292
+ function parseModulesInput(value) {
293
+ return value
294
+ .split(' ')
295
+ .map((moduleName) => moduleName.trim())
296
+ .filter(Boolean);
310
297
  }
311
298
 
312
299
  function validatePromptValue(validator) {
@@ -370,7 +357,7 @@ async function runInteractiveInit(options) {
370
357
  MODULES: modules.map(validateModuleName).join(' '),
371
358
  };
372
359
 
373
- if (!options.quiet) console.log('\n🚀 Lancement de la génération du projet...\n');
360
+ if (!options.quiet) console.log(chalk.cyan(' › Génération du projet…'));
374
361
  runBashScript(GENERATE_SCRIPT, [], env, options.quiet);
375
362
  return {
376
363
  projectName: env.APP_NAME,
@@ -393,23 +380,19 @@ async function runModuleGeneration(parsed) {
393
380
  orm = validateOrm(parsed.options.orm ?? config.orm);
394
381
  } else {
395
382
  if (parsed.options.noInteractive) throw new Error('module --no-interactive requiert un nom de module.');
396
- const answers = await inquirer.prompt([
397
- {
398
- type: 'input',
399
- name: 'moduleName',
383
+ moduleName = validateModuleName(
384
+ await input({
400
385
  message: '📦 Nom du module :',
401
386
  validate: validatePromptValue(validateModuleName),
402
- },
403
- {
404
- type: 'list',
405
- name: 'orm',
387
+ }),
388
+ );
389
+ orm = validateOrm(
390
+ await select({
406
391
  message: '🧠 ORM utilisé :',
407
- choices: ['typeorm', 'prisma'],
392
+ choices: ['typeorm', 'prisma'].map((value) => ({ name: value, value })),
408
393
  default: 'typeorm',
409
- },
410
- ]);
411
- moduleName = validateModuleName(answers.moduleName);
412
- orm = validateOrm(answers.orm);
394
+ }),
395
+ );
413
396
  }
414
397
 
415
398
  if (!isNestProject()) {
@@ -419,11 +402,11 @@ async function runModuleGeneration(parsed) {
419
402
  if (parsed.options.dryRun) {
420
403
  const plan = await planModuleGeneration(process.cwd(), moduleName, orm, parsed.options.application);
421
404
  const output = dryRunOutput('module', plan);
422
- if (!parsed.options.json) console.log(JSON.stringify(output, null, 2));
405
+ if (!parsed.options.json) console.log(formatPlan(output, parsed.options.verbose));
423
406
  return output;
424
407
  }
425
408
 
426
- if (!parsed.options.quiet) console.log(chalk.cyan(`\n⚙️ Génération du module ${moduleName}...\n`));
409
+ if (!parsed.options.quiet) console.log(chalk.cyan(` › Génération du module ${moduleName}…`));
427
410
  if (process.env.NESTGEN_ROOT) {
428
411
  runBashScript(ADD_MODULE_SCRIPT, [moduleName, orm], {}, parsed.options.quiet);
429
412
  return { module: moduleName, orm, generated: true };
@@ -548,7 +531,7 @@ async function runResourceGeneration(parsed) {
548
531
  if (parsed.options.dryRun) {
549
532
  const plan = await planResourceGeneration(process.cwd(), resourceOptions);
550
533
  const output = { ...dryRunOutput('resource', plan), ...(migration ? { migration } : {}) };
551
- if (!parsed.options.json) console.log(JSON.stringify(output, null, 2));
534
+ if (!parsed.options.json) console.log(formatPlan(output, parsed.options.verbose));
552
535
  return output;
553
536
  }
554
537
  await generateResource(process.cwd(), resourceOptions);
@@ -564,14 +547,14 @@ async function runResourceGeneration(parsed) {
564
547
 
565
548
  function printUsage() {
566
549
  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
550
+ 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
551
  config init|show Gère nestgen.config.json
569
552
  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
553
  --package-manager <pm> Définit npm, pnpm ou yarn
571
554
  --project-path <chemin> Définit le dossier parent du projet init
572
555
  --docker, --no-docker Active ou désactive Docker pour init
573
556
  --swagger, --no-swagger Active ou désactive Swagger pour init
574
- --operations, --no-operations Active ou désactive le socle d’exploitation pour init
557
+ --operations, --no-operations Active ou désactive le socle d’exploitation pour init
575
558
  --git, --no-git Active ou désactive Git pour init
576
559
  --modules <a,b> Définit les modules init, séparés par des virgules
577
560
  --application <nom> Cible une application d’un workspace Nest
@@ -579,18 +562,19 @@ function printUsage() {
579
562
  --migration-name <nom> Prépare une commande de migration officielle avec --update, sans l’appliquer
580
563
  --dry-run Affiche le plan sans écrire
581
564
  --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.`,
565
+ --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
566
  );
584
567
  }
585
568
 
586
- function runDoctor(machine = false) {
569
+ function runDoctor(options = {}) {
587
570
  const diagnostics = [];
588
571
  const add = (id, severity, cause, action, detail) => diagnostics.push({ id, severity, cause, action, detail });
589
572
  const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(Number);
573
+ const supportedNode = (nodeMajor === 24 && nodeMinor >= 15) || nodeMajor === 25 || nodeMajor === 26;
590
574
  add(
591
575
  'NODE_VERSION',
592
- nodeMajor === 24 && nodeMinor >= 15 ? 'INFO' : 'ERROR',
593
- nodeMajor === 24 && nodeMinor >= 15 ? 'Version Node.js compatible.' : 'Node.js doit être >=24.15.0 et <27.',
576
+ supportedNode ? 'INFO' : 'ERROR',
577
+ supportedNode ? 'Version Node.js compatible.' : 'Node.js doit être >=24.15.0 et <27.',
594
578
  'Installe une version Node.js supportée puis relance doctor.',
595
579
  process.versions.node,
596
580
  );
@@ -722,9 +706,10 @@ function runDoctor(machine = false) {
722
706
  error.message,
723
707
  );
724
708
  }
725
- if (!machine)
726
- for (const diagnostic of diagnostics)
727
- console.log(`${diagnostic.severity} ${diagnostic.id}: ${diagnostic.cause} — ${diagnostic.action}`);
709
+ if (!options.json) {
710
+ const output = formatDoctorOutput(diagnostics, options);
711
+ if (output) console.log(output);
712
+ }
728
713
  return { diagnostics, project };
729
714
  }
730
715
 
@@ -785,7 +770,15 @@ export async function main(args = process.argv.slice(2)) {
785
770
  const action = parsed.positionals[0];
786
771
  if (action === 'show') {
787
772
  result = loadConfigIfPresent(process.cwd()) ?? defaultConfig;
788
- if (!machine) console.log(JSON.stringify(result, null, 2));
773
+ if (!machine)
774
+ console.log(
775
+ formatConfig(
776
+ result,
777
+ fs.existsSync(path.join(process.cwd(), configFileName))
778
+ ? configFileName
779
+ : 'Valeurs par défaut',
780
+ ),
781
+ );
789
782
  break;
790
783
  }
791
784
  if (action === 'init') {
@@ -799,25 +792,37 @@ export async function main(args = process.argv.slice(2)) {
799
792
  throw new Error('Package manager invalide.');
800
793
  if (parsed.options.dryRun) {
801
794
  result = { operation: 'config-init', file: configFileName, config };
802
- if (!machine) console.log(JSON.stringify(result, null, 2));
795
+ if (!parsed.options.quiet) console.log(formatConfig(config, 'Aperçu · aucun fichier écrit'));
803
796
  break;
804
797
  }
805
798
  writeConfig(process.cwd(), config);
806
799
  result = { file: configFileName, created: true, config };
807
- if (!machine) console.log(configFileName);
800
+ if (!parsed.options.quiet) console.log(success(`Configuration créée : ${configFileName}`));
808
801
  break;
809
802
  }
810
803
  throw new Error('config requiert init ou show.');
811
804
  }
812
805
 
813
806
  case 'doctor':
814
- result = runDoctor(machine);
807
+ result = runDoctor(parsed.options);
815
808
  break;
816
809
 
817
810
  default:
818
811
  throw new Error(`Commande inconnue : ${parsed.command}. Utilise --help.`);
819
812
  }
820
813
  if (machine) console.log(JSON.stringify(machineResult(parsed.command, result)));
814
+ else if (!parsed.options.quiet && result) {
815
+ if (parsed.command === 'module' && result.generated)
816
+ console.log(success(`Module ${result.module} généré (${result.orm}).`));
817
+ if (parsed.command === 'resource' && result.generated) {
818
+ console.log(
819
+ success(
820
+ `Ressource ${result.resource} ${result.updated ? 'mise à jour' : 'générée'} (${result.orm} · ${result.profile}).`,
821
+ ),
822
+ );
823
+ if (result.migration) console.log(formatMigration(result.migration));
824
+ }
825
+ }
821
826
  return result;
822
827
  }
823
828
 
@@ -826,7 +831,10 @@ if (process.argv[1] && fs.realpathSync(process.argv[1]) === __filename) {
826
831
  if (process.argv.includes('--json')) {
827
832
  const message = error instanceof Error ? error.message : String(error);
828
833
  console.error(JSON.stringify({ version: 1, ok: false, error: { code: errorCode(error), message } }));
829
- } else console.error(chalk.red(`❌ ${error.message}`));
834
+ } else {
835
+ if (process.argv.includes('--no-color')) chalk.level = 0;
836
+ console.error(formatError(error.message));
837
+ }
830
838
  process.exitCode = 1;
831
839
  });
832
840
  }
@@ -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
- echo "❌ Package manager non supporté pour Docker : $PM."
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
- echo "❌ $file existe déjà. Aucune configuration Docker n'a été remplacée."
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
- echo "ℹ️ .env.example existe déjà et a été conservé."
191
+ log_info ".env.example existe déjà et a été conservé."
190
192
  fi
191
193
 
192
- echo "✅ Docker de développement et image de production générés pour $APP_NAME ($PM)."
194
+ log_success "Docker de développement et image de production générés pour $APP_NAME ($PM)."
@@ -11,4 +11,4 @@ source "$FEATURES_PATH/logger.sh"
11
11
  git init
12
12
  git add .
13
13
  git commit -m "🚀 Initial commit (NestJS starter clean architecture)"
14
- echo "✅ Git initialisé et commité !"
14
+ log_success "Git initialisé et commité !"
@@ -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
- echo "❌ Tu dois fournir un nom de module." >&2
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
- echo "❌ ORM non supporté : ${ORM:-vide}." >&2
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
- echo "❌ Le fichier $MODULE_FILE est introuvable." >&2
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
- echo "❌ Impossible de détecter la classe du module dans $MODULE_FILE" >&2
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
- echo "📄 Création de $APP_MODULE"
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
- echo "🎯 Module $MODULE_CLASS injecté dans app.module.ts avec succès 🧩"
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
- echo -e "${BLUE}ℹ️ $1${NC}"
15
+ printf ' %b›%b %s\n' "$BLUE" "$NC" "$1"
12
16
  }
13
17
 
14
18
  log_success() {
15
- echo -e "${GREEN}✅ $1${NC}"
19
+ printf ' %b✓%b %s\n' "$GREEN" "$NC" "$1"
16
20
  }
17
21
 
18
22
  log_warn() {
19
- echo -e "${YELLOW}⚠️ $1${NC}"
23
+ printf ' %b⚠%b %s\n' "$YELLOW" "$NC" "$1"
20
24
  }
21
25
 
22
26
  log_error() {
23
- echo -e "${RED}❌ $1${NC}"
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
- echo "❌ ORM non supporté : $ORM" >&2
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
- echo "✅ Socle d’exploitation configuré : /health/live et /health/ready."
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
- echo "❌ prisma/schema.prisma existe déjà. Aucune configuration Prisma n'a été remplacée."
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
- echo "ℹ️ .env existe déjà et a été conservé."
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
- echo "✅ Prisma initialisé avec succès !"
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 "📦 Installation de TypeORM et PostgreSQL..."
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 "✅ TypeORM installé avec succès"
78
+ log_success "TypeORM installé avec succès"
79
79
 
80
- log_info "💡 TypeORM utilise src/database/typeorm.config.ts et src/database/data-source.ts pour des migrations explicites."
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
- echo "❌ Package manager non supporté : $package_manager. Valeurs acceptées : npm, pnpm, yarn."
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
- echo "❌ Commande introuvable : $CMD"
61
- echo "👉 Installe-la ou vérifie ton PATH"
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 "✅ Projet NestJS \"$APP_NAME\" généré avec succès 🎉"
112
- echo "📁 Localisation : $FULL_PATH"
113
- echo "📦 Package manager : $PM"
114
- echo "🧠 ORM : $ORM"
115
- if [ "$WITH_DOCKER" = "y" ]; then echo "🐳 Docker activé"; fi
116
- if [ "$WITH_SWAGGER" = "y" ]; then echo "📚 Swagger activé"; fi
117
- if [ "$WITH_OPERATIONS" = "y" ]; then echo "🩺 Socle d’exploitation activé"; fi
118
- if [ "$WITH_GIT" = "y" ]; then echo "🔃 Git initialisé"; fi
119
- if [ -n "$MODULES" ]; then echo "📦 Modules générés : $MODULES"; fi
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestgen-cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "NestGen CLI — Générateur modulaire NestJS (DDD, CQRS, Hexagonal)",
5
5
  "files": [
6
6
  "dist",
@@ -52,8 +52,10 @@
52
52
  },
53
53
  "licenses": "MIT",
54
54
  "dependencies": {
55
+ "@inquirer/confirm": "^6.3.3",
56
+ "@inquirer/input": "^5.1.7",
57
+ "@inquirer/select": "^5.2.6",
55
58
  "chalk": "^5.3.0",
56
- "inquirer": "^9.0.0",
57
59
  "prettier": "^3.9.9",
58
60
  "typescript": "^5.9.3"
59
61
  },
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.1 / Disponible dans 0.2.1
9
+ ## Published in 0.2.3 / Disponible dans 0.2.3
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.1` and covered by the CLI contract tests. |
13
+ | Nest project, module and TypeORM or Prisma resource generation | The commands are shipped by `nestgen-cli@0.2.3` 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.1. 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.1 ; elles n’ont ni date de sortie ni engagement de compatibilité avant leur publication dans un guide versionné et le changelog.
20
+ The [roadmap](roadmap.md) contains topics under consideration after 0.2.3. 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.3 ; 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.1
56
- nestgen --version # 0.2.1
55
+ npm install --global nestgen-cli@0.2.3
56
+ nestgen --version # 0.2.3
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.1.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.
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.3.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.1
68
+ npm install --global nestgen-cli@0.2.3
69
69
  nestgen --version
70
70
  nestgen init
71
71
  ```