nestgen-cli 0.2.3 → 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.
@@ -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
- ...existing.generations,
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.3
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` 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.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.3
14
+ npm install nestgen-cli@0.2.4
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.3",
3
+ "cliVersion": "0.2.4",
4
4
  "orm": "typeorm",
5
5
  "profile": "advanced",
6
6
  "resources": ["customers", "products", "orders", "order-items"]
package/nestgen.js CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  formatMigration,
31
31
  formatHelp,
32
32
  } from './dist/cli/output.js';
33
+ import { checkGeneratedFiles } from './dist/engine/generation-manifest.js';
33
34
 
34
35
  // ────── Resolve __dirname compatible ES Modules
35
36
  const __filename = fileURLToPath(import.meta.url);
@@ -549,6 +550,7 @@ function printUsage() {
549
550
  console.log(
550
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
551
552
  config init|show Gère nestgen.config.json
553
+ check Vérifie les fichiers générés sans les modifier
552
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
553
555
  --package-manager <pm> Définit npm, pnpm ou yarn
554
556
  --project-path <chemin> Définit le dossier parent du projet init
@@ -714,14 +716,15 @@ function runDoctor(options = {}) {
714
716
  }
715
717
 
716
718
  function machineResult(command, result) {
717
- return { version: 1, ok: true, command, result };
719
+ return { version: 1, ok: command !== 'check' || result.ok, command, result };
718
720
  }
719
721
 
720
722
  function errorCode(error) {
721
723
  const message = error instanceof Error ? error.message : String(error);
722
724
  if (/^(Option inconnue|Commande inconnue|.* requiert |Route ou table invalide|Nom de )/.test(message))
723
725
  return 'USAGE';
724
- if (message.startsWith('Préflight échoué')) return 'PROJECT_INVALID';
726
+ if (message.startsWith('Préflight échoué') || message.toLowerCase().includes('manifeste de génération'))
727
+ return 'PROJECT_INVALID';
725
728
  if (/(existe déjà|Destination |Conflit |Transaction interrompue)/.test(message)) return 'CONFLICT';
726
729
  return 'INTERNAL';
727
730
  }
@@ -745,7 +748,9 @@ export async function main(args = process.argv.slice(2)) {
745
748
  if (parsed.options.help || !parsed.command) {
746
749
  if (machine)
747
750
  console.log(
748
- JSON.stringify(machineResult('help', { commands: ['init', 'module', 'resource', 'config', 'doctor'] })),
751
+ JSON.stringify(
752
+ machineResult('help', { commands: ['init', 'module', 'resource', 'config', 'check', 'doctor'] }),
753
+ ),
749
754
  );
750
755
  else printUsage();
751
756
  return;
@@ -807,6 +812,21 @@ export async function main(args = process.argv.slice(2)) {
807
812
  result = runDoctor(parsed.options);
808
813
  break;
809
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
+
810
830
  default:
811
831
  throw new Error(`Commande inconnue : ${parsed.command}. Utilise --help.`);
812
832
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestgen-cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "NestGen CLI — Générateur modulaire NestJS (DDD, CQRS, Hexagonal)",
5
5
  "files": [
6
6
  "dist",
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.3 / Disponible dans 0.2.3
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.3` 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.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.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
+ 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.3
56
- nestgen --version # 0.2.3
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.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
+ 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.3
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