@tndhuy/create-app 1.2.2 → 1.2.3

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 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
- return content2.replaceAll(startTag, "").replaceAll(endTag, "");
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
- return content2.replace(regex, "");
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
- const pkgManager = guardCancel(await (0, import_prompts.select)({
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
- (0, import_prompts.outro)(
798
- `Next steps:
793
+ (0, import_prompts.outro)(
794
+ `Next steps:
799
795
 
800
796
  cd ${config.serviceName}
801
- ${dryRun ? "" : " npm run start:dev\n"}`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tndhuy/create-app",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "create-app": "dist/cli.js"
@@ -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(() => {
@@ -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
@@ -5,6 +5,9 @@
5
5
  "author": "",
6
6
  "private": true,
7
7
  "license": "MIT",
8
+ "workspaces": [
9
+ "packages/*"
10
+ ],
8
11
  "scripts": {
9
12
  "build": "nest build",
10
13
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
@@ -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(() => {
@@ -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