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.
- 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 +118 -110
- 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 +4 -2
- 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.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.
|
|
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.
|
|
14
|
+
npm install nestgen-cli@0.2.3
|
|
15
15
|
|
|
16
16
|
./node_modules/.bin/nestgen init store-api \
|
|
17
17
|
--no-interactive \
|
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
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
405
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
593
|
-
|
|
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 (!
|
|
726
|
-
|
|
727
|
-
|
|
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)
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestgen-cli",
|
|
3
|
-
"version": "0.2.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
56
|
-
nestgen --version # 0.2.
|
|
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.
|
|
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.
|
|
68
|
+
npm install --global nestgen-cli@0.2.3
|
|
69
69
|
nestgen --version
|
|
70
70
|
nestgen init
|
|
71
71
|
```
|