nestgen-cli 0.2.2 → 0.2.4
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/engine/generation-manifest.js +48 -1
- package/examples/store-api/README.md +3 -3
- package/examples/store-api/nestgen.example.json +1 -1
- package/nestgen.js +86 -83
- package/package.json +4 -2
- package/readme.md +11 -7
|
@@ -52,6 +52,16 @@ export function loadGenerationManifest(projectRoot) {
|
|
|
52
52
|
const generations = value.generations;
|
|
53
53
|
if (!generations || typeof generations !== 'object' || Array.isArray(generations))
|
|
54
54
|
throw new Error('Le manifeste de génération est invalide.');
|
|
55
|
+
for (const entry of Object.values(generations)) {
|
|
56
|
+
if (!entry || typeof entry !== 'object')
|
|
57
|
+
throw new Error('Le manifeste de génération est invalide.');
|
|
58
|
+
const files = entry.files;
|
|
59
|
+
if (!files || typeof files !== 'object' || Array.isArray(files))
|
|
60
|
+
throw new Error('Le manifeste de génération est invalide.');
|
|
61
|
+
for (const fingerprint of Object.values(files))
|
|
62
|
+
if (typeof fingerprint !== 'string' || !/^[a-f0-9]{64}$/.test(fingerprint))
|
|
63
|
+
throw new Error('Empreinte invalide dans le manifeste de génération.');
|
|
64
|
+
}
|
|
55
65
|
return normalizeManifest(value);
|
|
56
66
|
}
|
|
57
67
|
function resourceDefinition(name, orm, sourceRoot, route, table, profile, fields, indexes, relations, list) {
|
|
@@ -83,10 +93,19 @@ export function withGenerationManifest(projectRoot, changes, definition) {
|
|
|
83
93
|
const files = Object.fromEntries(changes
|
|
84
94
|
.map((change) => [change.path, sha256(change.content)])
|
|
85
95
|
.sort(([left], [right]) => left.localeCompare(right)));
|
|
96
|
+
// A later generation can legitimately rewrite shared files (AppModule, Prisma schema,
|
|
97
|
+
// test support). Keep every owner aligned with the content committed by this transaction.
|
|
98
|
+
const generations = Object.fromEntries(Object.entries(existing.generations).map(([key, entry]) => [
|
|
99
|
+
key,
|
|
100
|
+
{
|
|
101
|
+
...entry,
|
|
102
|
+
files: Object.fromEntries(Object.entries(entry.files).map(([file, fingerprint]) => [file, files[file] ?? fingerprint])),
|
|
103
|
+
},
|
|
104
|
+
]));
|
|
86
105
|
const manifest = normalizeManifest({
|
|
87
106
|
...existing,
|
|
88
107
|
generations: {
|
|
89
|
-
...
|
|
108
|
+
...generations,
|
|
90
109
|
[generationKey(definition)]: {
|
|
91
110
|
definition,
|
|
92
111
|
files,
|
|
@@ -106,6 +125,34 @@ export function withGenerationManifest(projectRoot, changes, definition) {
|
|
|
106
125
|
},
|
|
107
126
|
];
|
|
108
127
|
}
|
|
128
|
+
/** Check all recorded paths once. Older manifests may hold historical hashes for shared files. */
|
|
129
|
+
export function checkGeneratedFiles(projectRoot) {
|
|
130
|
+
const manifest = loadGenerationManifest(projectRoot);
|
|
131
|
+
if (!manifest)
|
|
132
|
+
throw new Error('Manifeste de génération introuvable. Générez d’abord une ressource ou un module.');
|
|
133
|
+
const expected = new Map();
|
|
134
|
+
for (const entry of Object.values(manifest.generations)) {
|
|
135
|
+
for (const [file, fingerprint] of Object.entries(entry.files)) {
|
|
136
|
+
projectPath(projectRoot, file);
|
|
137
|
+
const hashes = expected.get(file) ?? new Set();
|
|
138
|
+
hashes.add(fingerprint);
|
|
139
|
+
expected.set(file, hashes);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (expected.size === 0)
|
|
143
|
+
throw new Error('Le manifeste de génération ne contient aucun fichier à vérifier.');
|
|
144
|
+
const missing = [];
|
|
145
|
+
const modified = [];
|
|
146
|
+
for (const [file, fingerprints] of [...expected].sort(([left], [right]) => left.localeCompare(right))) {
|
|
147
|
+
const target = projectPath(projectRoot, file);
|
|
148
|
+
const stat = fs.lstatSync(target, { throwIfNoEntry: false });
|
|
149
|
+
if (!stat)
|
|
150
|
+
missing.push(file);
|
|
151
|
+
else if (!stat.isFile() || !fingerprints.has(sha256(fs.readFileSync(target, 'utf8'))))
|
|
152
|
+
modified.push(file);
|
|
153
|
+
}
|
|
154
|
+
return { checkedFiles: expected.size, missing, modified };
|
|
155
|
+
}
|
|
109
156
|
/** Report generated files changed after their recorded successful generation. */
|
|
110
157
|
export function modifiedGeneratedFiles(projectRoot, entry) {
|
|
111
158
|
return Object.entries(entry.files)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Exemple Store API — NestGen CLI 0.2.
|
|
1
|
+
# Exemple Store API — NestGen CLI 0.2.4
|
|
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.4` 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.4
|
|
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';
|
|
@@ -28,6 +30,7 @@ import {
|
|
|
28
30
|
formatMigration,
|
|
29
31
|
formatHelp,
|
|
30
32
|
} from './dist/cli/output.js';
|
|
33
|
+
import { checkGeneratedFiles } from './dist/engine/generation-manifest.js';
|
|
31
34
|
|
|
32
35
|
// ────── Resolve __dirname compatible ES Modules
|
|
33
36
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -241,72 +244,57 @@ export function runBashScript(scriptPath, args = [], env = {}, quiet = false) {
|
|
|
241
244
|
|
|
242
245
|
// ────── Commande : INIT
|
|
243
246
|
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
|
-
]);
|
|
247
|
+
const projectName = await input({
|
|
248
|
+
message: '📛 Nom du projet :',
|
|
249
|
+
default: 'my-app',
|
|
250
|
+
validate: validatePromptValue(validateModuleName),
|
|
251
|
+
});
|
|
252
|
+
const projectPath = await input({
|
|
253
|
+
message: '📁 Dossier cible :',
|
|
254
|
+
default: './',
|
|
255
|
+
validate: validatePromptValue(resolveProjectPath),
|
|
256
|
+
});
|
|
257
|
+
const packageManager = await select({
|
|
258
|
+
message: '📦 Package manager :',
|
|
259
|
+
choices: ['pnpm', 'yarn', 'npm'].map((value) => ({ name: value, value })),
|
|
260
|
+
default: 'pnpm',
|
|
261
|
+
});
|
|
262
|
+
const orm = await select({
|
|
263
|
+
message: '🧠 ORM :',
|
|
264
|
+
choices: ['typeorm', 'prisma'].map((value) => ({ name: value, value })),
|
|
265
|
+
default: 'typeorm',
|
|
266
|
+
});
|
|
267
|
+
const withSwagger = await confirm({ message: '📚 Activer Swagger ?', default: true });
|
|
268
|
+
const withDocker = await confirm({ message: '🐳 Activer Docker ?', default: false });
|
|
269
|
+
const withOperations = await confirm({
|
|
270
|
+
message: '🩺 Activer le socle d’exploitation (santé, logs, arrêt propre) ?',
|
|
271
|
+
default: false,
|
|
272
|
+
});
|
|
273
|
+
const withGit = await confirm({ message: '🔃 Initialiser Git ?', default: true });
|
|
274
|
+
const modulesInput = await input({
|
|
275
|
+
message: '📦 Modules à générer (séparés par des espaces) :',
|
|
276
|
+
default: 'user',
|
|
277
|
+
validate: validatePromptValue((value) => parseModulesInput(value).forEach(validateModuleName)),
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
projectName,
|
|
282
|
+
projectPath,
|
|
283
|
+
packageManager,
|
|
284
|
+
orm,
|
|
285
|
+
withSwagger,
|
|
286
|
+
withDocker,
|
|
287
|
+
withOperations,
|
|
288
|
+
withGit,
|
|
289
|
+
modules: parseModulesInput(modulesInput),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function parseModulesInput(value) {
|
|
294
|
+
return value
|
|
295
|
+
.split(' ')
|
|
296
|
+
.map((moduleName) => moduleName.trim())
|
|
297
|
+
.filter(Boolean);
|
|
310
298
|
}
|
|
311
299
|
|
|
312
300
|
function validatePromptValue(validator) {
|
|
@@ -393,23 +381,19 @@ async function runModuleGeneration(parsed) {
|
|
|
393
381
|
orm = validateOrm(parsed.options.orm ?? config.orm);
|
|
394
382
|
} else {
|
|
395
383
|
if (parsed.options.noInteractive) throw new Error('module --no-interactive requiert un nom de module.');
|
|
396
|
-
|
|
397
|
-
{
|
|
398
|
-
type: 'input',
|
|
399
|
-
name: 'moduleName',
|
|
384
|
+
moduleName = validateModuleName(
|
|
385
|
+
await input({
|
|
400
386
|
message: '📦 Nom du module :',
|
|
401
387
|
validate: validatePromptValue(validateModuleName),
|
|
402
|
-
},
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
388
|
+
}),
|
|
389
|
+
);
|
|
390
|
+
orm = validateOrm(
|
|
391
|
+
await select({
|
|
406
392
|
message: '🧠 ORM utilisé :',
|
|
407
|
-
choices: ['typeorm', 'prisma'],
|
|
393
|
+
choices: ['typeorm', 'prisma'].map((value) => ({ name: value, value })),
|
|
408
394
|
default: 'typeorm',
|
|
409
|
-
},
|
|
410
|
-
|
|
411
|
-
moduleName = validateModuleName(answers.moduleName);
|
|
412
|
-
orm = validateOrm(answers.orm);
|
|
395
|
+
}),
|
|
396
|
+
);
|
|
413
397
|
}
|
|
414
398
|
|
|
415
399
|
if (!isNestProject()) {
|
|
@@ -566,6 +550,7 @@ function printUsage() {
|
|
|
566
550
|
console.log(
|
|
567
551
|
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
552
|
config init|show Gère nestgen.config.json
|
|
553
|
+
check Vérifie les fichiers générés sans les modifier
|
|
569
554
|
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
555
|
--package-manager <pm> Définit npm, pnpm ou yarn
|
|
571
556
|
--project-path <chemin> Définit le dossier parent du projet init
|
|
@@ -731,14 +716,15 @@ function runDoctor(options = {}) {
|
|
|
731
716
|
}
|
|
732
717
|
|
|
733
718
|
function machineResult(command, result) {
|
|
734
|
-
return { version: 1, ok:
|
|
719
|
+
return { version: 1, ok: command !== 'check' || result.ok, command, result };
|
|
735
720
|
}
|
|
736
721
|
|
|
737
722
|
function errorCode(error) {
|
|
738
723
|
const message = error instanceof Error ? error.message : String(error);
|
|
739
724
|
if (/^(Option inconnue|Commande inconnue|.* requiert |Route ou table invalide|Nom de )/.test(message))
|
|
740
725
|
return 'USAGE';
|
|
741
|
-
if (message.startsWith('Préflight échoué'))
|
|
726
|
+
if (message.startsWith('Préflight échoué') || message.toLowerCase().includes('manifeste de génération'))
|
|
727
|
+
return 'PROJECT_INVALID';
|
|
742
728
|
if (/(existe déjà|Destination |Conflit |Transaction interrompue)/.test(message)) return 'CONFLICT';
|
|
743
729
|
return 'INTERNAL';
|
|
744
730
|
}
|
|
@@ -762,7 +748,9 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
762
748
|
if (parsed.options.help || !parsed.command) {
|
|
763
749
|
if (machine)
|
|
764
750
|
console.log(
|
|
765
|
-
JSON.stringify(
|
|
751
|
+
JSON.stringify(
|
|
752
|
+
machineResult('help', { commands: ['init', 'module', 'resource', 'config', 'check', 'doctor'] }),
|
|
753
|
+
),
|
|
766
754
|
);
|
|
767
755
|
else printUsage();
|
|
768
756
|
return;
|
|
@@ -824,6 +812,21 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
824
812
|
result = runDoctor(parsed.options);
|
|
825
813
|
break;
|
|
826
814
|
|
|
815
|
+
case 'check': {
|
|
816
|
+
if (parsed.positionals.length) throw new Error('check ne prend aucun argument.');
|
|
817
|
+
const report = checkGeneratedFiles(process.cwd());
|
|
818
|
+
result = { ok: report.missing.length === 0 && report.modified.length === 0, ...report };
|
|
819
|
+
if (!machine) {
|
|
820
|
+
if (result.ok) console.log(`${report.checkedFiles} fichiers générés vérifiés.`);
|
|
821
|
+
else {
|
|
822
|
+
for (const file of report.missing) console.log(`MANQUANT ${file}`);
|
|
823
|
+
for (const file of report.modified) console.log(`MODIFIÉ ${file}`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (!result.ok) process.exitCode = 1;
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
|
|
827
830
|
default:
|
|
828
831
|
throw new Error(`Commande inconnue : ${parsed.command}. Utilise --help.`);
|
|
829
832
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestgen-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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,19 @@
|
|
|
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.4 / Disponible dans 0.2.4
|
|
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.4` 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
|
+
| Read-only generation drift check | `nestgen check` compares recorded file fingerprints and returns a nonzero status for missing or modified generated files. |
|
|
15
16
|
| 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
17
|
| Store API example | The package ships the [version-pinned example](examples/store-api/README.md), which CI reconstructs from its resource definitions. |
|
|
17
18
|
|
|
18
19
|
## Planned (not released) / Prévu (non publié)
|
|
19
20
|
|
|
20
|
-
The [roadmap](roadmap.md) contains topics under consideration after 0.2.
|
|
21
|
+
The [roadmap](roadmap.md) contains topics under consideration after 0.2.4. 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.4 ; elles n’ont ni date de sortie ni engagement de compatibilité avant leur publication dans un guide versionné et le changelog.
|
|
21
22
|
|
|
22
23
|
## Supported contract
|
|
23
24
|
|
|
@@ -52,11 +53,11 @@ For Prisma, run `npx prisma generate` and apply the schema with `npx prisma db p
|
|
|
52
53
|
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
54
|
|
|
54
55
|
```bash
|
|
55
|
-
npm install --global nestgen-cli@0.2.
|
|
56
|
-
nestgen --version # 0.2.
|
|
56
|
+
npm install --global nestgen-cli@0.2.4
|
|
57
|
+
nestgen --version # 0.2.4
|
|
57
58
|
```
|
|
58
59
|
|
|
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.
|
|
60
|
+
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.4.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
61
|
|
|
61
62
|
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
63
|
|
|
@@ -65,7 +66,7 @@ The CI verifies the public commands against the announced package version, recon
|
|
|
65
66
|
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
67
|
|
|
67
68
|
```bash
|
|
68
|
-
npm install --global nestgen-cli@0.2.
|
|
69
|
+
npm install --global nestgen-cli@0.2.4
|
|
69
70
|
nestgen --version
|
|
70
71
|
nestgen init
|
|
71
72
|
```
|
|
@@ -131,9 +132,12 @@ nestgen module <name> [--orm typeorm|prisma] [--application name] [--dry-run]
|
|
|
131
132
|
nestgen resource <name> --fields name:type[,name:type] [--route route] [--table table] [--profile simple|advanced] [--application name]
|
|
132
133
|
nestgen config init [--orm typeorm|prisma] [--profile simple|advanced] [--package-manager npm|pnpm|yarn]
|
|
133
134
|
nestgen config show
|
|
135
|
+
nestgen check
|
|
134
136
|
nestgen doctor
|
|
135
137
|
```
|
|
136
138
|
|
|
139
|
+
`nestgen check` compares the files recorded in `.nestgen/generation-manifest.json` with their current contents. It is read-only, exits with status `1` when a recorded file is missing or modified, and supports `--json` for CI. Run it from the generated Nest project. It reports generated-file drift; it does not validate runtime behavior or database migrations. New generations update recorded fingerprints for shared files such as `app.module.ts` across all affected resources.
|
|
140
|
+
|
|
137
141
|
Pour la CI, `init` accepte toutes les réponses interactives sous forme d’options :
|
|
138
142
|
|
|
139
143
|
```bash
|