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,7 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
RAW_NAME=${1:-}
|
|
6
|
+
ORM=${2:-}
|
|
5
7
|
|
|
6
8
|
# ────── Charger les helpers ──────
|
|
7
9
|
FEATURES_PATH="$(dirname "$0")"
|
|
@@ -9,33 +11,68 @@ source "$FEATURES_PATH/utils.sh"
|
|
|
9
11
|
source "$FEATURES_PATH/logger.sh"
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
if [
|
|
13
|
-
|
|
14
|
+
if [[ ! "$RAW_NAME" =~ ^[A-Za-z][A-Za-z0-9_-]*$ ]]; then
|
|
15
|
+
log_error "Le nom du module doit commencer par une lettre et ne contenir que des lettres, chiffres, tirets ou underscores."
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if [[ "$ORM" != "typeorm" && "$ORM" != "prisma" ]]; then
|
|
20
|
+
log_error "ORM non supporté : ${ORM:-vide}. Valeurs acceptées : typeorm, prisma."
|
|
14
21
|
exit 1
|
|
15
22
|
fi
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
# ────── Formatage portable ──────
|
|
25
|
+
if ! RESOURCE_DATA=$(node "$FEATURES_PATH/resource_name.mjs" "$RAW_NAME"); then
|
|
26
|
+
log_error "Impossible de normaliser le nom de la ressource."
|
|
19
27
|
exit 1
|
|
20
28
|
fi
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
while IFS='=' read -r key value; do
|
|
31
|
+
case "$key" in
|
|
32
|
+
NAME) NAME=$value ;;
|
|
33
|
+
PASCAL) PASCAL=$value ;;
|
|
34
|
+
CAMEL) CAMEL=$value ;;
|
|
35
|
+
PLURAL) PLURAL=$value ;;
|
|
36
|
+
ROUTE) ROUTE=$value ;;
|
|
37
|
+
TABLE) TABLE=$value ;;
|
|
38
|
+
esac
|
|
39
|
+
done <<< "$RESOURCE_DATA"
|
|
40
|
+
|
|
41
|
+
if [[ -z "${NAME:-}" || -z "${PASCAL:-}" || -z "${CAMEL:-}" || -z "${ROUTE:-}" || -z "${TABLE:-}" ]]; then
|
|
42
|
+
log_error "Impossible de normaliser le nom de la ressource."
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
25
45
|
|
|
26
|
-
|
|
27
|
-
PASCAL=$(echo "$NAME" | sed -E 's/(^|[-_])([a-z])/\U\2/g' | sed -E 's/[-_]//g')
|
|
46
|
+
REPOSITORY_TOKEN="${PASCAL}RepositoryToken"
|
|
28
47
|
|
|
29
|
-
|
|
48
|
+
PROJECT_ROOT="$(pwd -P)"
|
|
49
|
+
SOURCE_ROOT="$PROJECT_ROOT/src/app"
|
|
50
|
+
|
|
51
|
+
if ! node "$FEATURES_PATH/preflight.mjs" "$PROJECT_ROOT" "$ORM" >/dev/null; then
|
|
52
|
+
log_error "Le projet cible ne satisfait pas les préconditions de génération."
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if [ -L "$PROJECT_ROOT/src" ] || [ -L "$SOURCE_ROOT" ]; then
|
|
57
|
+
log_error "Les liens symboliques ne sont pas pris en charge dans src/app."
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
mkdir -p "$SOURCE_ROOT"
|
|
62
|
+
MODULE_DIR="$SOURCE_ROOT/$NAME"
|
|
63
|
+
|
|
64
|
+
if [ -e "$MODULE_DIR" ] || [ -L "$MODULE_DIR" ]; then
|
|
65
|
+
log_error "Le module $NAME existe déjà. Aucune modification n'a été appliquée."
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
30
68
|
|
|
31
|
-
MODULE_DIR="src/app/$NAME"
|
|
32
69
|
mkdir -p "$MODULE_DIR"/{core/{application/{commands,events,queries},domain/{entities,ports}},infrastructure/{adapters,persistences/repositories},interfaces/{controllers,dtos}}
|
|
33
70
|
|
|
34
71
|
# ────── Entity ──────
|
|
35
72
|
cat > "$MODULE_DIR/core/domain/entities/${NAME}.entity.ts" <<EOF
|
|
36
73
|
export class $PASCAL {
|
|
37
74
|
constructor(
|
|
38
|
-
public readonly id: string,
|
|
75
|
+
public readonly id: string | undefined,
|
|
39
76
|
public name: string,
|
|
40
77
|
public email: string,
|
|
41
78
|
) {}
|
|
@@ -46,6 +83,8 @@ EOF
|
|
|
46
83
|
cat > "$MODULE_DIR/core/domain/ports/${NAME}.repository.ts" <<EOF
|
|
47
84
|
import { $PASCAL } from '../entities/${NAME}.entity';
|
|
48
85
|
|
|
86
|
+
export const ${REPOSITORY_TOKEN} = Symbol('${PASCAL}RepositoryPort');
|
|
87
|
+
|
|
49
88
|
export interface ${PASCAL}RepositoryPort {
|
|
50
89
|
save(${CAMEL}: $PASCAL): Promise<$PASCAL>;
|
|
51
90
|
findById(id: string): Promise<$PASCAL | null>;
|
|
@@ -61,17 +100,20 @@ EOF
|
|
|
61
100
|
|
|
62
101
|
# ────── Command Handler ──────
|
|
63
102
|
cat > "$MODULE_DIR/core/application/commands/create-${NAME}.handler.ts" <<EOF
|
|
103
|
+
import { Inject } from '@nestjs/common';
|
|
64
104
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
|
65
105
|
import { Create${PASCAL}Command } from './create-${NAME}.command';
|
|
66
106
|
import { $PASCAL } from '../../domain/entities/${NAME}.entity';
|
|
67
|
-
import { ${PASCAL}RepositoryPort } from '../../domain/ports/${NAME}.repository';
|
|
107
|
+
import { ${PASCAL}RepositoryPort, ${REPOSITORY_TOKEN} } from '../../domain/ports/${NAME}.repository';
|
|
68
108
|
|
|
69
109
|
@CommandHandler(Create${PASCAL}Command)
|
|
70
110
|
export class Create${PASCAL}Handler implements ICommandHandler<Create${PASCAL}Command> {
|
|
71
|
-
constructor(
|
|
111
|
+
constructor(
|
|
112
|
+
@Inject(${REPOSITORY_TOKEN}) private readonly repo: ${PASCAL}RepositoryPort,
|
|
113
|
+
) {}
|
|
72
114
|
|
|
73
115
|
async execute(command: Create${PASCAL}Command): Promise<string> {
|
|
74
|
-
const $CAMEL = new $PASCAL(
|
|
116
|
+
const $CAMEL = new $PASCAL(undefined, command.name, command.email);
|
|
75
117
|
const saved = await this.repo.save($CAMEL);
|
|
76
118
|
return saved.id;
|
|
77
119
|
}
|
|
@@ -98,7 +140,7 @@ import { CommandBus } from '@nestjs/cqrs';
|
|
|
98
140
|
import { Create${PASCAL}Command } from '../../core/application/commands/create-${NAME}.command';
|
|
99
141
|
import { Create${PASCAL}Dto } from '../dtos/create-${NAME}.dto';
|
|
100
142
|
|
|
101
|
-
@Controller('${
|
|
143
|
+
@Controller('${ROUTE}')
|
|
102
144
|
export class ${PASCAL}Controller {
|
|
103
145
|
constructor(private readonly commandBus: CommandBus) {}
|
|
104
146
|
|
|
@@ -121,7 +163,7 @@ if [ "$ORM" == "typeorm" ]; then
|
|
|
121
163
|
cat > "$MODULE_DIR/infrastructure/persistences/repositories/${NAME}.orm.ts" <<EOF
|
|
122
164
|
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
|
123
165
|
|
|
124
|
-
@Entity('${
|
|
166
|
+
@Entity('${TABLE}')
|
|
125
167
|
export class ${PASCAL}Entity {
|
|
126
168
|
@PrimaryGeneratedColumn('uuid')
|
|
127
169
|
id: string;
|
|
@@ -147,7 +189,10 @@ export class ${PASCAL}TypeOrmRepository implements ${PASCAL}RepositoryPort {
|
|
|
147
189
|
constructor(@InjectRepository(${PASCAL}Entity) private readonly repo: Repository<${PASCAL}Entity>) {}
|
|
148
190
|
|
|
149
191
|
async save(${CAMEL}: $PASCAL): Promise<$PASCAL> {
|
|
150
|
-
const entity = this.repo.create(
|
|
192
|
+
const entity = this.repo.create({
|
|
193
|
+
name: ${CAMEL}.name,
|
|
194
|
+
email: ${CAMEL}.email,
|
|
195
|
+
});
|
|
151
196
|
const saved = await this.repo.save(entity);
|
|
152
197
|
return new $PASCAL(saved.id, saved.name, saved.email);
|
|
153
198
|
}
|
|
@@ -162,7 +207,7 @@ EOF
|
|
|
162
207
|
REPO_CLASS="${PASCAL}TypeOrmRepository"
|
|
163
208
|
ENTITY_IMPORT="TypeOrmModule.forFeature([${PASCAL}Entity])"
|
|
164
209
|
REPO_PROVIDER="{
|
|
165
|
-
provide:
|
|
210
|
+
provide: ${REPOSITORY_TOKEN},
|
|
166
211
|
useClass: ${PASCAL}TypeOrmRepository,
|
|
167
212
|
}"
|
|
168
213
|
|
|
@@ -198,7 +243,7 @@ EOF
|
|
|
198
243
|
REPO_CLASS="${PASCAL}PrismaRepository"
|
|
199
244
|
ENTITY_IMPORT=""
|
|
200
245
|
REPO_PROVIDER="{
|
|
201
|
-
provide:
|
|
246
|
+
provide: ${REPOSITORY_TOKEN},
|
|
202
247
|
useClass: ${PASCAL}PrismaRepository,
|
|
203
248
|
}"
|
|
204
249
|
fi
|
|
@@ -208,6 +253,7 @@ IMPORTS="import { Module } from '@nestjs/common';
|
|
|
208
253
|
import { CqrsModule } from '@nestjs/cqrs';
|
|
209
254
|
import { ${PASCAL}Controller } from './interfaces/controllers/${NAME}.controller';
|
|
210
255
|
import { Create${PASCAL}Handler } from './core/application/commands/create-${NAME}.handler';
|
|
256
|
+
import { ${REPOSITORY_TOKEN} } from './core/domain/ports/${NAME}.repository';
|
|
211
257
|
import { $REPO_CLASS } from './infrastructure/persistences/repositories/${NAME}.${ORM}.repository';"
|
|
212
258
|
|
|
213
259
|
if [ "$ORM" = "typeorm" ]; then
|
|
@@ -223,7 +269,6 @@ echo "$IMPORTS
|
|
|
223
269
|
controllers: [${PASCAL}Controller],
|
|
224
270
|
providers: [
|
|
225
271
|
Create${PASCAL}Handler,
|
|
226
|
-
$REPO_CLASS,
|
|
227
272
|
$REPO_PROVIDER,
|
|
228
273
|
],
|
|
229
274
|
})
|
|
@@ -231,4 +276,3 @@ export class ${PASCAL}Module {}
|
|
|
231
276
|
" > "$MODULE_DIR/${NAME}.module.ts"
|
|
232
277
|
|
|
233
278
|
bash "$(dirname "$0")/inject_module_to_app.sh" "$NAME" "$ORM"
|
|
234
|
-
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
const [mainPath, swaggerEnabled] = process.argv.slice(2);
|
|
4
|
+
|
|
5
|
+
if (!mainPath || !fs.existsSync(mainPath)) {
|
|
6
|
+
console.error(`❌ Bootstrap Nest introuvable : ${mainPath ?? 'src/main.ts'}`);
|
|
7
|
+
process.exitCode = 1;
|
|
8
|
+
} else {
|
|
9
|
+
const swagger = swaggerEnabled === 'y';
|
|
10
|
+
const source = `import { ValidationPipe } from '@nestjs/common';
|
|
11
|
+
import { NestFactory } from '@nestjs/core';
|
|
12
|
+
${swagger ? "import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';\n" : ''}import { AppModule } from './app.module';
|
|
13
|
+
|
|
14
|
+
async function bootstrap() {
|
|
15
|
+
const app = await NestFactory.create(AppModule);
|
|
16
|
+
app.useGlobalPipes(new ValidationPipe({
|
|
17
|
+
whitelist: true,
|
|
18
|
+
forbidNonWhitelisted: true,
|
|
19
|
+
transform: true,
|
|
20
|
+
}));
|
|
21
|
+
${
|
|
22
|
+
swagger
|
|
23
|
+
? `
|
|
24
|
+
const config = new DocumentBuilder()
|
|
25
|
+
.setTitle('API')
|
|
26
|
+
.setDescription('Documentation de l’API')
|
|
27
|
+
.setVersion('1.0')
|
|
28
|
+
.build();
|
|
29
|
+
SwaggerModule.setup('api', app, SwaggerModule.createDocument(app, config));
|
|
30
|
+
`
|
|
31
|
+
: ''
|
|
32
|
+
}
|
|
33
|
+
await app.listen(process.env.PORT ?? 3000);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void bootstrap();
|
|
37
|
+
`;
|
|
38
|
+
fs.writeFileSync(mainPath, source);
|
|
39
|
+
}
|
|
@@ -1,59 +1,192 @@
|
|
|
1
|
-
#!/bin/bash
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
+
set -euo pipefail
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
source "$FEATURES_PATH/utils.sh"
|
|
7
|
-
source "$FEATURES_PATH/logger.sh"
|
|
5
|
+
APP_NAME=${1:?Nom_application_requis}
|
|
6
|
+
PM=${2:-npm}
|
|
8
7
|
|
|
8
|
+
case "$PM" in
|
|
9
|
+
npm|pnpm|yarn) ;;
|
|
10
|
+
*)
|
|
11
|
+
echo "❌ Package manager non supporté pour Docker : $PM."
|
|
12
|
+
exit 1
|
|
13
|
+
;;
|
|
14
|
+
esac
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
for file in Dockerfile compose.yaml .dockerignore; do
|
|
17
|
+
if [ -e "$file" ]; then
|
|
18
|
+
echo "❌ $file existe déjà. Aucune configuration Docker n'a été remplacée."
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
done
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
case "$PM" in
|
|
24
|
+
npm)
|
|
25
|
+
cat > Dockerfile <<'DOCKERFILE'
|
|
26
|
+
FROM node:24-alpine AS base
|
|
27
|
+
WORKDIR /app
|
|
28
|
+
|
|
29
|
+
FROM base AS dependencies
|
|
30
|
+
COPY package.json package-lock.json ./
|
|
31
|
+
RUN npm ci
|
|
32
|
+
|
|
33
|
+
FROM dependencies AS development
|
|
34
|
+
COPY . .
|
|
35
|
+
CMD ["npm", "run", "start:dev"]
|
|
36
|
+
|
|
37
|
+
FROM dependencies AS build
|
|
17
38
|
COPY . .
|
|
18
39
|
RUN npm run build
|
|
19
|
-
CMD ["node", "dist/main"]
|
|
20
|
-
EOF
|
|
21
40
|
|
|
22
|
-
|
|
23
|
-
|
|
41
|
+
FROM base AS production-dependencies
|
|
42
|
+
COPY package.json package-lock.json ./
|
|
43
|
+
RUN npm ci --omit=dev && npm cache clean --force && mkdir -p node_modules
|
|
44
|
+
|
|
45
|
+
FROM node:24-alpine AS production
|
|
46
|
+
ENV NODE_ENV=production
|
|
47
|
+
WORKDIR /app
|
|
48
|
+
COPY --chown=node:node --from=production-dependencies /app/node_modules ./node_modules
|
|
49
|
+
COPY --chown=node:node --from=build /app/dist ./dist
|
|
50
|
+
USER node
|
|
51
|
+
EXPOSE 3000
|
|
52
|
+
CMD ["node", "dist/main.js"]
|
|
53
|
+
DOCKERFILE
|
|
54
|
+
;;
|
|
55
|
+
pnpm)
|
|
56
|
+
cat > Dockerfile <<'DOCKERFILE'
|
|
57
|
+
FROM node:24-alpine AS base
|
|
58
|
+
WORKDIR /app
|
|
59
|
+
RUN corepack enable
|
|
60
|
+
|
|
61
|
+
FROM base AS dependencies
|
|
62
|
+
COPY package.json pnpm-lock.yaml ./
|
|
63
|
+
RUN pnpm install --frozen-lockfile
|
|
64
|
+
|
|
65
|
+
FROM dependencies AS development
|
|
66
|
+
COPY . .
|
|
67
|
+
CMD ["pnpm", "run", "start:dev"]
|
|
68
|
+
|
|
69
|
+
FROM dependencies AS build
|
|
70
|
+
COPY . .
|
|
71
|
+
RUN pnpm run build
|
|
72
|
+
|
|
73
|
+
FROM base AS production-dependencies
|
|
74
|
+
COPY package.json pnpm-lock.yaml ./
|
|
75
|
+
RUN pnpm install --prod --frozen-lockfile && mkdir -p node_modules
|
|
76
|
+
|
|
77
|
+
FROM node:24-alpine AS production
|
|
78
|
+
ENV NODE_ENV=production
|
|
79
|
+
WORKDIR /app
|
|
80
|
+
COPY --chown=node:node --from=production-dependencies /app/node_modules ./node_modules
|
|
81
|
+
COPY --chown=node:node --from=build /app/dist ./dist
|
|
82
|
+
USER node
|
|
83
|
+
EXPOSE 3000
|
|
84
|
+
CMD ["node", "dist/main.js"]
|
|
85
|
+
DOCKERFILE
|
|
86
|
+
;;
|
|
87
|
+
yarn)
|
|
88
|
+
cat > Dockerfile <<'DOCKERFILE'
|
|
89
|
+
FROM node:24-alpine AS base
|
|
90
|
+
WORKDIR /app
|
|
91
|
+
RUN corepack enable
|
|
24
92
|
|
|
93
|
+
FROM base AS dependencies
|
|
94
|
+
COPY package.json yarn.lock ./
|
|
95
|
+
RUN yarn install --frozen-lockfile
|
|
96
|
+
|
|
97
|
+
FROM dependencies AS development
|
|
98
|
+
COPY . .
|
|
99
|
+
CMD ["yarn", "start:dev"]
|
|
100
|
+
|
|
101
|
+
FROM dependencies AS build
|
|
102
|
+
COPY . .
|
|
103
|
+
RUN yarn build
|
|
104
|
+
|
|
105
|
+
FROM base AS production-dependencies
|
|
106
|
+
COPY package.json yarn.lock ./
|
|
107
|
+
RUN yarn install --production=true --frozen-lockfile && mkdir -p node_modules
|
|
108
|
+
|
|
109
|
+
FROM node:24-alpine AS production
|
|
110
|
+
ENV NODE_ENV=production
|
|
111
|
+
WORKDIR /app
|
|
112
|
+
COPY --chown=node:node --from=production-dependencies /app/node_modules ./node_modules
|
|
113
|
+
COPY --chown=node:node --from=build /app/dist ./dist
|
|
114
|
+
USER node
|
|
115
|
+
EXPOSE 3000
|
|
116
|
+
CMD ["node", "dist/main.js"]
|
|
117
|
+
DOCKERFILE
|
|
118
|
+
;;
|
|
119
|
+
esac
|
|
120
|
+
|
|
121
|
+
cat > compose.yaml <<COMPOSE
|
|
25
122
|
services:
|
|
26
123
|
postgres:
|
|
27
|
-
image: postgres:
|
|
28
|
-
restart: always
|
|
124
|
+
image: postgres:16-alpine
|
|
29
125
|
environment:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
126
|
+
POSTGRES_DB: \${POSTGRES_DB:-appdb}
|
|
127
|
+
POSTGRES_USER: \${POSTGRES_USER:-nestgen}
|
|
128
|
+
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-nestgen-local-only}
|
|
33
129
|
ports:
|
|
34
|
-
- "5432:5432"
|
|
130
|
+
- "\${POSTGRES_PORT:-5432}:5432"
|
|
35
131
|
volumes:
|
|
36
|
-
-
|
|
132
|
+
- postgres-data:/var/lib/postgresql/data
|
|
133
|
+
healthcheck:
|
|
134
|
+
test: ["CMD-SHELL", "pg_isready -U \$\$POSTGRES_USER -d \$\$POSTGRES_DB"]
|
|
135
|
+
interval: 5s
|
|
136
|
+
timeout: 3s
|
|
137
|
+
retries: 20
|
|
138
|
+
start_period: 5s
|
|
37
139
|
|
|
38
140
|
app:
|
|
39
|
-
build:
|
|
141
|
+
build:
|
|
142
|
+
context: .
|
|
143
|
+
target: development
|
|
144
|
+
command: \${NESTGEN_DEV_COMMAND:-$PM run start:dev}
|
|
145
|
+
environment:
|
|
146
|
+
DATABASE_HOST: postgres
|
|
147
|
+
DATABASE_PORT: 5432
|
|
148
|
+
DATABASE_NAME: \${POSTGRES_DB:-appdb}
|
|
149
|
+
DATABASE_USER: \${POSTGRES_USER:-nestgen}
|
|
150
|
+
DATABASE_PASSWORD: \${POSTGRES_PASSWORD:-nestgen-local-only}
|
|
151
|
+
PORT: \${PORT:-3000}
|
|
40
152
|
ports:
|
|
41
|
-
- "3000:3000"
|
|
42
|
-
depends_on:
|
|
43
|
-
- postgres
|
|
44
|
-
env_file:
|
|
45
|
-
- .env
|
|
153
|
+
- "\${PORT:-3000}:3000"
|
|
46
154
|
volumes:
|
|
47
|
-
- .:/
|
|
48
|
-
|
|
155
|
+
- .:/app
|
|
156
|
+
- app-node-modules:/app/node_modules
|
|
157
|
+
depends_on:
|
|
158
|
+
postgres:
|
|
159
|
+
condition: service_healthy
|
|
49
160
|
|
|
50
161
|
volumes:
|
|
51
|
-
|
|
52
|
-
|
|
162
|
+
postgres-data:
|
|
163
|
+
app-node-modules:
|
|
164
|
+
COMPOSE
|
|
165
|
+
|
|
166
|
+
cat > .dockerignore <<'IGNORE'
|
|
167
|
+
node_modules
|
|
168
|
+
dist
|
|
169
|
+
coverage
|
|
170
|
+
.git
|
|
171
|
+
.env
|
|
172
|
+
.env.*
|
|
173
|
+
!.env.example
|
|
174
|
+
npm-debug.log*
|
|
175
|
+
yarn-debug.log*
|
|
176
|
+
pnpm-debug.log*
|
|
177
|
+
IGNORE
|
|
53
178
|
|
|
54
|
-
|
|
55
|
-
|
|
179
|
+
if [ ! -e .env.example ]; then
|
|
180
|
+
cat > .env.example <<'ENV'
|
|
181
|
+
# Variables utilisées par compose.yaml en développement local.
|
|
182
|
+
POSTGRES_DB=appdb
|
|
183
|
+
POSTGRES_USER=nestgen
|
|
184
|
+
POSTGRES_PASSWORD=change-me-before-sharing
|
|
185
|
+
POSTGRES_PORT=5432
|
|
56
186
|
PORT=3000
|
|
57
|
-
|
|
187
|
+
ENV
|
|
188
|
+
else
|
|
189
|
+
echo "ℹ️ .env.example existe déjà et a été conservé."
|
|
190
|
+
fi
|
|
58
191
|
|
|
59
|
-
echo "✅
|
|
192
|
+
echo "✅ Docker de développement et image de production générés pour $APP_NAME ($PM)."
|
|
@@ -1,35 +1,41 @@
|
|
|
1
|
-
#!/bin/bash
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
RAW_NAME=${1:-}
|
|
6
|
+
ORM=${2:-}
|
|
5
7
|
APP_MODULE="src/app.module.ts"
|
|
6
8
|
MODULE_FILE="src/app/${RAW_NAME}/${RAW_NAME}.module.ts"
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
|
|
11
|
+
if [[ -z "$RAW_NAME" ]]; then
|
|
12
|
+
echo "❌ Tu dois fournir un nom de module." >&2
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
7
15
|
|
|
8
|
-
if [
|
|
9
|
-
echo "❌
|
|
16
|
+
if [[ "$ORM" != "typeorm" && "$ORM" != "prisma" ]]; then
|
|
17
|
+
echo "❌ ORM non supporté : ${ORM:-vide}." >&2
|
|
10
18
|
exit 1
|
|
11
19
|
fi
|
|
12
20
|
|
|
13
|
-
if [ ! -f "$MODULE_FILE" ]; then
|
|
14
|
-
echo "❌ Le fichier $MODULE_FILE est introuvable."
|
|
21
|
+
if [[ ! -f "$MODULE_FILE" ]]; then
|
|
22
|
+
echo "❌ Le fichier $MODULE_FILE est introuvable." >&2
|
|
15
23
|
exit 1
|
|
16
24
|
fi
|
|
17
25
|
|
|
18
|
-
# 🧠 Extraire le vrai nom de la classe exportée
|
|
19
26
|
MODULE_CLASS=$(grep -oE 'export class [A-Za-z0-9_]+' "$MODULE_FILE" | awk '{print $3}')
|
|
20
27
|
|
|
21
|
-
if [ -z "$MODULE_CLASS" ]; then
|
|
22
|
-
echo "❌ Impossible de détecter la classe du module dans $MODULE_FILE"
|
|
28
|
+
if [[ -z "$MODULE_CLASS" ]]; then
|
|
29
|
+
echo "❌ Impossible de détecter la classe du module dans $MODULE_FILE" >&2
|
|
23
30
|
exit 1
|
|
24
31
|
fi
|
|
25
32
|
|
|
26
33
|
MODULE_PATH="./app/${RAW_NAME}/${RAW_NAME}.module"
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
if [ ! -f "$APP_MODULE" ]; then
|
|
35
|
+
if [[ ! -f "$APP_MODULE" ]]; then
|
|
30
36
|
echo "📄 Création de $APP_MODULE"
|
|
31
37
|
mkdir -p src
|
|
32
|
-
cat > "$APP_MODULE" <<EOF
|
|
38
|
+
cat > "$APP_MODULE" <<'EOF'
|
|
33
39
|
import { Module } from '@nestjs/common';
|
|
34
40
|
|
|
35
41
|
@Module({
|
|
@@ -39,61 +45,8 @@ export class AppModule {}
|
|
|
39
45
|
EOF
|
|
40
46
|
fi
|
|
41
47
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
echo "🔧 Insertion de $MODULE_CLASS dans les imports du haut"
|
|
46
|
-
sed -i '' "1s|^|$IMPORT_LINE\n|" "$APP_MODULE"
|
|
47
|
-
else
|
|
48
|
-
echo "ℹ️ Import $MODULE_CLASS déjà présent"
|
|
49
|
-
fi
|
|
50
|
-
|
|
51
|
-
# ➕ Ajouter dans imports: []
|
|
52
|
-
if grep -q "imports: \[" "$APP_MODULE"; then
|
|
53
|
-
if ! grep -q "$MODULE_CLASS" "$APP_MODULE"; then
|
|
54
|
-
sed -i '' "s|imports: \[|imports: [$MODULE_CLASS, |" "$APP_MODULE"
|
|
55
|
-
echo "✅ $MODULE_CLASS injecté dans imports"
|
|
56
|
-
else
|
|
57
|
-
echo "ℹ️ $MODULE_CLASS déjà dans imports"
|
|
58
|
-
fi
|
|
59
|
-
else
|
|
60
|
-
echo "❌ Aucun bloc imports: [] trouvé dans $APP_MODULE"
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
# ➕ Ajouter CqrsModule si absent
|
|
64
|
-
if ! grep -q "CqrsModule" "$APP_MODULE"; then
|
|
65
|
-
sed -i '' "1i\\
|
|
66
|
-
import { CqrsModule } from '@nestjs/cqrs';
|
|
67
|
-
" "$APP_MODULE"
|
|
68
|
-
sed -i '' "s|imports: \[|imports: [CqrsModule, |" "$APP_MODULE"
|
|
69
|
-
echo "✅ CqrsModule injecté"
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
# ➕ Ajouter TypeOrmModule si ORM = typeorm
|
|
73
|
-
if [ "$ORM" = "typeorm" ]; then
|
|
74
|
-
if ! grep -q "TypeOrmModule" "$APP_MODULE"; then
|
|
75
|
-
sed -i '' "1i\\
|
|
76
|
-
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
77
|
-
" "$APP_MODULE"
|
|
78
|
-
echo "✅ Import de TypeOrmModule ajouté"
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
if ! grep -q "TypeOrmModule.forRoot" "$APP_MODULE"; then
|
|
82
|
-
sed -i '' -E "s|(imports: \[)|\1\
|
|
83
|
-
TypeOrmModule.forRoot({\
|
|
84
|
-
type: 'postgres',\
|
|
85
|
-
host: 'localhost',\
|
|
86
|
-
port: 5432,\
|
|
87
|
-
username: 'postgres',\
|
|
88
|
-
password: 'postgres',\
|
|
89
|
-
database: 'appdb',\
|
|
90
|
-
synchronize: true,\
|
|
91
|
-
autoLoadEntities: true,\
|
|
92
|
-
}), |" "$APP_MODULE"
|
|
93
|
-
echo "✅ TypeOrmModule.forRoot injecté"
|
|
94
|
-
else
|
|
95
|
-
echo "ℹ️ TypeOrmModule.forRoot déjà présent"
|
|
96
|
-
fi
|
|
97
|
-
fi
|
|
48
|
+
# Le transformateur prépare le nouveau contenu en mémoire et n'écrit qu'après
|
|
49
|
+
# avoir validé le décorateur @Module et son tableau imports.
|
|
50
|
+
node "$SCRIPT_DIR/update_app_module.mjs" "$APP_MODULE" "$MODULE_CLASS" "$MODULE_PATH" "$ORM"
|
|
98
51
|
|
|
99
52
|
echo "🎯 Module $MODULE_CLASS injecté dans app.module.ts avec succès 🧩"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
function error(message) {
|
|
5
|
+
throw new Error(`Préflight échoué : ${message}`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function inspectProject(projectRoot, orm) {
|
|
9
|
+
const packagePath = path.join(projectRoot, 'package.json');
|
|
10
|
+
const appModulePath = path.join(projectRoot, 'src', 'app.module.ts');
|
|
11
|
+
const nestCliPath = path.join(projectRoot, 'nest-cli.json');
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(packagePath))
|
|
14
|
+
error('package.json introuvable. Exécute la commande à la racine d’un projet NestJS.');
|
|
15
|
+
if (!fs.existsSync(appModulePath))
|
|
16
|
+
error(
|
|
17
|
+
'src/app.module.ts introuvable. Les workspaces et structures personnalisées ne sont pas encore supportés.',
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
21
|
+
const nestCli = fs.existsSync(nestCliPath) ? JSON.parse(fs.readFileSync(nestCliPath, 'utf8')) : null;
|
|
22
|
+
if (nestCli?.monorepo) error('les workspaces Nest ne sont pas encore supportés. Cible une application autonome.');
|
|
23
|
+
|
|
24
|
+
const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
25
|
+
const missing = ['@nestjs/common', '@nestjs/core', '@nestjs/cqrs'];
|
|
26
|
+
if (orm === 'typeorm') missing.push('@nestjs/typeorm', 'typeorm');
|
|
27
|
+
if (orm === 'prisma') missing.push('@prisma/client');
|
|
28
|
+
const unavailable = missing.filter((dependency) => !dependencies[dependency]);
|
|
29
|
+
if (unavailable.length) error(`dépendances manquantes : ${unavailable.join(', ')}.`);
|
|
30
|
+
|
|
31
|
+
const appModule = fs.readFileSync(appModulePath, 'utf8');
|
|
32
|
+
if (!/@Module\s*\(/.test(appModule)) error('le décorateur @Module est introuvable dans src/app.module.ts.');
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
appModulePath,
|
|
36
|
+
packageManager: fs.existsSync(path.join(projectRoot, 'pnpm-lock.yaml'))
|
|
37
|
+
? 'pnpm'
|
|
38
|
+
: fs.existsSync(path.join(projectRoot, 'yarn.lock'))
|
|
39
|
+
? 'yarn'
|
|
40
|
+
: fs.existsSync(path.join(projectRoot, 'package-lock.json'))
|
|
41
|
+
? 'npm'
|
|
42
|
+
: null,
|
|
43
|
+
hasRootTypeOrmConnection: /\bTypeOrmModule\.forRoot\s*\(/.test(appModule),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
48
|
+
try {
|
|
49
|
+
const [projectRoot, orm] = process.argv.slice(2);
|
|
50
|
+
process.stdout.write(`${JSON.stringify(inspectProject(projectRoot, orm))}\n`);
|
|
51
|
+
} catch (cause) {
|
|
52
|
+
console.error(`❌ ${cause.message}`);
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
}
|
|
55
|
+
}
|