create-forgeon 0.3.4 → 0.3.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-forgeon",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
@@ -572,10 +572,9 @@ describe('addModule', () => {
572
572
 
573
573
  const mainTs = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'main.ts'), 'utf8');
574
574
  assert.match(mainTs, /ForgeonLoggerService/);
575
- assert.match(mainTs, /ForgeonHttpLoggingInterceptor/);
576
575
  assert.match(mainTs, /bufferLogs: true/);
577
576
  assert.match(mainTs, /app\.useLogger\(app\.get\(ForgeonLoggerService\)\);/);
578
- assert.match(mainTs, /app\.useGlobalInterceptors\(app\.get\(ForgeonHttpLoggingInterceptor\)\);/);
577
+ assert.doesNotMatch(mainTs, /useGlobalInterceptors/);
579
578
 
580
579
  const apiDockerfile = fs.readFileSync(
581
580
  path.join(projectRoot, 'apps', 'api', 'Dockerfile'),
@@ -591,6 +590,13 @@ describe('addModule', () => {
591
590
  );
592
591
  assert.match(loggerTsconfig, /"extends": "\.\.\/\.\.\/tsconfig\.base\.node\.json"/);
593
592
 
593
+ const loggerModule = fs.readFileSync(
594
+ path.join(projectRoot, 'packages', 'logger', 'src', 'forgeon-logger.module.ts'),
595
+ 'utf8',
596
+ );
597
+ assert.match(loggerModule, /ForgeonHttpLoggingMiddleware/);
598
+ assert.match(loggerModule, /consumer\.apply\(RequestIdMiddleware, ForgeonHttpLoggingMiddleware\)\.forRoutes\('\*'\);/);
599
+
594
600
  const apiEnv = fs.readFileSync(path.join(projectRoot, 'apps', 'api', '.env.example'), 'utf8');
595
601
  assert.match(apiEnv, /LOGGER_LEVEL=log/);
596
602
  assert.match(apiEnv, /LOGGER_HTTP_ENABLED=true/);
@@ -957,7 +963,7 @@ describe('addModule', () => {
957
963
 
958
964
  const mainTs = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'main.ts'), 'utf8');
959
965
  assert.match(mainTs, /ForgeonLoggerService/);
960
- assert.match(mainTs, /ForgeonHttpLoggingInterceptor/);
966
+ assert.doesNotMatch(mainTs, /ForgeonHttpLoggingInterceptor/);
961
967
  } finally {
962
968
  fs.rmSync(targetRoot, { recursive: true, force: true });
963
969
  }
@@ -1058,7 +1064,7 @@ describe('addModule', () => {
1058
1064
  const mainTs = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'src', 'main.ts'), 'utf8');
1059
1065
  assert.match(mainTs, /setupSwagger\(app,\s*swaggerConfigService\)/);
1060
1066
  assert.match(mainTs, /app\.useLogger\(app\.get\(ForgeonLoggerService\)\);/);
1061
- assert.match(mainTs, /app\.useGlobalInterceptors\(app\.get\(ForgeonHttpLoggingInterceptor\)\);/);
1067
+ assert.doesNotMatch(mainTs, /useGlobalInterceptors/);
1062
1068
 
1063
1069
  const apiPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'api', 'package.json'), 'utf8');
1064
1070
  assert.match(apiPackage, /@forgeon\/swagger/);
@@ -44,10 +44,14 @@ function patchMain(targetRoot) {
44
44
  }
45
45
 
46
46
  let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
47
+ content = content.replace(
48
+ "import { ForgeonHttpLoggingInterceptor, ForgeonLoggerService } from '@forgeon/logger';",
49
+ "import { ForgeonLoggerService } from '@forgeon/logger';",
50
+ );
47
51
  content = ensureLineBefore(
48
52
  content,
49
53
  "import { NestFactory } from '@nestjs/core';",
50
- "import { ForgeonHttpLoggingInterceptor, ForgeonLoggerService } from '@forgeon/logger';",
54
+ "import { ForgeonLoggerService } from '@forgeon/logger';",
51
55
  );
52
56
 
