create-tigra 2.4.0 → 2.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-tigra",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "type": "module",
5
5
  "description": "Create a production-ready full-stack app with Next.js 16 + Fastify 5 + Prisma + Redis",
6
6
  "bin": {
@@ -0,0 +1,63 @@
1
+ # Dependencies (will be installed fresh in Docker)
2
+ node_modules
3
+ npm-debug.log*
4
+ yarn-debug.log*
5
+ yarn-error.log*
6
+ pnpm-debug.log*
7
+
8
+ # Build output (will be built fresh in Docker)
9
+ .next
10
+ out
11
+ *.tsbuildinfo
12
+
13
+ # Environment files (use Docker env vars instead)
14
+ .env
15
+ .env.local
16
+ .env.*.local
17
+ .env.development
18
+ .env.test
19
+
20
+ # Git
21
+ .git
22
+ .gitignore
23
+ .gitattributes
24
+
25
+ # IDE
26
+ .vscode
27
+ .idea
28
+ *.swp
29
+ *.swo
30
+ *~
31
+ .DS_Store
32
+
33
+ # Testing
34
+ coverage
35
+ .nyc_output
36
+ **/__tests__
37
+ **/*.test.ts
38
+ **/*.test.tsx
39
+ **/*.spec.ts
40
+ **/*.spec.tsx
41
+ vitest.config.ts
42
+ jest.config.ts
43
+
44
+ # Documentation
45
+ docs
46
+ CHANGELOG.md
47
+ LICENSE
48
+
49
+ # Docker
50
+ Dockerfile
51
+ .dockerignore
52
+ docker-compose.yml
53
+
54
+ # CI/CD
55
+ .github
56
+ .gitlab-ci.yml
57
+ .circleci
58
+
59
+ # Misc
60
+ .prettierrc
61
+ .eslintrc
62
+ .eslintignore
63
+ .editorconfig
@@ -0,0 +1,98 @@
1
+ # ===================================================================
2
+ # Multi-stage Dockerfile for Next.js Production Deployment
3
+ # Optimized for Coolify, Docker Compose, and Kubernetes
4
+ # Requires `output: "standalone"` in next.config.ts
5
+ # ===================================================================
6
+
7
+ # ===================================================================
8
+ # Stage 1: Dependencies (cached layer)
9
+ # ===================================================================
10
+ FROM node:20-alpine AS dependencies
11
+
12
+ WORKDIR /app
13
+
14
+ # Copy package files
15
+ COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./
16
+
17
+ # Install dependencies (use the lockfile that exists)
18
+ RUN if [ -f pnpm-lock.yaml ]; then \
19
+ npm install -g pnpm && pnpm install --frozen-lockfile; \
20
+ elif [ -f yarn.lock ]; then \
21
+ yarn install --frozen-lockfile; \
22
+ else \
23
+ npm ci; \
24
+ fi
25
+
26
+ # ===================================================================
27
+ # Stage 2: Build (compile Next.js)
28
+ # ===================================================================
29
+ FROM node:20-alpine AS builder
30
+
31
+ WORKDIR /app
32
+
33
+ # Copy dependencies from previous stage
34
+ COPY --from=dependencies /app/node_modules ./node_modules
35
+
36
+ # Copy source code
37
+ COPY . .
38
+
39
+ # Build arguments for env vars needed at build time
40
+ # Next.js inlines NEXT_PUBLIC_* values during build, so they must
41
+ # be available here. Pass them via --build-arg in Coolify/Docker.
42
+ ARG NEXT_PUBLIC_API_BASE_URL
43
+ ARG NEXT_PUBLIC_APP_NAME
44
+
45
+ ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
46
+ ENV NEXT_PUBLIC_APP_NAME=$NEXT_PUBLIC_APP_NAME
47
+
48
+ # Disable Next.js telemetry during build
49
+ ENV NEXT_TELEMETRY_DISABLED=1
50
+
51
+ # Build the application (outputs to .next/standalone)
52
+ RUN npm run build
53
+
54
+ # ===================================================================
55
+ # Stage 3: Production Runtime
56
+ # ===================================================================
57
+ FROM node:20-alpine AS production
58
+
59
+ # Install dumb-init for proper signal handling
60
+ RUN apk add --no-cache dumb-init
61
+
62
+ # Create non-root user for security
63
+ RUN addgroup -g 1001 -S nodejs && \
64
+ adduser -S nextjs -u 1001
65
+
66
+ WORKDIR /app
67
+
68
+ # Copy the standalone server (includes only required node_modules)
69
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
70
+
71
+ # Copy static assets (not included in standalone output)
72
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
73
+
74
+ # Copy public folder (favicon, images, etc.)
75
+ COPY --from=builder --chown=nextjs:nodejs /app/public ./public
76
+
77
+ # Switch to non-root user
78
+ USER nextjs
79
+
80
+ # Disable telemetry at runtime
81
+ ENV NEXT_TELEMETRY_DISABLED=1
82
+
83
+ # Set hostname to listen on all interfaces (required in containers)
84
+ ENV HOSTNAME="0.0.0.0"
85
+
86
+ # Default port (override via PORT env var in Coolify)
87
+ ENV PORT=3000
88
+ EXPOSE 3000
89
+
90
+ # Health check for container orchestration
91
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
92
+ CMD node -e "const p=process.env.PORT||3000;require('http').get('http://localhost:'+p,(r)=>{process.exit(r.statusCode===200?0:1)})"
93
+
94
+ # Use dumb-init to handle signals properly
95
+ ENTRYPOINT ["dumb-init", "--"]
96
+
97
+ # Start the standalone Next.js server
98
+ CMD ["node", "server.js"]
@@ -10,6 +10,7 @@ const apiOrigin = (() => {
10
10
  })();
11
11
 
12
12
  const nextConfig: NextConfig = {
13
+ output: "standalone",
13
14
  async headers() {
14
15
  return [
15
16
  {
@@ -27,7 +27,7 @@ export default function RootLayout({
27
27
  children: React.ReactNode;
28
28
  }>): React.ReactElement {
29
29
  return (
30
- <html lang="en" suppressHydrationWarning>
30
+ <html lang="en" className="dark" suppressHydrationWarning>
31
31
  <body className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased`}>
32
32
  <Providers>{children}</Providers>
33
33
  </body>
@@ -29,7 +29,7 @@ export function Providers({ children }: { children: React.ReactNode }): React.Re
29
29
  return (
30
30
  <ReduxProvider store={store}>
31
31
  <QueryClientProvider client={queryClient}>
32
- <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
32
+ <ThemeProvider attribute="class" defaultTheme="dark" disableTransitionOnChange>
33
33
  <AuthInitializer>
34
34
  {children}
35
35
  </AuthInitializer>
@@ -10,12 +10,16 @@ import { ROUTES } from '@/lib/constants/routes';
10
10
  import { authService } from '../services/auth.service';
11
11
  import { setUser, setInitialized } from '../store/authSlice';
12
12
 
13
- const AUTH_PAGES: string[] = [ROUTES.LOGIN, ROUTES.REGISTER];
13
+ const PROTECTED_PATHS: string[] = [ROUTES.DASHBOARD, ROUTES.PROFILE];
14
14
 
15
15
  interface AuthInitializerProps {
16
16
  children: React.ReactNode;
17
17
  }
18
18
 
19
+ function isProtectedPath(pathname: string): boolean {
20
+ return PROTECTED_PATHS.some((path) => pathname.startsWith(path));
21
+ }
22
+
19
23
  export const AuthInitializer = ({ children }: AuthInitializerProps): React.ReactElement => {
20
24
  const dispatch = useAppDispatch();
21
25
  const pathname = usePathname();
@@ -23,10 +27,12 @@ export const AuthInitializer = ({ children }: AuthInitializerProps): React.React
23
27
  const { isAuthenticated, isLoggingOut } = useAppSelector((state) => state.auth);
24
28
 
25
29
  useEffect(() => {
26
- if (AUTH_PAGES.includes(pathname)) {
30
+ // On public pages, skip auth hydration — just mark as initialized
31
+ if (!isProtectedPath(pathname)) {
27
32
  dispatch(setInitialized());
28
33
  return;
29
34
  }
35
+
30
36
  if (isAuthenticated || isLoggingOut) return;
31
37
 
32
38
  let cancelled = false;
@@ -45,7 +45,7 @@ apiClient.interceptors.response.use(
45
45
 
46
46
  // Don't retry auth endpoints that don't use tokens —
47
47
  // a 401 here means wrong credentials, not an expired token.
48
- const noRetryEndpoints = [
48
+ const noRetryEndpoints: string[] = [
49
49
  API_ENDPOINTS.AUTH.LOGIN,
50
50
  API_ENDPOINTS.AUTH.REGISTER,
51
51
  API_ENDPOINTS.AUTH.REFRESH,
@@ -63,4 +63,3 @@ postman/
63
63
  .eslintrc
64
64
  .eslintignore
65
65
  .editorconfig
66
- tsconfig.json
@@ -1,8 +1,18 @@
1
1
  # ===================================================================
2
2
  # APPLICATION CONFIGURATION
3
3
  # ===================================================================
4
+ #
5
+ # COOLIFY DEPLOYMENT NOTE:
6
+ # When adding environment variables in Coolify, do NOT check
7
+ # "Available at Buildtime" for any variable unless explicitly noted.
8
+ # The server Dockerfile handles build-time config internally.
9
+ # All variables below are RUNTIME-ONLY unless marked otherwise.
10
+ #
11
+ # ===================================================================
4
12
 
5
13
  # Environment: development | production | test
14
+ # COOLIFY: Do NOT check "Available at Buildtime" — the Dockerfile
15
+ # sets NODE_ENV=development during build to ensure devDependencies install.
6
16
  NODE_ENV=development
7
17
 
8
18
  # Server port (default: 8000)
@@ -17,6 +27,7 @@ HOST=0.0.0.0
17
27
 
18
28
  # Database connection string
19
29
  # Format: mysql://username:password@host:port/database
30
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime".
20
31
  DATABASE_URL="mysql://root:rootpassword@localhost:{{MYSQL_PORT}}/{{DATABASE_NAME}}"
21
32
 
22
33
  # Connection pool settings (for high-traffic production)
@@ -31,6 +42,7 @@ DATABASE_POOL_MAX=10
31
42
 
32
43
  # Redis connection URL
33
44
  # Format: redis://[:password@]host:port[/database]
45
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime".
34
46
  REDIS_URL="redis://localhost:{{REDIS_PORT}}"
35
47
 
36
48
  # Max retry attempts for failed Redis operations
@@ -62,6 +74,13 @@ RATE_LIMIT_MULTIPLIER=1
62
74
  # Maximum file upload size in MB (default: 10)
63
75
  MAX_FILE_SIZE_MB=10
64
76
 
77
+ # COOLIFY PERSISTENT STORAGE (required for uploads to survive redeployments):
78
+ # Go to your service in Coolify → Storages → Add Volume Mount:
79
+ # Name: uploads (or <project-name>-uploads)
80
+ # Source Path: (leave empty — Coolify manages the Docker volume)
81
+ # Destination Path: /app/uploads
82
+ # Without this, all uploaded files are lost on every redeployment.
83
+
65
84
  # ===================================================================
66
85
  # DOCKER PORTS (auto-generated, unique per project)
67
86
  # ===================================================================
@@ -79,6 +98,7 @@ REDIS_COMMANDER_PORT={{REDIS_COMMANDER_PORT}}
79
98
  # JWT secret key (MUST be at least 32 characters)
80
99
  # CRITICAL: Generate a strong random secret for production!
81
100
  # Example: openssl rand -base64 48
101
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
82
102
  JWT_SECRET="{{JWT_SECRET}}"
83
103
 
84
104
  # Access token expiry (short-lived)
@@ -92,6 +112,7 @@ JWT_REFRESH_EXPIRY="7d"
92
112
  # Cookie signing secret (separate from JWT for defense-in-depth)
93
113
  # Optional: defaults to JWT_SECRET if not set
94
114
  # For production: generate a separate secret: openssl rand -base64 48
115
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
95
116
  # COOKIE_SECRET="change-this-to-a-different-secret-at-least-32-chars"
96
117
 
97
118
  # Cookie domain for cross-origin deployments
@@ -3,12 +3,22 @@
3
3
  # ===================================================================
4
4
  # This file contains production-optimized settings for high-traffic
5
5
  # deployments (10K-100K users/day). Copy to .env and customize.
6
+ #
7
+ # COOLIFY DEPLOYMENT NOTE:
8
+ # When adding environment variables in Coolify, do NOT check
9
+ # "Available at Buildtime" for any variable unless explicitly noted.
10
+ # The server Dockerfile handles build-time config internally.
11
+ # Secrets (DATABASE_URL, JWT_SECRET, COOKIE_SECRET, REDIS_URL) must
12
+ # NEVER be available at buildtime — they get baked into the image layer.
13
+ #
6
14
  # ===================================================================
7
15
 
8
16
  # ===================================================================
9
17
  # APPLICATION
10
18
  # ===================================================================
11
19
 
20
+ # COOLIFY: Do NOT check "Available at Buildtime" — the Dockerfile
21
+ # sets NODE_ENV=development during build to ensure devDependencies install.
12
22
  NODE_ENV=production
13
23
  PORT=3000
14
24
  HOST=0.0.0.0
@@ -19,6 +29,7 @@ HOST=0.0.0.0
19
29
 
20
30
  # Production database connection
21
31
  # CRITICAL: Use secure credentials, SSL/TLS, and private network
32
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime".
22
33
  DATABASE_URL="mysql://prod_user:SECURE_PASSWORD@db.internal:3306/prod_db?ssl=true"
23
34
 
24
35
  # Connection pool for high traffic (10K-100K users/day)
@@ -33,6 +44,7 @@ DATABASE_POOL_MAX=50
33
44
 
34
45
  # Production Redis instance
35
46
  # CRITICAL: Use authentication and private network
47
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime".
36
48
  REDIS_URL="redis://:REDIS_PASSWORD@redis.internal:6379"
37
49
 
38
50
  # Production retry settings
@@ -60,18 +72,30 @@ RATE_LIMIT_AUTH_REGISTER_MAX=5
60
72
  # Maximum file upload size in MB
61
73
  MAX_FILE_SIZE_MB=10
62
74
 
75
+ # COOLIFY PERSISTENT STORAGE (required for uploads to survive redeployments):
76
+ # Go to your service in Coolify → Storages → Add Volume Mount:
77
+ # Name: uploads (or <project-name>-uploads)
78
+ # Source Path: (leave empty — Coolify manages the Docker volume)
79
+ # Destination Path: /app/uploads
80
+ # Without this, all uploaded files are lost on every redeployment.
81
+
63
82
  # ===================================================================
64
83
  # JWT AUTHENTICATION
65
84
  # ===================================================================
66
85
 
67
86
  # CRITICAL: Generate a cryptographically secure secret
68
87
  # Example: openssl rand -base64 48
88
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
69
89
  JWT_SECRET="YOUR_PRODUCTION_JWT_SECRET_MUST_BE_AT_LEAST_32_CHARS_LONG"
70
90
 
71
91
  # Production token expiry (tighter security)
72
92
  JWT_ACCESS_EXPIRY="15m"
73
93
  JWT_REFRESH_EXPIRY="7d"
74
94
 
95
+ # Cookie signing secret (separate from JWT for defense-in-depth)
96
+ # COOLIFY: Runtime only. Do NOT check "Available at Buildtime" — this is a secret!
97
+ COOKIE_SECRET="YOUR_PRODUCTION_COOKIE_SECRET_MUST_BE_AT_LEAST_32_CHARS"
98
+
75
99
  # ===================================================================
76
100
  # CORS
77
101
  # ===================================================================
@@ -118,3 +142,5 @@ SENTRY_DSN="https://YOUR_PUBLIC_KEY@o0.ingest.sentry.io/YOUR_PROJECT_ID"
118
142
  # [ ] All secrets are stored in environment variables, not in code
119
143
  # [ ] SSL/TLS certificates are configured
120
144
  # [ ] Firewall rules restrict access to database and Redis
145
+ # [ ] In Coolify: NO secrets have "Available at Buildtime" checked
146
+ # [ ] In Coolify: NODE_ENV does NOT have "Available at Buildtime" checked
@@ -28,6 +28,11 @@ RUN if [ -f pnpm-lock.yaml ]; then \
28
28
  # ===================================================================
29
29
  FROM node:20-alpine AS builder
30
30
 
31
+ # Override NODE_ENV to ensure devDependencies are installed.
32
+ # Coolify may inject NODE_ENV=production as a build arg, which causes npm ci
33
+ # to skip devDependencies (typescript, @types/node, etc.) and break the build.
34
+ ENV NODE_ENV=development
35
+
31
36
  WORKDIR /app
32
37
 
33
38
  # Copy package files and install ALL dependencies (including dev)
@@ -77,6 +82,9 @@ COPY --from=builder --chown=nodejs:nodejs /app/package.json ./package.json
77
82
  # Copy Prisma files (needed for migrations)
78
83
  COPY --from=builder --chown=nodejs:nodejs /app/prisma ./prisma
79
84
 
85
+ # Create uploads directory with correct ownership (must be before USER nodejs)
86
+ RUN mkdir -p /app/uploads && chown nodejs:nodejs /app/uploads
87
+
80
88
  # Switch to non-root user
81
89
  USER nodejs
82
90
 
@@ -6,7 +6,7 @@
6
6
  "main": "dist/server.js",
7
7
  "scripts": {
8
8
  "dev": "tsx watch src/server.ts",
9
- "build": "tsc && tsc-alias",
9
+ "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
10
10
  "start": "node dist/server.js",
11
11
  "test": "vitest run",
12
12
  "test:watch": "vitest",
@@ -13,7 +13,7 @@ async function start(): Promise<void> {
13
13
  if (isShuttingDown) return; // Prevent multiple shutdown attempts
14
14
  isShuttingDown = true;
15
15
 
16
- logger.info(`[SERVER] Received ${signal} shutting down gracefully`);
16
+ logger.info(`[SERVER] Received ${signal} - shutting down gracefully`);
17
17
  try {
18
18
  if (app) {
19
19
  await app.close();
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "dist", "src/test", "src/**/*.test.ts", "src/**/*.spec.ts"]
4
+ }
@@ -14,6 +14,7 @@
14
14
  "outDir": "dist",
15
15
  "rootDir": "src",
16
16
  "baseUrl": ".",
17
+ "types": ["node"],
17
18
  "paths": {
18
19
  "@/*": ["./src/*"],
19
20
  "@modules/*": ["./src/modules/*"],