@tndhuy/create-app 1.2.2 → 1.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/cli.js +26 -28
- package/package.json +1 -1
- package/templates/mongo/gitignore.template +51 -0
- package/templates/mongo/src/infrastructure/cache/redis.service.ts +28 -0
- package/templates/mongo/src/main.ts +5 -3
- package/templates/mongo/src/shared/logger/pino.config.ts +4 -0
- package/templates/postgres/gitignore.template +51 -0
- package/templates/postgres/package.json +3 -0
- package/templates/postgres/src/infrastructure/cache/redis.service.ts +28 -0
- package/templates/postgres/src/infrastructure/health/prisma.health-indicator.ts +6 -0
- package/templates/postgres/src/main.ts +5 -3
- package/templates/postgres/src/shared/logger/pino.config.ts +4 -0
package/dist/cli.js
CHANGED
|
@@ -181,14 +181,26 @@ async function replaceFileContents(dir, replacements, options) {
|
|
|
181
181
|
const checkBlock = (content2, flag, tag) => {
|
|
182
182
|
const startTag = `{{#IF_${tag}}}`;
|
|
183
183
|
const endTag = `{{/IF_${tag}}}`;
|
|
184
|
+
const startNotTag = `{{#IF_NOT_${tag}}}`;
|
|
185
|
+
const endNotTag = `{{/IF_NOT_${tag}}}`;
|
|
184
186
|
if (content2.includes(startTag)) {
|
|
185
187
|
if (flag) {
|
|
186
|
-
|
|
188
|
+
content2 = content2.replaceAll(startTag, "").replaceAll(endTag, "");
|
|
187
189
|
} else {
|
|
188
190
|
const escapedStart = startTag.replace(/\{/g, "\\{").replace(/\}/g, "\\}");
|
|
189
191
|
const escapedEnd = endTag.replace(/\{/g, "\\{").replace(/\}/g, "\\}");
|
|
190
192
|
const regex = new RegExp(`${escapedStart}[\\s\\S]*?${escapedEnd}`, "g");
|
|
191
|
-
|
|
193
|
+
content2 = content2.replace(regex, "");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (content2.includes(startNotTag)) {
|
|
197
|
+
if (!flag) {
|
|
198
|
+
content2 = content2.replaceAll(startNotTag, "").replaceAll(endNotTag, "");
|
|
199
|
+
} else {
|
|
200
|
+
const escapedStart = startNotTag.replace(/\{/g, "\\{").replace(/\}/g, "\\}");
|
|
201
|
+
const escapedEnd = endNotTag.replace(/\{/g, "\\{").replace(/\}/g, "\\}");
|
|
202
|
+
const regex = new RegExp(`${escapedStart}[\\s\\S]*?${escapedEnd}`, "g");
|
|
203
|
+
content2 = content2.replace(regex, "");
|
|
192
204
|
}
|
|
193
205
|
}
|
|
194
206
|
return content2;
|
|
@@ -441,26 +453,6 @@ async function removeOtel(destDir) {
|
|
|
441
453
|
// Remove shutdown logic: void otelSdk?.shutdown()...
|
|
442
454
|
/^\s*void\s+otelSdk\?\.shutdown\(\)\.catch\(\(\)\s*=>\s*undefined\);\n?/m
|
|
443
455
|
]);
|
|
444
|
-
const pinoConfigPath = (0, import_path2.join)(destDir, "src", "shared", "logger", "pino.config.ts");
|
|
445
|
-
if (await pathExists(pinoConfigPath)) {
|
|
446
|
-
await removeMatchingLines(pinoConfigPath, [
|
|
447
|
-
// Remove OTel imports
|
|
448
|
-
/^import\s*\{[^}]*trace[^}]*\}\s*from\s*['"]@opentelemetry\/api['"];\n?/m,
|
|
449
|
-
// Remove mixin block (more robust regex for multiline)
|
|
450
|
-
/^\s*mixin\(\)\s*\{[\s\S]*?\n\s*\},\n/m
|
|
451
|
-
]);
|
|
452
|
-
}
|
|
453
|
-
const redisServicePath = (0, import_path2.join)(destDir, "src", "infrastructure", "cache", "redis.service.ts");
|
|
454
|
-
if (await pathExists(redisServicePath)) {
|
|
455
|
-
await removeMatchingLines(redisServicePath, [
|
|
456
|
-
/^import\s*\{[^}]*trace[^}]*\}\s*from\s*['"]@opentelemetry\/api['"];\n?/m,
|
|
457
|
-
// Remove tracing spans from methods
|
|
458
|
-
/^\s*const\s+span\s*=\s*trace\.getTracer[\s\S]*?span\.end\(\);\n/gm,
|
|
459
|
-
// Fallback: remove any remaining span.end() or span related lines
|
|
460
|
-
/^\s*span\.end\(\);\n/gm,
|
|
461
|
-
/^\s*const\s+span\s*=\s*trace\.getTracer.*\n/gm
|
|
462
|
-
]);
|
|
463
|
-
}
|
|
464
456
|
await safeDeleteFile(destDir, (0, import_path2.join)(destDir, "prometheus.yml"));
|
|
465
457
|
}
|
|
466
458
|
async function addKafka(destDir, serviceName) {
|
|
@@ -571,6 +563,10 @@ async function scaffold(options) {
|
|
|
571
563
|
if (modules.includes("kafka")) {
|
|
572
564
|
await addKafka(destDir, serviceName);
|
|
573
565
|
}
|
|
566
|
+
const templateGitignore = (0, import_path2.join)(destDir, "gitignore.template");
|
|
567
|
+
if (await pathExists(templateGitignore)) {
|
|
568
|
+
await (0, import_promises2.rename)(templateGitignore, (0, import_path2.join)(destDir, ".gitignore"));
|
|
569
|
+
}
|
|
574
570
|
}
|
|
575
571
|
|
|
576
572
|
// src/cli.ts
|
|
@@ -765,8 +761,9 @@ Modules : ${selectedModules}`,
|
|
|
765
761
|
}
|
|
766
762
|
}
|
|
767
763
|
const installDeps = guardCancel(await (0, import_prompts.confirm)({ message: "Install dependencies now?", initialValue: true }));
|
|
764
|
+
let pkgManager = "npm";
|
|
768
765
|
if (installDeps) {
|
|
769
|
-
|
|
766
|
+
pkgManager = guardCancel(await (0, import_prompts.select)({
|
|
770
767
|
message: "Select package manager",
|
|
771
768
|
options: [
|
|
772
769
|
{ value: "pnpm", label: "pnpm", hint: "recommended" },
|
|
@@ -793,13 +790,14 @@ Modules : ${selectedModules}`,
|
|
|
793
790
|
is.stop("Dependency installation failed.");
|
|
794
791
|
}
|
|
795
792
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
`Next steps:
|
|
793
|
+
(0, import_prompts.outro)(
|
|
794
|
+
`Next steps:
|
|
799
795
|
|
|
800
796
|
cd ${config.serviceName}
|
|
801
|
-
${dryRun ? "" :
|
|
802
|
-
|
|
797
|
+
${dryRun ? "" : ` ${pkgManager} run start:dev
|
|
798
|
+
`}`
|
|
799
|
+
);
|
|
800
|
+
}
|
|
803
801
|
}
|
|
804
802
|
main().catch((err) => {
|
|
805
803
|
(0, import_prompts.cancel)(err instanceof Error ? err.message : String(err));
|
package/package.json
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# compiled output
|
|
2
|
+
/dist
|
|
3
|
+
/node_modules
|
|
4
|
+
/build
|
|
5
|
+
|
|
6
|
+
# Logs
|
|
7
|
+
logs
|
|
8
|
+
*.log
|
|
9
|
+
npm-debug.log*
|
|
10
|
+
pnpm-debug.log*
|
|
11
|
+
yarn-debug.log*
|
|
12
|
+
yarn-error.log*
|
|
13
|
+
lerna-debug.log*
|
|
14
|
+
|
|
15
|
+
# OS
|
|
16
|
+
.DS_Store
|
|
17
|
+
|
|
18
|
+
# Tests
|
|
19
|
+
/coverage
|
|
20
|
+
/.nyc_output
|
|
21
|
+
|
|
22
|
+
# IDEs and editors
|
|
23
|
+
/.idea
|
|
24
|
+
.project
|
|
25
|
+
.classpath
|
|
26
|
+
.c9/
|
|
27
|
+
*.launch
|
|
28
|
+
.settings/
|
|
29
|
+
*.sublime-workspace
|
|
30
|
+
|
|
31
|
+
# IDE - VSCode
|
|
32
|
+
.vscode/*
|
|
33
|
+
!.vscode/settings.json
|
|
34
|
+
!.vscode/tasks.json
|
|
35
|
+
!.vscode/launch.json
|
|
36
|
+
!.vscode/extensions.json
|
|
37
|
+
|
|
38
|
+
# Environment files
|
|
39
|
+
.env
|
|
40
|
+
.env.development.local
|
|
41
|
+
.env.test.local
|
|
42
|
+
.env.production.local
|
|
43
|
+
.env.local
|
|
44
|
+
|
|
45
|
+
# TypeScript incremental compilation
|
|
46
|
+
*.tsbuildinfo
|
|
47
|
+
|
|
48
|
+
# Claude Code memory files
|
|
49
|
+
**/.claude/
|
|
50
|
+
**/CLAUDE.md
|
|
51
|
+
!/CLAUDE.md
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Injectable, OnModuleDestroy, Logger } from '@nestjs/common';
|
|
2
2
|
import Redis from 'ioredis';
|
|
3
3
|
import { circuitBreaker, ConsecutiveBreaker, CircuitBreakerPolicy, handleAll } from 'cockatiel';
|
|
4
|
+
{{#IF_OTEL}}
|
|
4
5
|
import { trace } from '@opentelemetry/api';
|
|
5
6
|
|
|
6
7
|
const tracer = trace.getTracer('redis-service');
|
|
8
|
+
{{/IF_OTEL}}
|
|
7
9
|
|
|
8
10
|
@Injectable()
|
|
9
11
|
export class RedisService implements OnModuleDestroy {
|
|
@@ -46,6 +48,7 @@ export class RedisService implements OnModuleDestroy {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
async get(key: string): Promise<string | null> {
|
|
51
|
+
{{#IF_OTEL}}
|
|
49
52
|
return tracer.startActiveSpan('redis.get', async (span) => {
|
|
50
53
|
try {
|
|
51
54
|
span.setAttribute('db.system', 'redis');
|
|
@@ -59,9 +62,14 @@ export class RedisService implements OnModuleDestroy {
|
|
|
59
62
|
span.end();
|
|
60
63
|
}
|
|
61
64
|
});
|
|
65
|
+
{{/IF_OTEL}}
|
|
66
|
+
{{#IF_NOT_OTEL}}
|
|
67
|
+
return this.execute((c) => c.get(key));
|
|
68
|
+
{{/IF_NOT_OTEL}}
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
async set(key: string, value: string, ttlSeconds?: number): Promise<void> {
|
|
72
|
+
{{#IF_OTEL}}
|
|
65
73
|
return tracer.startActiveSpan('redis.set', async (span) => {
|
|
66
74
|
try {
|
|
67
75
|
span.setAttribute('db.system', 'redis');
|
|
@@ -84,9 +92,20 @@ export class RedisService implements OnModuleDestroy {
|
|
|
84
92
|
span.end();
|
|
85
93
|
}
|
|
86
94
|
});
|
|
95
|
+
{{/IF_OTEL}}
|
|
96
|
+
{{#IF_NOT_OTEL}}
|
|
97
|
+
await this.execute(async (c) => {
|
|
98
|
+
if (ttlSeconds) {
|
|
99
|
+
await c.set(key, value, 'EX', ttlSeconds);
|
|
100
|
+
} else {
|
|
101
|
+
await c.set(key, value);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
{{/IF_NOT_OTEL}}
|
|
87
105
|
}
|
|
88
106
|
|
|
89
107
|
async del(key: string): Promise<void> {
|
|
108
|
+
{{#IF_OTEL}}
|
|
90
109
|
return tracer.startActiveSpan('redis.del', async (span) => {
|
|
91
110
|
try {
|
|
92
111
|
span.setAttribute('db.system', 'redis');
|
|
@@ -100,9 +119,14 @@ export class RedisService implements OnModuleDestroy {
|
|
|
100
119
|
span.end();
|
|
101
120
|
}
|
|
102
121
|
});
|
|
122
|
+
{{/IF_OTEL}}
|
|
123
|
+
{{#IF_NOT_OTEL}}
|
|
124
|
+
await this.execute((c) => c.del(key).then(() => undefined));
|
|
125
|
+
{{/IF_NOT_OTEL}}
|
|
103
126
|
}
|
|
104
127
|
|
|
105
128
|
async expire(key: string, ttlSeconds: number): Promise<void> {
|
|
129
|
+
{{#IF_OTEL}}
|
|
106
130
|
return tracer.startActiveSpan('redis.expire', async (span) => {
|
|
107
131
|
try {
|
|
108
132
|
span.setAttribute('db.system', 'redis');
|
|
@@ -117,5 +141,9 @@ export class RedisService implements OnModuleDestroy {
|
|
|
117
141
|
span.end();
|
|
118
142
|
}
|
|
119
143
|
});
|
|
144
|
+
{{/IF_OTEL}}
|
|
145
|
+
{{#IF_NOT_OTEL}}
|
|
146
|
+
await this.execute((c) => c.expire(key, ttlSeconds).then(() => undefined));
|
|
147
|
+
{{/IF_NOT_OTEL}}
|
|
120
148
|
}
|
|
121
149
|
}
|
|
@@ -84,6 +84,11 @@ async function bootstrap() {
|
|
|
84
84
|
const port = parseInt(process.env.PORT ?? '3000', 10);
|
|
85
85
|
const httpServer = await app.listen(port);
|
|
86
86
|
|
|
87
|
+
const logger = app.get(Logger);
|
|
88
|
+
logger.log(`🚀 Application is running on: http://localhost:${port}`);
|
|
89
|
+
logger.log(`📖 API Documentation (Scalar): http://localhost:${port}/docs`);
|
|
90
|
+
logger.log(`📄 API Spec (JSON): http://localhost:${port}/docs/json`);
|
|
91
|
+
|
|
87
92
|
process.on('SIGTERM', () => {
|
|
88
93
|
loggerService.log('SIGTERM signal received: closing HTTP server');
|
|
89
94
|
setTimeout(() => {
|
|
@@ -98,9 +103,6 @@ async function bootstrap() {
|
|
|
98
103
|
});
|
|
99
104
|
}, 15000);
|
|
100
105
|
});
|
|
101
|
-
|
|
102
|
-
const logger = app.get(Logger);
|
|
103
|
-
logger.log(`Application running on http://localhost:${port}`);
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
void bootstrap();
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import type { Params } from 'nestjs-pino';
|
|
3
|
+
{{#IF_OTEL}}
|
|
3
4
|
import { trace, context, isSpanContextValid } from '@opentelemetry/api';
|
|
5
|
+
{{/IF_OTEL}}
|
|
4
6
|
|
|
5
7
|
const isDev = process.env.NODE_ENV !== 'production';
|
|
6
8
|
const LOG_DIR = join(process.cwd(), 'logs');
|
|
@@ -11,6 +13,7 @@ export const pinoConfig: Params = {
|
|
|
11
13
|
level: isDev ? 'debug' : 'info',
|
|
12
14
|
// Inject Trace context into every log line
|
|
13
15
|
mixin() {
|
|
16
|
+
{{#IF_OTEL}}
|
|
14
17
|
const activeSpan = trace.getSpan(context.active());
|
|
15
18
|
if (activeSpan) {
|
|
16
19
|
const spanContext = activeSpan.spanContext();
|
|
@@ -22,6 +25,7 @@ export const pinoConfig: Params = {
|
|
|
22
25
|
};
|
|
23
26
|
}
|
|
24
27
|
}
|
|
28
|
+
{{/IF_OTEL}}
|
|
25
29
|
return {};
|
|
26
30
|
},
|
|
27
31
|
// Standardize on X-Request-Id as the primary req.id
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# compiled output
|
|
2
|
+
dist/
|
|
3
|
+
node_modules/
|
|
4
|
+
/build
|
|
5
|
+
|
|
6
|
+
# Logs
|
|
7
|
+
logs
|
|
8
|
+
*.log
|
|
9
|
+
npm-debug.log*
|
|
10
|
+
pnpm-debug.log*
|
|
11
|
+
yarn-debug.log*
|
|
12
|
+
yarn-error.log*
|
|
13
|
+
lerna-debug.log*
|
|
14
|
+
|
|
15
|
+
# OS
|
|
16
|
+
.DS_Store
|
|
17
|
+
|
|
18
|
+
# Tests
|
|
19
|
+
/coverage
|
|
20
|
+
/.nyc_output
|
|
21
|
+
|
|
22
|
+
# IDEs and editors
|
|
23
|
+
/.idea
|
|
24
|
+
.project
|
|
25
|
+
.classpath
|
|
26
|
+
.c9/
|
|
27
|
+
*.launch
|
|
28
|
+
.settings/
|
|
29
|
+
*.sublime-workspace
|
|
30
|
+
|
|
31
|
+
# IDE - VSCode
|
|
32
|
+
.vscode/*
|
|
33
|
+
!.vscode/settings.json
|
|
34
|
+
!.vscode/tasks.json
|
|
35
|
+
!.vscode/launch.json
|
|
36
|
+
!.vscode/extensions.json
|
|
37
|
+
|
|
38
|
+
# Environment files
|
|
39
|
+
.env
|
|
40
|
+
.env.development.local
|
|
41
|
+
.env.test.local
|
|
42
|
+
.env.production.local
|
|
43
|
+
.env.local
|
|
44
|
+
|
|
45
|
+
# TypeScript incremental compilation
|
|
46
|
+
*.tsbuildinfo
|
|
47
|
+
|
|
48
|
+
# Do not add .npmrc files with auth tokens
|
|
49
|
+
# packages/create-app/.npmrc is committed intentionally (uses ${NODE_AUTH_TOKEN} placeholder, no literal token)
|
|
50
|
+
.npmrc
|
|
51
|
+
!packages/create-app/.npmrc
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Injectable, OnModuleDestroy, Logger } from '@nestjs/common';
|
|
2
2
|
import Redis from 'ioredis';
|
|
3
3
|
import { circuitBreaker, ConsecutiveBreaker, CircuitBreakerPolicy, handleAll } from 'cockatiel';
|
|
4
|
+
{{#IF_OTEL}}
|
|
4
5
|
import { trace } from '@opentelemetry/api';
|
|
5
6
|
|
|
6
7
|
const tracer = trace.getTracer('redis-service');
|
|
8
|
+
{{/IF_OTEL}}
|
|
7
9
|
|
|
8
10
|
@Injectable()
|
|
9
11
|
export class RedisService implements OnModuleDestroy {
|
|
@@ -46,6 +48,7 @@ export class RedisService implements OnModuleDestroy {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
async get(key: string): Promise<string | null> {
|
|
51
|
+
{{#IF_OTEL}}
|
|
49
52
|
return tracer.startActiveSpan('redis.get', async (span) => {
|
|
50
53
|
try {
|
|
51
54
|
span.setAttribute('db.system', 'redis');
|
|
@@ -59,9 +62,14 @@ export class RedisService implements OnModuleDestroy {
|
|
|
59
62
|
span.end();
|
|
60
63
|
}
|
|
61
64
|
});
|
|
65
|
+
{{/IF_OTEL}}
|
|
66
|
+
{{#IF_NOT_OTEL}}
|
|
67
|
+
return this.execute((c) => c.get(key));
|
|
68
|
+
{{/IF_NOT_OTEL}}
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
async set(key: string, value: string, ttlSeconds?: number): Promise<void> {
|
|
72
|
+
{{#IF_OTEL}}
|
|
65
73
|
return tracer.startActiveSpan('redis.set', async (span) => {
|
|
66
74
|
try {
|
|
67
75
|
span.setAttribute('db.system', 'redis');
|
|
@@ -84,9 +92,20 @@ export class RedisService implements OnModuleDestroy {
|
|
|
84
92
|
span.end();
|
|
85
93
|
}
|
|
86
94
|
});
|
|
95
|
+
{{/IF_OTEL}}
|
|
96
|
+
{{#IF_NOT_OTEL}}
|
|
97
|
+
await this.execute(async (c) => {
|
|
98
|
+
if (ttlSeconds) {
|
|
99
|
+
await c.set(key, value, 'EX', ttlSeconds);
|
|
100
|
+
} else {
|
|
101
|
+
await c.set(key, value);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
{{/IF_NOT_OTEL}}
|
|
87
105
|
}
|
|
88
106
|
|
|
89
107
|
async del(key: string): Promise<void> {
|
|
108
|
+
{{#IF_OTEL}}
|
|
90
109
|
return tracer.startActiveSpan('redis.del', async (span) => {
|
|
91
110
|
try {
|
|
92
111
|
span.setAttribute('db.system', 'redis');
|
|
@@ -100,9 +119,14 @@ export class RedisService implements OnModuleDestroy {
|
|
|
100
119
|
span.end();
|
|
101
120
|
}
|
|
102
121
|
});
|
|
122
|
+
{{/IF_OTEL}}
|
|
123
|
+
{{#IF_NOT_OTEL}}
|
|
124
|
+
await this.execute((c) => c.del(key).then(() => undefined));
|
|
125
|
+
{{/IF_NOT_OTEL}}
|
|
103
126
|
}
|
|
104
127
|
|
|
105
128
|
async expire(key: string, ttlSeconds: number): Promise<void> {
|
|
129
|
+
{{#IF_OTEL}}
|
|
106
130
|
return tracer.startActiveSpan('redis.expire', async (span) => {
|
|
107
131
|
try {
|
|
108
132
|
span.setAttribute('db.system', 'redis');
|
|
@@ -117,5 +141,9 @@ export class RedisService implements OnModuleDestroy {
|
|
|
117
141
|
span.end();
|
|
118
142
|
}
|
|
119
143
|
});
|
|
144
|
+
{{/IF_OTEL}}
|
|
145
|
+
{{#IF_NOT_OTEL}}
|
|
146
|
+
await this.execute((c) => c.expire(key, ttlSeconds).then(() => undefined));
|
|
147
|
+
{{/IF_NOT_OTEL}}
|
|
120
148
|
}
|
|
121
149
|
}
|
|
@@ -10,7 +10,13 @@ export class PrismaHealthIndicator extends HealthIndicator {
|
|
|
10
10
|
|
|
11
11
|
async isHealthy(key: string): Promise<HealthIndicatorResult> {
|
|
12
12
|
try {
|
|
13
|
+
{{#IF_POSTGRES}}
|
|
13
14
|
await this.prisma.$queryRaw`SELECT 1`;
|
|
15
|
+
{{/IF_POSTGRES}}
|
|
16
|
+
{{#IF_MONGO}}
|
|
17
|
+
// Prisma on MongoDB uses $runCommandRaw for ping or simple check
|
|
18
|
+
await this.prisma.$runCommandRaw({ ping: 1 });
|
|
19
|
+
{{/IF_MONGO}}
|
|
14
20
|
return this.getStatus(key, true);
|
|
15
21
|
} catch (error) {
|
|
16
22
|
throw new HealthCheckError('Database check failed', this.getStatus(key, false));
|
|
@@ -84,6 +84,11 @@ async function bootstrap() {
|
|
|
84
84
|
const port = parseInt(process.env.PORT ?? '3000', 10);
|
|
85
85
|
const httpServer = await app.listen(port);
|
|
86
86
|
|
|
87
|
+
const logger = app.get(Logger);
|
|
88
|
+
logger.log(`🚀 Application is running on: http://localhost:${port}`);
|
|
89
|
+
logger.log(`📖 API Documentation (Scalar): http://localhost:${port}/docs`);
|
|
90
|
+
logger.log(`📄 API Spec (JSON): http://localhost:${port}/docs/json`);
|
|
91
|
+
|
|
87
92
|
process.on('SIGTERM', () => {
|
|
88
93
|
loggerService.log('SIGTERM signal received: closing HTTP server');
|
|
89
94
|
setTimeout(() => {
|
|
@@ -98,9 +103,6 @@ async function bootstrap() {
|
|
|
98
103
|
});
|
|
99
104
|
}, 15000);
|
|
100
105
|
});
|
|
101
|
-
|
|
102
|
-
const logger = app.get(Logger);
|
|
103
|
-
logger.log(`Application running on http://localhost:${port}`);
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
void bootstrap();
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import type { Params } from 'nestjs-pino';
|
|
3
|
+
{{#IF_OTEL}}
|
|
3
4
|
import { trace, context, isSpanContextValid } from '@opentelemetry/api';
|
|
5
|
+
{{/IF_OTEL}}
|
|
4
6
|
|
|
5
7
|
const isDev = process.env.NODE_ENV !== 'production';
|
|
6
8
|
const LOG_DIR = join(process.cwd(), 'logs');
|
|
@@ -11,6 +13,7 @@ export const pinoConfig: Params = {
|
|
|
11
13
|
level: isDev ? 'debug' : 'info',
|
|
12
14
|
// Inject Trace context into every log line
|
|
13
15
|
mixin() {
|
|
16
|
+
{{#IF_OTEL}}
|
|
14
17
|
const activeSpan = trace.getSpan(context.active());
|
|
15
18
|
if (activeSpan) {
|
|
16
19
|
const spanContext = activeSpan.spanContext();
|
|
@@ -22,6 +25,7 @@ export const pinoConfig: Params = {
|
|
|
22
25
|
};
|
|
23
26
|
}
|
|
24
27
|
}
|
|
28
|
+
{{/IF_OTEL}}
|
|
25
29
|
return {};
|
|
26
30
|
},
|
|
27
31
|
// Standardize on X-Request-Id as the primary req.id
|