53
57
  content = content.replace(
@@ -55,12 +59,16 @@ function patchMain(targetRoot) {
55
59
  'const app = await NestFactory.create(AppModule, { bufferLogs: true });',
56
60
  );
57
61
 
62
+ content = content.replace(
63
+ /\n\s*app\.useGlobalInterceptors\(app\.get\(ForgeonHttpLoggingInterceptor\)\);\s*/g,
64
+ '\n',
65
+ );
66
+
58
67
  if (!content.includes('app.useLogger(app.get(ForgeonLoggerService));')) {
59
68
  content = content.replace(
60
69
  ' const coreConfigService = app.get(CoreConfigService);',
61
70
  ` const coreConfigService = app.get(CoreConfigService);
62
- app.useLogger(app.get(ForgeonLoggerService));
63
- app.useGlobalInterceptors(app.get(ForgeonHttpLoggingInterceptor));`,
71
+ app.useLogger(app.get(ForgeonLoggerService));`,
64
72
  );
65
73
  }
66
74
 
@@ -179,6 +187,7 @@ function patchReadme(targetRoot) {
179
187
  The logger add-module provides:
180
188
  - request id middleware (default header: \`x-request-id\`)
181
189
  - HTTP access logs with method/path/status/duration/ip/requestId
190
+ - HTTP access logs are emitted from middleware, so requests rejected by guards (for example 429 from rate-limit) are still logged
182
191
  - Nest logger integration via \`app.useLogger(...)\`
183
192
 
184
193
  It installs independently and intentionally does not add a dedicated API/web probe.
@@ -1,17 +1,16 @@
1
1
  import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
2
- import { ForgeonHttpLoggingInterceptor } from './http-logging.interceptor';
2
+ import { ForgeonHttpLoggingMiddleware } from './http-logging.middleware';
3
3
  import { ForgeonLoggerService } from './forgeon-logger.service';
4
4
  import { LoggerConfigModule } from './logger-config.module';
5
5
  import { RequestIdMiddleware } from './request-id.middleware';
6
6
 
7
7
  @Module({
8
8
  imports: [LoggerConfigModule],
9
- providers: [RequestIdMiddleware, ForgeonLoggerService, ForgeonHttpLoggingInterceptor],
10
- exports: [LoggerConfigModule, ForgeonLoggerService, ForgeonHttpLoggingInterceptor],
9
+ providers: [RequestIdMiddleware, ForgeonHttpLoggingMiddleware, ForgeonLoggerService],
10
+ exports: [LoggerConfigModule, ForgeonLoggerService],
11
11
  })
12
12
  export class ForgeonLoggerModule implements NestModule {
13
13
  configure(consumer: MiddlewareConsumer): void {
14
- consumer.apply(RequestIdMiddleware).forRoutes('*');
14
+ consumer.apply(RequestIdMiddleware, ForgeonHttpLoggingMiddleware).forRoutes('*');
15
15
  }
16
16
  }
17
-
@@ -0,0 +1,74 @@
1
+ import { Injectable, NestMiddleware } from '@nestjs/common';
2
+ import { ForgeonLoggerService } from './forgeon-logger.service';
3
+ import { LoggerConfigService } from './logger-config.service';
4
+
5
+ type HeaderValue = string | string[] | undefined;
6
+ type HeadersRecord = Record<string, HeaderValue>;
7
+
8
+ interface RequestLike {
9
+ method?: string;
10
+ originalUrl?: string;
11
+ url?: string;
12
+ ip?: string;
13
+ requestId?: string;
14
+ headers?: HeadersRecord;
15
+ }
16
+
17
+ interface ResponseLike {
18
+ statusCode?: number;
19
+ once?: (event: string, listener: () => void) => void;
20
+ }
21
+
22
+ type NextFunction = () => void;
23
+
24
+ @Injectable()
25
+ export class ForgeonHttpLoggingMiddleware implements NestMiddleware {
26
+ constructor(
27
+ private readonly logger: ForgeonLoggerService,
28
+ private readonly loggerConfig: LoggerConfigService,
29
+ ) {}
30
+
31
+ use(request: RequestLike, response: ResponseLike, next: NextFunction): void {
32
+ if (!this.loggerConfig.httpEnabled) {
33
+ next();
34
+ return;
35
+ }
36
+
37
+ const method = request.method ?? 'UNKNOWN';
38
+ const path = request.originalUrl ?? request.url ?? '/';
39
+ const ip = request.ip;
40
+ const requestId =
41
+ request.requestId ?? this.readHeader(request.headers, this.loggerConfig.requestIdHeader);
42
+ const startedAt = Date.now();
43
+
44
+ if (typeof response.once === 'function') {
45
+ response.once('finish', () => {
46
+ this.logger.logHttpRequest({
47
+ method,
48
+ path,
49
+ statusCode: response.statusCode ?? 200,
50
+ durationMs: Date.now() - startedAt,
51
+ requestId,
52
+ ip,
53
+ });
54
+ });
55
+ }
56
+
57
+ next();
58
+ }
59
+
60
+ private readHeader(headers: HeadersRecord | undefined, name: string): string | undefined {
61
+ if (!headers) {
62
+ return undefined;
63
+ }
64
+
65
+ const value = headers[name.toLowerCase()];
66
+ if (typeof value === 'string' && value.trim().length > 0) {
67
+ return value;
68
+ }
69
+ if (Array.isArray(value) && typeof value[0] === 'string' && value[0].trim().length > 0) {
70
+ return value[0];
71
+ }
72
+ return undefined;
73
+ }
74
+ }
@@ -3,7 +3,7 @@ export * from './logger-config.loader';
3
3
  export * from './logger-config.service';
4
4
  export * from './logger-config.module';
5
5
  export * from './forgeon-logger.service';
6
+ export * from './http-logging.middleware';
6
7
  export * from './http-logging.interceptor';
7
8
  export * from './request-id.middleware';
8
9
  export * from './forgeon-logger.module';
9
-