nestgen-cli 0.0.7 → 0.1.0
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/architecture-profile.js +10 -0
- package/dist/engine/module-generator.js +142 -0
- package/dist/engine/project-config.js +39 -0
- package/dist/engine/resource-generator.js +117 -0
- package/dist/engine/resource-spec.js +44 -0
- package/nestgen.js +318 -83
- package/nestjs-generator/features/add_module.sh +68 -24
- package/nestjs-generator/features/configure_bootstrap.mjs +39 -0
- package/nestjs-generator/features/docker.sh +169 -36
- package/nestjs-generator/features/git.sh +4 -2
- package/nestjs-generator/features/inject_module_to_app.sh +22 -69
- package/nestjs-generator/features/preflight.mjs +55 -0
- package/nestjs-generator/features/prisma.sh +48 -8
- package/nestjs-generator/features/resource_name.mjs +57 -0
- package/nestjs-generator/features/swagger.sh +7 -20
- package/nestjs-generator/features/typeorm.sh +68 -7
- package/nestjs-generator/features/update_app_module.mjs +146 -0
- package/nestjs-generator/features/utils.sh +52 -14
- package/nestjs-generator/generate_project.sh +41 -71
- package/package.json +56 -31
- package/readme.md +90 -133
|
@@ -1,21 +1,32 @@
|
|
|
1
|
-
#!/bin/bash
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
2
4
|
|
|
3
5
|
# ────── Charger les helpers ──────
|
|
4
|
-
FEATURES_PATH="$(dirname "$0")
|
|
6
|
+
FEATURES_PATH="$(dirname "$0")"
|
|
5
7
|
source "$FEATURES_PATH/utils.sh"
|
|
6
8
|
source "$FEATURES_PATH/logger.sh"
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
PM=$1
|
|
10
|
-
APP_NAME=$2
|
|
11
|
+
PM=${1:?Package manager requis}
|
|
12
|
+
APP_NAME=${2:?Nom_application_requis}
|
|
13
|
+
|
|
14
|
+
if [ -e prisma/schema.prisma ]; then
|
|
15
|
+
echo "❌ prisma/schema.prisma existe déjà. Aucune configuration Prisma n'a été remplacée."
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
11
18
|
|
|
12
|
-
$PM
|
|
13
|
-
$PM
|
|
14
|
-
|
|
19
|
+
pm_add_dev "$PM" prisma@6
|
|
20
|
+
pm_add "$PM" @prisma/client@6
|
|
21
|
+
pm_exec "$PM" prisma init
|
|
15
22
|
|
|
23
|
+
if [ ! -e .env ]; then
|
|
16
24
|
cat > .env <<EOF
|
|
17
25
|
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/$APP_NAME"
|
|
18
26
|
EOF
|
|
27
|
+
else
|
|
28
|
+
echo "ℹ️ .env existe déjà et a été conservé."
|
|
29
|
+
fi
|
|
19
30
|
|
|
20
31
|
cat > prisma/schema.prisma <<EOF
|
|
21
32
|
generator client {
|
|
@@ -28,6 +39,35 @@ datasource db {
|
|
|
28
39
|
}
|
|
29
40
|
EOF
|
|
30
41
|
|
|
31
|
-
|
|
42
|
+
mkdir -p src/prisma
|
|
43
|
+
cat > src/prisma/prisma.service.ts <<'EOF'
|
|
44
|
+
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
45
|
+
import { PrismaClient } from '@prisma/client';
|
|
46
|
+
|
|
47
|
+
@Injectable()
|
|
48
|
+
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
|
49
|
+
async onModuleInit(): Promise<void> {
|
|
50
|
+
await this.$connect();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async onModuleDestroy(): Promise<void> {
|
|
54
|
+
await this.$disconnect();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
EOF
|
|
58
|
+
|
|
59
|
+
cat > src/prisma/prisma.module.ts <<'EOF'
|
|
60
|
+
import { Global, Module } from '@nestjs/common';
|
|
61
|
+
import { PrismaService } from './prisma.service';
|
|
62
|
+
|
|
63
|
+
@Global()
|
|
64
|
+
@Module({
|
|
65
|
+
providers: [PrismaService],
|
|
66
|
+
exports: [PrismaService],
|
|
67
|
+
})
|
|
68
|
+
export class PrismaModule {}
|
|
69
|
+
EOF
|
|
70
|
+
|
|
71
|
+
pm_exec "$PM" prisma generate
|
|
32
72
|
|
|
33
73
|
echo "✅ Prisma initialisé avec succès !"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const RESOURCE_NAME_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i;
|
|
2
|
+
const ROUTE_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
3
|
+
const TABLE_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
4
|
+
|
|
5
|
+
function fail(message) {
|
|
6
|
+
throw new Error(`Nom de ressource invalide : ${message}`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function pluralize(word) {
|
|
10
|
+
if (/(?:s|x|z|ch|sh)$/i.test(word)) return `${word}es`;
|
|
11
|
+
if (/[^aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;
|
|
12
|
+
return `${word}s`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function describeResource(rawName, options = {}) {
|
|
16
|
+
const name = String(rawName ?? '').trim();
|
|
17
|
+
if (!RESOURCE_NAME_PATTERN.test(name)) {
|
|
18
|
+
fail('il doit commencer par une lettre et ne contenir que des lettres, chiffres, tirets ou underscores.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const segments = name.toLowerCase().split(/[-_]/);
|
|
22
|
+
const singular = segments.join('-');
|
|
23
|
+
const plural = options.plural
|
|
24
|
+
? String(options.plural).trim().toLowerCase()
|
|
25
|
+
: `${segments.slice(0, -1).join('-')}${segments.length > 1 ? '-' : ''}${pluralize(segments.at(-1))}`;
|
|
26
|
+
const route = options.route ? String(options.route).trim().toLowerCase() : plural;
|
|
27
|
+
const table = options.table ? String(options.table).trim().toLowerCase() : plural.replaceAll('-', '_');
|
|
28
|
+
|
|
29
|
+
if (!ROUTE_PATTERN.test(plural)) fail('le pluriel doit être une ressource kebab-case.');
|
|
30
|
+
if (!ROUTE_PATTERN.test(route)) fail('la route doit être une ressource kebab-case.');
|
|
31
|
+
if (!TABLE_PATTERN.test(table)) fail('la table doit être une ressource snake_case.');
|
|
32
|
+
|
|
33
|
+
const pascal = segments.map((segment) => `${segment[0].toUpperCase()}${segment.slice(1)}`).join('');
|
|
34
|
+
return {
|
|
35
|
+
name: singular,
|
|
36
|
+
pascal,
|
|
37
|
+
camel: `${pascal[0].toLowerCase()}${pascal.slice(1)}`,
|
|
38
|
+
plural,
|
|
39
|
+
route,
|
|
40
|
+
table,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
45
|
+
try {
|
|
46
|
+
const [rawName] = process.argv.slice(2);
|
|
47
|
+
const resource = describeResource(rawName, {
|
|
48
|
+
plural: process.env.RESOURCE_PLURAL,
|
|
49
|
+
route: process.env.RESOURCE_ROUTE,
|
|
50
|
+
table: process.env.RESOURCE_TABLE,
|
|
51
|
+
});
|
|
52
|
+
for (const [key, value] of Object.entries(resource)) console.log(`${key.toUpperCase()}=${value}`);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(`❌ ${error.message}`);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,27 +1,14 @@
|
|
|
1
|
-
#!/bin/bash
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
2
4
|
|
|
3
5
|
# ────── Charger les helpers ──────
|
|
4
|
-
FEATURES_PATH="$(dirname "$0")
|
|
6
|
+
FEATURES_PATH="$(dirname "$0")"
|
|
5
7
|
source "$FEATURES_PATH/utils.sh"
|
|
6
8
|
source "$FEATURES_PATH/logger.sh"
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
PM=$1
|
|
10
|
-
|
|
11
|
-
$PM install @nestjs/swagger swagger-ui-express
|
|
12
|
-
|
|
13
|
-
# Ajout dans main.ts (à faire manuellement ou via automatisation)
|
|
14
|
-
echo ""
|
|
15
|
-
echo "📘 Pour activer Swagger, ajoute ceci dans main.ts :"
|
|
16
|
-
echo ""
|
|
17
|
-
cat <<'DOC'
|
|
18
|
-
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
|
11
|
+
PM=${1:?Package manager requis}
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.setDescription('The API description')
|
|
23
|
-
.setVersion('1.0')
|
|
24
|
-
.build();
|
|
25
|
-
const document = SwaggerModule.createDocument(app, config);
|
|
26
|
-
SwaggerModule.setup('api', app, document);
|
|
27
|
-
DOC
|
|
13
|
+
pm_add "$PM" @nestjs/swagger@12 swagger-ui-express@5
|
|
14
|
+
log_success "Swagger sera activé sur /api dans src/main.ts."
|
|
@@ -1,19 +1,80 @@
|
|
|
1
|
-
#!/bin/bash
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
PM=${1:?Package manager requis}
|
|
6
|
+
APP_NAME=${2:?Nom_application_requis}
|
|
5
7
|
|
|
6
8
|
# ────── Charger les helpers ──────
|
|
7
9
|
FEATURES_PATH="$(dirname "$0")"
|
|
8
10
|
source "$FEATURES_PATH/utils.sh"
|
|
9
11
|
source "$FEATURES_PATH/logger.sh"
|
|
10
12
|
|
|
11
|
-
INSTALL_CMD=$(get_install_cmd "$PM")
|
|
12
|
-
|
|
13
13
|
log_info "📦 Installation de TypeORM et PostgreSQL..."
|
|
14
14
|
|
|
15
|
-
$PM
|
|
15
|
+
pm_add "$PM" @nestjs/typeorm@12 typeorm@0.3 pg@8 dotenv@16
|
|
16
|
+
pm_add_dev "$PM" ts-node@10
|
|
17
|
+
|
|
18
|
+
mkdir -p src/database/migrations
|
|
19
|
+
|
|
20
|
+
cat > src/database/typeorm.config.ts <<'EOF'
|
|
21
|
+
import type { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
|
22
|
+
|
|
23
|
+
function required(name: string): string {
|
|
24
|
+
const value = process.env[name];
|
|
25
|
+
if (!value) throw new Error(`Variable d'environnement manquante : ${name}`);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function typeOrmOptions(): TypeOrmModuleOptions {
|
|
30
|
+
return {
|
|
31
|
+
type: 'postgres',
|
|
32
|
+
host: required('DATABASE_HOST'),
|
|
33
|
+
port: Number(process.env.DATABASE_PORT ?? 5432),
|
|
34
|
+
username: required('DATABASE_USER'),
|
|
35
|
+
password: required('DATABASE_PASSWORD'),
|
|
36
|
+
database: required('DATABASE_NAME'),
|
|
37
|
+
autoLoadEntities: true,
|
|
38
|
+
synchronize: false,
|
|
39
|
+
migrationsRun: false,
|
|
40
|
+
migrations: ['dist/database/migrations/*.js'],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
EOF
|
|
44
|
+
|
|
45
|
+
cat > src/database/data-source.ts <<'EOF'
|
|
46
|
+
import 'dotenv/config';
|
|
47
|
+
import { DataSource, type DataSourceOptions } from 'typeorm';
|
|
48
|
+
import { typeOrmOptions } from './typeorm.config';
|
|
49
|
+
|
|
50
|
+
const options = typeOrmOptions();
|
|
51
|
+
|
|
52
|
+
export default new DataSource({
|
|
53
|
+
...(options as DataSourceOptions),
|
|
54
|
+
entities: ['src/**/*.entity{.ts,.js}'],
|
|
55
|
+
migrations: ['src/database/migrations/*{.ts,.js}'],
|
|
56
|
+
});
|
|
57
|
+
EOF
|
|
58
|
+
|
|
59
|
+
cat > src/database/migrations/0000000000000-InitialSchema.ts <<'EOF'
|
|
60
|
+
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
|
61
|
+
|
|
62
|
+
export class InitialSchema0000000000000 implements MigrationInterface {
|
|
63
|
+
async up(_queryRunner: QueryRunner): Promise<void> {}
|
|
64
|
+
async down(_queryRunner: QueryRunner): Promise<void> {}
|
|
65
|
+
}
|
|
66
|
+
EOF
|
|
67
|
+
|
|
68
|
+
if [ ! -e .env.example ]; then
|
|
69
|
+
cat > .env.example <<'EOF'
|
|
70
|
+
DATABASE_HOST=localhost
|
|
71
|
+
DATABASE_PORT=5432
|
|
72
|
+
DATABASE_USER=postgres
|
|
73
|
+
DATABASE_PASSWORD=change-me
|
|
74
|
+
DATABASE_NAME=appdb
|
|
75
|
+
EOF
|
|
76
|
+
fi
|
|
16
77
|
|
|
17
78
|
log_success "✅ TypeORM installé avec succès"
|
|
18
79
|
|
|
19
|
-
log_info "💡 TypeORM
|
|
80
|
+
log_info "💡 TypeORM utilise src/database/typeorm.config.ts et src/database/data-source.ts pour des migrations explicites."
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
const [appModulePath, moduleClass, modulePath, orm] = process.argv.slice(2);
|
|
4
|
+
|
|
5
|
+
function fail(message) {
|
|
6
|
+
throw new Error(`Impossible de mettre à jour AppModule : ${message}`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function findMatching(source, start, open, close) {
|
|
10
|
+
let depth = 0;
|
|
11
|
+
let quote = null;
|
|
12
|
+
let escaped = false;
|
|
13
|
+
let lineComment = false;
|
|
14
|
+
let blockComment = false;
|
|
15
|
+
|
|
16
|
+
for (let index = start; index < source.length; index += 1) {
|
|
17
|
+
const character = source[index];
|
|
18
|
+
const next = source[index + 1];
|
|
19
|
+
|
|
20
|
+
if (lineComment) {
|
|
21
|
+
if (character === '\n') lineComment = false;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (blockComment) {
|
|
25
|
+
if (character === '*' && next === '/') {
|
|
26
|
+
blockComment = false;
|
|
27
|
+
index += 1;
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (quote) {
|
|
32
|
+
if (!escaped && character === quote) quote = null;
|
|
33
|
+
escaped = !escaped && character === '\\';
|
|
34
|
+
if (character !== '\\') escaped = false;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (character === '/' && next === '/') {
|
|
38
|
+
lineComment = true;
|
|
39
|
+
index += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (character === '/' && next === '*') {
|
|
43
|
+
blockComment = true;
|
|
44
|
+
index += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (character === '"' || character === "'" || character === '`') {
|
|
48
|
+
quote = character;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (character === open) depth += 1;
|
|
52
|
+
if (character === close) {
|
|
53
|
+
depth -= 1;
|
|
54
|
+
if (depth === 0) return index;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fail(`délimiteur ${open}${close} non fermé.`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function findDecoratorObject(source) {
|
|
62
|
+
const decoratorMatch = /@Module\s*\(/.exec(source);
|
|
63
|
+
if (!decoratorMatch) fail('décorateur @Module introuvable.');
|
|
64
|
+
|
|
65
|
+
const objectStart = source.indexOf('{', decoratorMatch.index + decoratorMatch[0].length);
|
|
66
|
+
if (objectStart === -1) fail('objet du décorateur @Module introuvable.');
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
start: objectStart,
|
|
70
|
+
end: findMatching(source, objectStart, '{', '}'),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function findImportsArray(source, object) {
|
|
75
|
+
const propertyPattern = /\bimports\s*:\s*\[/g;
|
|
76
|
+
let match;
|
|
77
|
+
|
|
78
|
+
while ((match = propertyPattern.exec(source)) !== null) {
|
|
79
|
+
if (match.index > object.end) break;
|
|
80
|
+
const before = source.slice(object.start + 1, match.index);
|
|
81
|
+
let depth = 0;
|
|
82
|
+
for (const character of before) {
|
|
83
|
+
if (character === '{') depth += 1;
|
|
84
|
+
if (character === '}') depth -= 1;
|
|
85
|
+
}
|
|
86
|
+
if (depth !== 0) continue;
|
|
87
|
+
|
|
88
|
+
const arrayStart = match.index + match[0].lastIndexOf('[');
|
|
89
|
+
return { start: arrayStart, end: findMatching(source, arrayStart, '[', ']') };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fail('tableau imports dans @Module introuvable ou non supporté.');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function containsToken(items, token) {
|
|
96
|
+
const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
97
|
+
return new RegExp(`\\b${escaped}\\b`).test(items);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function appendImports(source, array, additions) {
|
|
101
|
+
if (additions.length === 0) return source;
|
|
102
|
+
|
|
103
|
+
const current = source.slice(array.start + 1, array.end);
|
|
104
|
+
const separator = current.trim() === '' ? '' : current.trimEnd().endsWith(',') ? '\n' : ',\n';
|
|
105
|
+
const indentation = current.includes('\n') ? (current.match(/\n([ \t]*)[^\n]*$/)?.[1] ?? ' ') : '';
|
|
106
|
+
const inserted = additions.map((item) => `${indentation}${item}`).join(',\n');
|
|
107
|
+
const replacement =
|
|
108
|
+
current.trim() === ''
|
|
109
|
+
? current.includes('\n')
|
|
110
|
+
? `\n${inserted}\n `
|
|
111
|
+
: inserted
|
|
112
|
+
: `${current}${separator}${inserted}`;
|
|
113
|
+
|
|
114
|
+
return `${source.slice(0, array.start + 1)}${replacement}${source.slice(array.end)}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function addImport(source, line) {
|
|
118
|
+
return source.includes(line) ? source : `${line}\n${source}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
let source = fs.readFileSync(appModulePath, 'utf8');
|
|
123
|
+
const object = findDecoratorObject(source);
|
|
124
|
+
const array = findImportsArray(source, object);
|
|
125
|
+
const currentImports = source.slice(array.start + 1, array.end);
|
|
126
|
+
const additions = [];
|
|
127
|
+
|
|
128
|
+
if (!containsToken(currentImports, 'CqrsModule')) additions.push('CqrsModule');
|
|
129
|
+
if (!containsToken(currentImports, moduleClass)) additions.push(moduleClass);
|
|
130
|
+
if (orm === 'typeorm' && !/\bTypeOrmModule\.forRoot(?:Async)?\s*\(/.test(currentImports)) {
|
|
131
|
+
additions.unshift('TypeOrmModule.forRootAsync({ useFactory: typeOrmOptions })');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
source = appendImports(source, array, additions);
|
|
135
|
+
source = addImport(source, `import { ${moduleClass} } from '${modulePath}';`);
|
|
136
|
+
source = addImport(source, "import { CqrsModule } from '@nestjs/cqrs';");
|
|
137
|
+
if (orm === 'typeorm') {
|
|
138
|
+
source = addImport(source, "import { typeOrmOptions } from './database/typeorm.config';");
|
|
139
|
+
source = addImport(source, "import { TypeOrmModule } from '@nestjs/typeorm';");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fs.writeFileSync(appModulePath, source);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error(`❌ ${error.message}`);
|
|
145
|
+
process.exitCode = 1;
|
|
146
|
+
}
|
|
@@ -1,12 +1,55 @@
|
|
|
1
|
-
#!/bin/bash
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
pnpm|
|
|
9
|
-
*)
|
|
3
|
+
NEST_CLI_VERSION="12.0.0"
|
|
4
|
+
|
|
5
|
+
assert_package_manager() {
|
|
6
|
+
local package_manager=$1
|
|
7
|
+
case "$package_manager" in
|
|
8
|
+
npm|pnpm|yarn) assert_command_exists "$package_manager" ;;
|
|
9
|
+
*)
|
|
10
|
+
echo "❌ Package manager non supporté : $package_manager. Valeurs acceptées : npm, pnpm, yarn."
|
|
11
|
+
return 1
|
|
12
|
+
;;
|
|
13
|
+
esac
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pm_add() {
|
|
17
|
+
local package_manager=$1
|
|
18
|
+
shift
|
|
19
|
+
case "$package_manager" in
|
|
20
|
+
npm) npm install "$@" ;;
|
|
21
|
+
pnpm) pnpm add "$@" ;;
|
|
22
|
+
yarn) yarn add "$@" ;;
|
|
23
|
+
esac
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pm_add_dev() {
|
|
27
|
+
local package_manager=$1
|
|
28
|
+
shift
|
|
29
|
+
case "$package_manager" in
|
|
30
|
+
npm) npm install --save-dev "$@" ;;
|
|
31
|
+
pnpm) pnpm add --save-dev "$@" ;;
|
|
32
|
+
yarn) yarn add --dev "$@" ;;
|
|
33
|
+
esac
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pm_exec() {
|
|
37
|
+
local package_manager=$1
|
|
38
|
+
shift
|
|
39
|
+
case "$package_manager" in
|
|
40
|
+
npm) npm exec -- "$@" ;;
|
|
41
|
+
pnpm) pnpm exec "$@" ;;
|
|
42
|
+
yarn) yarn exec "$@" ;;
|
|
43
|
+
esac
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
nest_new() {
|
|
47
|
+
local package_manager=$1
|
|
48
|
+
local destination=$2
|
|
49
|
+
case "$package_manager" in
|
|
50
|
+
npm) npx --yes "@nestjs/cli@${NEST_CLI_VERSION}" new "$destination" --package-manager npm --skip-git ;;
|
|
51
|
+
pnpm) pnpm dlx "@nestjs/cli@${NEST_CLI_VERSION}" new "$destination" --package-manager pnpm --skip-git ;;
|
|
52
|
+
yarn) yarn dlx "@nestjs/cli@${NEST_CLI_VERSION}" new "$destination" --package-manager yarn --skip-git ;;
|
|
10
53
|
esac
|
|
11
54
|
}
|
|
12
55
|
|
|
@@ -16,11 +59,6 @@ assert_command_exists() {
|
|
|
16
59
|
if ! command -v "$CMD" &> /dev/null; then
|
|
17
60
|
echo "❌ Commande introuvable : $CMD"
|
|
18
61
|
echo "👉 Installe-la ou vérifie ton PATH"
|
|
19
|
-
|
|
62
|
+
return 1
|
|
20
63
|
fi
|
|
21
64
|
}
|
|
22
|
-
|
|
23
|
-
# ────── Formatage ──────
|
|
24
|
-
to_pascal_case() {
|
|
25
|
-
echo "$1" | sed -E 's/(^|-|_)([a-z])/\U\2/g'
|
|
26
|
-
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
#!/bin/bash
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
3
|
# ────── INIT ──────
|
|
4
|
-
set -
|
|
4
|
+
set -euo pipefail
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
FEATURES_PATH="$SCRIPT_DIR/features"
|
|
7
8
|
source "$FEATURES_PATH/utils.sh"
|
|
8
9
|
source "$FEATURES_PATH/logger.sh"
|
|
9
10
|
|
|
@@ -19,6 +20,20 @@ debug_log() {
|
|
|
19
20
|
fi
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
run_step() {
|
|
24
|
+
local step=$1
|
|
25
|
+
shift
|
|
26
|
+
|
|
27
|
+
log_info "$step"
|
|
28
|
+
if "$@"; then
|
|
29
|
+
return 0
|
|
30
|
+
else
|
|
31
|
+
local status=$?
|
|
32
|
+
log_error "$step a échoué (code $status)."
|
|
33
|
+
exit "$status"
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
debug_log "Script lancé avec les arguments : $*"
|
|
23
38
|
debug_log "Chemin du script courant : $(pwd)"
|
|
24
39
|
debug_log "Features path : $FEATURES_PATH"
|
|
@@ -30,14 +45,8 @@ FULL_PATH="$PROJECT_PATH/$APP_NAME"
|
|
|
30
45
|
PM=${PM:-$(read -p "📦 Package manager (npm/yarn/pnpm) : " tmp && echo "$tmp")}
|
|
31
46
|
ORM=${ORM:-$(read -p "🧠 ORM ? (typeorm/prisma) : " tmp && echo "$tmp")}
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
debug_log "
|
|
35
|
-
|
|
36
|
-
# ────── Vérification de Nest CLI ──────
|
|
37
|
-
if ! command -v nest &> /dev/null; then
|
|
38
|
-
log_warn "Nest CLI non installée. Installation avec npm..."
|
|
39
|
-
npm install -g @nestjs/cli
|
|
40
|
-
fi
|
|
48
|
+
run_step "Vérification du package manager" assert_package_manager "$PM"
|
|
49
|
+
debug_log "Package manager : $PM ; Nest CLI local : $NEST_CLI_VERSION"
|
|
41
50
|
|
|
42
51
|
# ────── Création du projet ──────
|
|
43
52
|
log_info "Création du projet à $FULL_PATH"
|
|
@@ -47,23 +56,19 @@ cd "$FULL_PATH" || {
|
|
|
47
56
|
exit 1
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
log_error "Échec de nest new"
|
|
52
|
-
exit 1
|
|
53
|
-
}
|
|
59
|
+
run_step "Création du projet NestJS" nest_new "$PM" .
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
$PM $INSTALL_CMD @nestjs/cqrs class-validator class-transformer @nestjs/config
|
|
61
|
+
run_step "Installation des packages communs" pm_add "$PM" @nestjs/cqrs@12 @nestjs/config@4 class-validator@0.14 class-transformer@0.5
|
|
57
62
|
|
|
58
63
|
# ────── ORM SETUP ──────
|
|
59
64
|
case "$ORM" in
|
|
60
65
|
typeorm)
|
|
61
66
|
debug_log "Appel de typeorm.sh"
|
|
62
|
-
bash "$FEATURES_PATH/typeorm.sh" "$PM" "$APP_NAME"
|
|
67
|
+
run_step "Configuration de TypeORM" bash "$FEATURES_PATH/typeorm.sh" "$PM" "$APP_NAME"
|
|
63
68
|
;;
|
|
64
69
|
prisma)
|
|
65
70
|
debug_log "Appel de prisma.sh"
|
|
66
|
-
bash "$FEATURES_PATH/prisma.sh" "$PM" "$APP_NAME"
|
|
71
|
+
run_step "Configuration de Prisma" bash "$FEATURES_PATH/prisma.sh" "$PM" "$APP_NAME"
|
|
67
72
|
;;
|
|
68
73
|
*)
|
|
69
74
|
log_error "❌ ORM non reconnu : $ORM"
|
|
@@ -73,62 +78,27 @@ esac
|
|
|
73
78
|
|
|
74
79
|
# ────── Docker, Swagger, Git ──────
|
|
75
80
|
WITH_DOCKER=${WITH_DOCKER:-$(read -p "🐳 Activer Docker ? (y/n) : " tmp && echo "$tmp")}
|
|
76
|
-
[ "$WITH_DOCKER" = "y" ]
|
|
81
|
+
if [ "$WITH_DOCKER" = "y" ]; then
|
|
82
|
+
run_step "Configuration Docker" bash "$FEATURES_PATH/docker.sh" "$APP_NAME" "$PM"
|
|
83
|
+
fi
|
|
77
84
|
|
|
78
85
|
WITH_SWAGGER=${WITH_SWAGGER:-$(read -p "📚 Activer Swagger ? (y/n) : " tmp && echo "$tmp")}
|
|
79
|
-
[ "$WITH_SWAGGER" = "y" ]
|
|
80
|
-
|
|
81
|
-
WITH_GIT=${WITH_GIT:-$(read -p "🔃 Initialiser Git ? (y/n) : " tmp && echo "$tmp")}
|
|
82
|
-
[ "$WITH_GIT" = "y" ] && bash "$FEATURES_PATH/git.sh"
|
|
83
|
-
|
|
84
|
-
# ────── Modules à générer ──────
|
|
85
|
-
MODULES=${MODULES:-$(read -p "👤 Modules à générer (séparés par espaces) : " tmp && echo "$tmp")}
|
|
86
|
-
for MODULE in $MODULES; do
|
|
87
|
-
debug_log "Génération du module $MODULE"
|
|
88
|
-
bash "$FEATURES_PATH/add_module.sh" "$MODULE" "$ORM"
|
|
89
|
-
done
|
|
90
|
-
|
|
91
|
-
# ────── Injection dans app.module.ts ──────
|
|
92
|
-
APP_MODULE="src/app.module.ts"
|
|
93
|
-
|
|
94
|
-
# CqrsModule
|
|
95
|
-
if ! grep -q "CqrsModule" "$APP_MODULE"; then
|
|
96
|
-
sed -i '' "1i\\
|
|
97
|
-
import { CqrsModule } from '@nestjs/cqrs';
|
|
98
|
-
" "$APP_MODULE"
|
|
99
|
-
sed -i '' "s|imports: \[|imports: [CqrsModule, |" "$APP_MODULE"
|
|
100
|
-
echo "✅ CqrsModule injecté"
|
|
86
|
+
if [ "$WITH_SWAGGER" = "y" ]; then
|
|
87
|
+
run_step "Configuration Swagger" bash "$FEATURES_PATH/swagger.sh" "$PM"
|
|
101
88
|
fi
|
|
102
89
|
|
|
103
|
-
|
|
104
|
-
if [ "$ORM" = "typeorm" ]; then
|
|
105
|
-
if ! grep -q "TypeOrmModule" "$APP_MODULE"; then
|
|
106
|
-
sed -i '' "1i\\
|
|
107
|
-
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
108
|
-
" "$APP_MODULE"
|
|
109
|
-
echo "✅ Import de TypeOrmModule ajouté"
|
|
110
|
-
fi
|
|
90
|
+
run_step "Configuration du bootstrap Nest" node "$FEATURES_PATH/configure_bootstrap.mjs" src/main.ts "$WITH_SWAGGER"
|
|
111
91
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
type: '\''postgres'\'',\
|
|
116
|
-
host: '\''localhost'\'',\
|
|
117
|
-
port: 5432,\
|
|
118
|
-
username: '\''postgres'\'',\
|
|
119
|
-
password: '\''postgres'\'',\
|
|
120
|
-
database: '\''appdb'\'',\
|
|
121
|
-
synchronize: true,\
|
|
122
|
-
autoLoadEntities: true,\
|
|
123
|
-
}), \2/g' "$APP_MODULE"
|
|
124
|
-
echo "✅ TypeOrmModule.forRoot injecté"
|
|
125
|
-
fi
|
|
92
|
+
WITH_GIT=${WITH_GIT:-$(read -p "🔃 Initialiser Git ? (y/n) : " tmp && echo "$tmp")}
|
|
93
|
+
if [ "$WITH_GIT" = "y" ]; then
|
|
94
|
+
run_step "Initialisation Git" bash "$FEATURES_PATH/git.sh"
|
|
126
95
|
fi
|
|
127
96
|
|
|
128
|
-
# Modules
|
|
97
|
+
# ────── Modules à générer ──────
|
|
98
|
+
MODULES=${MODULES-$(read -p "👤 Modules à générer (séparés par espaces) : " tmp && echo "$tmp")}
|
|
129
99
|
for MODULE in $MODULES; do
|
|
130
|
-
debug_log "
|
|
131
|
-
bash "$FEATURES_PATH/
|
|
100
|
+
debug_log "Génération du module $MODULE"
|
|
101
|
+
run_step "Génération du module $MODULE" bash "$FEATURES_PATH/add_module.sh" "$MODULE" "$ORM"
|
|
132
102
|
done
|
|
133
103
|
|
|
134
104
|
# ────── Résumé final ──────
|
|
@@ -137,7 +107,7 @@ log_success "✅ Projet NestJS \"$APP_NAME\" généré avec succès 🎉"
|
|
|
137
107
|
echo "📁 Localisation : $FULL_PATH"
|
|
138
108
|
echo "📦 Package manager : $PM"
|
|
139
109
|
echo "🧠 ORM : $ORM"
|
|
140
|
-
[ "$WITH_DOCKER" = "y" ]
|
|
141
|
-
[ "$WITH_SWAGGER" = "y" ]
|
|
142
|
-
[ "$WITH_GIT" = "y" ]
|
|
143
|
-
[ -n "$MODULES" ]
|
|
110
|
+
if [ "$WITH_DOCKER" = "y" ]; then echo "🐳 Docker activé"; fi
|
|
111
|
+
if [ "$WITH_SWAGGER" = "y" ]; then echo "📚 Swagger activé"; fi
|
|
112
|
+
if [ "$WITH_GIT" = "y" ]; then echo "🔃 Git initialisé"; fi
|
|
113
|
+
if [ -n "$MODULES" ]; then echo "📦 Modules générés : $MODULES"; fi
|