better-ts-stack 1.1.4 → 2.0.1
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/LICENSE +21 -0
- package/README.md +113 -82
- package/dist/args.js +90 -0
- package/dist/builder/buildPlanner.js +71 -0
- package/dist/builder/configGenerator.js +19 -10
- package/dist/builder/dependencyInstaller.js +8 -15
- package/dist/builder/fileProcessor.js +47 -44
- package/dist/builder/gitInitializer.js +21 -35
- package/dist/builder/index.js +33 -91
- package/dist/builder/moduleSelector.js +11 -16
- package/dist/builder/templateContext.js +6 -27
- package/dist/index.js +47 -21
- package/dist/modules/registry.js +4 -6
- package/dist/output/nextSteps.js +10 -20
- package/dist/prompts/index.js +22 -59
- package/dist/prompts/stack.js +85 -71
- package/dist/types/index.js +47 -41
- package/dist/ui/index.js +18 -0
- package/dist/ui/messages.js +124 -0
- package/dist/ui/prompts.js +70 -0
- package/dist/ui/theme.js +23 -0
- package/dist/validators/index.js +15 -40
- package/package.json +71 -66
- package/templates/backend/express/.prettierrc +2 -2
- package/templates/backend/express/config.json +40 -44
- package/templates/backend/express/src/{index.ts → index.ts.hbs} +75 -76
- package/templates/backend/nestjs/.prettierignore +52 -0
- package/templates/backend/nestjs/.prettierrc +16 -0
- package/templates/backend/nestjs/config.json +41 -0
- package/templates/backend/nestjs/eslint.config.mjs +40 -0
- package/templates/backend/nestjs/gitignore +36 -0
- package/templates/backend/nestjs/nest-cli.json +9 -0
- package/templates/backend/nestjs/src/app.module.ts.hbs +34 -0
- package/templates/backend/nestjs/src/health/health.controller.ts +12 -0
- package/templates/backend/nestjs/src/main.ts +23 -0
- package/templates/backend/nestjs/tsconfig.eslint.json +9 -0
- package/templates/backend/nestjs/tsconfig.json +25 -0
- package/templates/frontend/nextjs/app/layout.tsx +13 -13
- package/templates/frontend/nextjs/app/page.tsx.hbs +97 -97
- package/templates/frontend/nextjs/components/ui/button.tsx +51 -51
- package/templates/frontend/nextjs/components/ui/card.tsx +60 -60
- package/templates/frontend/nextjs/components/ui/field.tsx +67 -67
- package/templates/frontend/nextjs/components/ui/input.tsx +18 -18
- package/templates/frontend/nextjs/config.json +37 -38
- package/templates/frontend/nextjs/eslint.config.mjs +11 -11
- package/templates/frontend/nextjs/next.config.ts.hbs +12 -12
- package/templates/frontend/tanstack/config.json +45 -48
- package/templates/modules/auth/express/config.json +20 -26
- package/templates/modules/auth/express/src/controllers/authController.ts.hbs +43 -57
- package/templates/modules/auth/nestjs/config.json +20 -0
- package/templates/modules/auth/nestjs/src/auth/auth.controller.ts +46 -0
- package/templates/modules/auth/nestjs/src/auth/auth.guard.ts +36 -0
- package/templates/modules/auth/nestjs/src/auth/auth.module.ts.hbs +22 -0
- package/templates/modules/auth/nestjs/src/auth/auth.schemas.ts +35 -0
- package/templates/modules/auth/nestjs/src/auth/auth.service.ts +70 -0
- package/templates/modules/auth/nestjs/src/auth/jwt.ts +27 -0
- package/templates/modules/auth/nestjs/src/auth/users.service.ts.hbs +144 -0
- package/templates/modules/auth/nestjs/src/auth/zod-validation.pipe.ts +16 -0
- package/templates/modules/auth/nextjs/config.json +18 -31
- package/templates/modules/auth/tanstack/config.json +2 -16
- package/templates/modules/docker/Dockerfile.hbs +135 -133
- package/templates/modules/docker/config.json +15 -19
- package/templates/modules/drizzle/express/config.json +26 -27
- package/templates/modules/drizzle/nestjs/config.json +26 -0
- package/templates/modules/drizzle/nestjs/drizzle.config.ts +13 -0
- package/templates/modules/drizzle/nestjs/src/database/database.module.ts +30 -0
- package/templates/modules/drizzle/nestjs/src/database/schema.ts +10 -0
- package/templates/modules/drizzle/nextjs/config.json +26 -29
- package/templates/modules/drizzle/tanstack/config.json +5 -5
- package/templates/modules/mongoose/express/config.json +15 -16
- package/templates/modules/mongoose/nestjs/config.json +15 -0
- package/templates/modules/mongoose/nestjs/src/users/user.schema.ts +21 -0
- package/templates/modules/mongoose/nextjs/config.json +15 -16
- package/templates/modules/mongoose/tanstack/config.json +16 -17
- package/templates/modules/prisma/express/config.json +27 -28
- package/templates/modules/prisma/nestjs/config.json +27 -0
- package/templates/modules/prisma/nestjs/prisma/schema.prisma +23 -0
- package/templates/modules/prisma/nestjs/prisma.config.ts +12 -0
- package/templates/modules/prisma/nestjs/src/prisma/prisma.module.ts +11 -0
- package/templates/modules/prisma/nestjs/src/prisma/prisma.service.ts +17 -0
- package/templates/modules/prisma/nextjs/config.json +28 -29
- package/templates/modules/prisma/tanstack/config.json +5 -6
- package/dist/builder/configGenerator.d.ts +0 -7
- package/dist/builder/configGenerator.d.ts.map +0 -1
- package/dist/builder/configGenerator.js.map +0 -1
- package/dist/builder/dependencyInstaller.d.ts +0 -3
- package/dist/builder/dependencyInstaller.d.ts.map +0 -1
- package/dist/builder/dependencyInstaller.js.map +0 -1
- package/dist/builder/fileProcessor.d.ts +0 -6
- package/dist/builder/fileProcessor.d.ts.map +0 -1
- package/dist/builder/fileProcessor.js.map +0 -1
- package/dist/builder/gitInitializer.d.ts +0 -2
- package/dist/builder/gitInitializer.d.ts.map +0 -1
- package/dist/builder/gitInitializer.js.map +0 -1
- package/dist/builder/index.d.ts +0 -3
- package/dist/builder/index.d.ts.map +0 -1
- package/dist/builder/index.js.map +0 -1
- package/dist/builder/moduleSelector.d.ts +0 -9
- package/dist/builder/moduleSelector.d.ts.map +0 -1
- package/dist/builder/moduleSelector.js.map +0 -1
- package/dist/builder/templateContext.d.ts +0 -21
- package/dist/builder/templateContext.d.ts.map +0 -1
- package/dist/builder/templateContext.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/intro.d.ts +0 -2
- package/dist/intro.d.ts.map +0 -1
- package/dist/intro.js +0 -27
- package/dist/intro.js.map +0 -1
- package/dist/modules/registry.d.ts +0 -6
- package/dist/modules/registry.d.ts.map +0 -1
- package/dist/modules/registry.js.map +0 -1
- package/dist/output/nextSteps.d.ts +0 -4
- package/dist/output/nextSteps.d.ts.map +0 -1
- package/dist/output/nextSteps.js.map +0 -1
- package/dist/prompts/index.d.ts +0 -4
- package/dist/prompts/index.d.ts.map +0 -1
- package/dist/prompts/index.js.map +0 -1
- package/dist/prompts/stack.d.ts +0 -3
- package/dist/prompts/stack.d.ts.map +0 -1
- package/dist/prompts/stack.js.map +0 -1
- package/dist/types/index.d.ts +0 -159
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/validators/index.d.ts +0 -5
- package/dist/validators/index.d.ts.map +0 -1
- package/dist/validators/index.js.map +0 -1
- package/templates/backend/express/.eslintrc.js +0 -24
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
{{#if (eq database 'drizzle')}}
|
|
2
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
3
|
+
import { eq } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import { type Database, DRIZZLE } from "../database/database.module.js";
|
|
6
|
+
import { users } from "../database/schema.js";
|
|
7
|
+
{{/if}}
|
|
8
|
+
{{#if (eq database 'prisma')}}
|
|
9
|
+
import { Injectable } from "@nestjs/common";
|
|
10
|
+
|
|
11
|
+
import { PrismaService } from "../prisma/prisma.service.js";
|
|
12
|
+
{{/if}}
|
|
13
|
+
{{#if (eq database 'mongoose')}}
|
|
14
|
+
import { Injectable } from "@nestjs/common";
|
|
15
|
+
import { InjectModel } from "@nestjs/mongoose";
|
|
16
|
+
import type { Model } from "mongoose";
|
|
17
|
+
|
|
18
|
+
import { User } from "../users/user.schema.js";
|
|
19
|
+
{{/if}}
|
|
20
|
+
{{#if (eq database 'none')}}
|
|
21
|
+
import { Injectable } from "@nestjs/common";
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
23
|
+
{{/if}}
|
|
24
|
+
|
|
25
|
+
export interface AuthUser {
|
|
26
|
+
id: string;
|
|
27
|
+
email: string;
|
|
28
|
+
name?: string | null;
|
|
29
|
+
password: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CreateUserInput {
|
|
33
|
+
email: string;
|
|
34
|
+
name?: string | null;
|
|
35
|
+
password: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Injectable()
|
|
39
|
+
export class UsersService {
|
|
40
|
+
{{#if (eq database 'drizzle')}}
|
|
41
|
+
constructor(@Inject(DRIZZLE) private readonly db: Database) {}
|
|
42
|
+
|
|
43
|
+
async findByEmail(email: string): Promise<AuthUser | null> {
|
|
44
|
+
const [user] = await this.db
|
|
45
|
+
.select()
|
|
46
|
+
.from(users)
|
|
47
|
+
.where(eq(users.email, email))
|
|
48
|
+
.limit(1);
|
|
49
|
+
return user ?? null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async create(data: CreateUserInput): Promise<AuthUser> {
|
|
53
|
+
const [user] = await this.db.insert(users).values(data).returning();
|
|
54
|
+
return user;
|
|
55
|
+
}
|
|
56
|
+
{{/if}}
|
|
57
|
+
{{#if (eq database 'prisma')}}
|
|
58
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
59
|
+
|
|
60
|
+
findByEmail(email: string): Promise<AuthUser | null> {
|
|
61
|
+
return this.prisma.user.findUnique({ where: { email } });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
create(data: CreateUserInput): Promise<AuthUser> {
|
|
65
|
+
return this.prisma.user.create({ data });
|
|
66
|
+
}
|
|
67
|
+
{{/if}}
|
|
68
|
+
{{#if (eq database 'mongoose')}}
|
|
69
|
+
constructor(
|
|
70
|
+
@InjectModel(User.name) private readonly userModel: Model<User>
|
|
71
|
+
) {}
|
|
72
|
+
|
|
73
|
+
async findByEmail(email: string): Promise<AuthUser | null> {
|
|
74
|
+
const user = await this.userModel.findOne({ email }).exec();
|
|
75
|
+
if (!user) return null;
|
|
76
|
+
return {
|
|
77
|
+
id: user._id.toString(),
|
|
78
|
+
email: user.email,
|
|
79
|
+
name: user.name,
|
|
80
|
+
password: user.password,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async create(data: CreateUserInput): Promise<AuthUser> {
|
|
85
|
+
const user = await this.userModel.create(data);
|
|
86
|
+
return {
|
|
87
|
+
id: user._id.toString(),
|
|
88
|
+
email: user.email,
|
|
89
|
+
name: user.name,
|
|
90
|
+
password: user.password,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
{{/if}}
|
|
94
|
+
{{#if (eq database 'none')}}
|
|
95
|
+
// In-memory store for trying the API out: users are lost on restart, so add
|
|
96
|
+
// a database before going to production
|
|
97
|
+
private readonly users: AuthUser[] = [];
|
|
98
|
+
|
|
99
|
+
findByEmail(email: string): Promise<AuthUser | null> {
|
|
100
|
+
const user = this.users.find((candidate) => candidate.email === email);
|
|
101
|
+
return Promise.resolve(user ?? null);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
create(data: CreateUserInput): Promise<AuthUser> {
|
|
105
|
+
// Enforce unique emails like a database constraint would, so a sign-up that
|
|
106
|
+
// raced past the findByEmail check is rejected instead of duplicated
|
|
107
|
+
if (this.users.some((candidate) => candidate.email === data.email)) {
|
|
108
|
+
return Promise.reject(
|
|
109
|
+
Object.assign(new Error("User already exists"), {
|
|
110
|
+
code: "DUPLICATE_EMAIL",
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const user: AuthUser = { id: randomUUID(), ...data };
|
|
116
|
+
this.users.push(user);
|
|
117
|
+
return Promise.resolve(user);
|
|
118
|
+
}
|
|
119
|
+
{{/if}}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Unique-constraint error codes: Prisma, PostgreSQL (Drizzle wraps it in `cause`),
|
|
123
|
+
// MongoDB, and the in-memory store
|
|
124
|
+
const DUPLICATE_KEY_CODES = new Set<unknown>([
|
|
125
|
+
"P2002",
|
|
126
|
+
"23505",
|
|
127
|
+
11000,
|
|
128
|
+
"DUPLICATE_EMAIL",
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
// True when an insert failed because the email is already taken (e.g. concurrent sign-ups)
|
|
132
|
+
export function isDuplicateEmailError(error: unknown): boolean {
|
|
133
|
+
let current = error;
|
|
134
|
+
for (
|
|
135
|
+
let depth = 0;
|
|
136
|
+
depth < 5 && current && typeof current === "object";
|
|
137
|
+
depth++
|
|
138
|
+
) {
|
|
139
|
+
if (DUPLICATE_KEY_CODES.has((current as { code?: unknown }).code))
|
|
140
|
+
return true;
|
|
141
|
+
current = (current as { cause?: unknown }).cause;
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BadRequestException, type PipeTransform } from "@nestjs/common";
|
|
2
|
+
import type { ZodType } from "zod";
|
|
3
|
+
|
|
4
|
+
// Validates a request body against a Zod schema and returns the parsed value
|
|
5
|
+
export class ZodValidationPipe<T> implements PipeTransform<unknown, T> {
|
|
6
|
+
constructor(private readonly schema: ZodType<T>) {}
|
|
7
|
+
|
|
8
|
+
transform(value: unknown): T {
|
|
9
|
+
// The body is undefined when the request has none
|
|
10
|
+
const result = this.schema.safeParse(value ?? {});
|
|
11
|
+
if (!result.success) {
|
|
12
|
+
throw new BadRequestException(result.error.issues[0].message);
|
|
13
|
+
}
|
|
14
|
+
return result.data;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,31 +1,18 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "auth",
|
|
3
|
-
"name": "Better Auth",
|
|
4
|
-
"description": "Better Auth for Next.js with optional Prisma or Drizzle adapter",
|
|
5
|
-
"type": "feature",
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"@hookform/resolvers": "^5.9.1",
|
|
8
|
-
"better-auth": "^1.7.3",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"templateFiles": [
|
|
20
|
-
"lib/auth.ts.hbs",
|
|
21
|
-
"lib/auth-client.ts",
|
|
22
|
-
"app/api/auth/[...all]/route.ts",
|
|
23
|
-
"app/dashboard/page.tsx",
|
|
24
|
-
"app/sign-in/page.tsx",
|
|
25
|
-
"app/sign-up/page.tsx",
|
|
26
|
-
"components/auth/sign-in-form.tsx",
|
|
27
|
-
"components/auth/sign-up-form.tsx",
|
|
28
|
-
"components/auth/sign-out-button.tsx",
|
|
29
|
-
"lib/auth-schema.ts"
|
|
30
|
-
]
|
|
31
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"id": "auth",
|
|
3
|
+
"name": "Better Auth",
|
|
4
|
+
"description": "Better Auth for Next.js with optional Prisma or Drizzle adapter",
|
|
5
|
+
"type": "feature",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@hookform/resolvers": "^5.9.1",
|
|
8
|
+
"better-auth": "^1.7.3",
|
|
9
|
+
"react-hook-form": "^7.87.0",
|
|
10
|
+
"zod": "^4.6.5"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {},
|
|
13
|
+
"scripts": {},
|
|
14
|
+
"envVars": {
|
|
15
|
+
"BETTER_AUTH_SECRET": "please-change-me-to-a-random-string",
|
|
16
|
+
"BETTER_AUTH_URL": "http://localhost:3000"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -7,27 +7,13 @@
|
|
|
7
7
|
"@hookform/resolvers": "^5.9.1",
|
|
8
8
|
"better-auth": "^1.7.3",
|
|
9
9
|
"dotenv": "^17.4.2",
|
|
10
|
-
"mongodb": "^7.6.0",
|
|
11
10
|
"react-hook-form": "^7.87.0",
|
|
12
|
-
"zod": "^4.5
|
|
11
|
+
"zod": "^4.6.5"
|
|
13
12
|
},
|
|
14
13
|
"devDependencies": {},
|
|
15
14
|
"scripts": {},
|
|
16
15
|
"envVars": {
|
|
17
16
|
"BETTER_AUTH_SECRET": "please-change-me-to-a-random-string",
|
|
18
17
|
"BETTER_AUTH_URL": "http://localhost:3000"
|
|
19
|
-
}
|
|
20
|
-
"templateFiles": [
|
|
21
|
-
"src/lib/auth.ts.hbs",
|
|
22
|
-
"src/lib/auth-client.ts",
|
|
23
|
-
"src/lib/auth-functions.ts",
|
|
24
|
-
"src/routes/api/auth/$.ts",
|
|
25
|
-
"src/routes/dashboard.tsx",
|
|
26
|
-
"src/routes/sign-in.tsx",
|
|
27
|
-
"src/routes/sign-up.tsx",
|
|
28
|
-
"src/components/auth/sign-in-form.tsx",
|
|
29
|
-
"src/components/auth/sign-up-form.tsx",
|
|
30
|
-
"src/components/auth/sign-out-button.tsx",
|
|
31
|
-
"src/lib/auth-schema.ts"
|
|
32
|
-
]
|
|
18
|
+
}
|
|
33
19
|
}
|
|
@@ -1,134 +1,136 @@
|
|
|
1
|
-
{{#if (eq framework 'nextjs')}}
|
|
2
|
-
# Base stage
|
|
3
|
-
FROM node:24-alpine AS base
|
|
4
|
-
WORKDIR /app
|
|
5
|
-
RUN apk add --no-cache libc6-compat
|
|
6
|
-
|
|
7
|
-
# Dependencies stage
|
|
8
|
-
FROM base AS deps
|
|
9
|
-
COPY package*.json ./
|
|
10
|
-
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
11
|
-
|
|
12
|
-
# Build stage
|
|
13
|
-
FROM base AS builder
|
|
14
|
-
COPY --from=deps /app/node_modules ./node_modules
|
|
15
|
-
COPY . .
|
|
16
|
-
{{#if useAuth}}
|
|
17
|
-
# next build loads the auth route, which initializes Better Auth. The build gets
|
|
18
|
-
# a placeholder secret for this command only; the real one is supplied at runtime.
|
|
19
|
-
{{/if}}
|
|
20
|
-
{{#if (eq database 'prisma')}}
|
|
21
|
-
RUN {{#if useAuth}}BETTER_AUTH_SECRET="build-only-placeholder-secret-not-used-at-runtime" {{/if}}DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" npm run build
|
|
22
|
-
{{else if (eq database 'mongoose')}}
|
|
23
|
-
RUN {{#if useAuth}}BETTER_AUTH_SECRET="build-only-placeholder-secret-not-used-at-runtime" {{/if}}MONGODB_URI="mongodb://localhost:27017/build" npm run build
|
|
24
|
-
{{else}}
|
|
25
|
-
RUN {{#if useAuth}}BETTER_AUTH_SECRET="build-only-placeholder-secret-not-used-at-runtime" {{/if}}npm run build
|
|
26
|
-
{{/if}}
|
|
27
|
-
|
|
28
|
-
# Production stage
|
|
29
|
-
FROM node:24-alpine AS runner
|
|
30
|
-
WORKDIR /app
|
|
31
|
-
ENV NODE_ENV=production
|
|
32
|
-
|
|
33
|
-
RUN addgroup --system --gid 1001 nodejs && \
|
|
34
|
-
adduser --system --uid 1001 nextjs
|
|
35
|
-
|
|
36
|
-
COPY --from=builder /app/public ./public
|
|
37
|
-
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
38
|
-
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
39
|
-
|
|
40
|
-
USER nextjs
|
|
41
|
-
EXPOSE {{port}}
|
|
42
|
-
ENV PORT={{port}} HOSTNAME=0.0.0.0
|
|
43
|
-
|
|
44
|
-
CMD ["node", "server.js"]
|
|
45
|
-
|
|
46
|
-
{{else if (eq framework 'tanstack')}}
|
|
47
|
-
# Build stage
|
|
48
|
-
FROM node:24-alpine AS builder
|
|
49
|
-
WORKDIR /app
|
|
50
|
-
|
|
51
|
-
COPY package*.json ./
|
|
52
|
-
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
53
|
-
|
|
54
|
-
COPY . .
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{{
|
|
105
|
-
RUN
|
|
106
|
-
{{else
|
|
107
|
-
RUN
|
|
108
|
-
{{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
1
|
+
{{#if (eq framework 'nextjs')}}
|
|
2
|
+
# Base stage
|
|
3
|
+
FROM node:24-alpine AS base
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
RUN apk add --no-cache libc6-compat
|
|
6
|
+
|
|
7
|
+
# Dependencies stage
|
|
8
|
+
FROM base AS deps
|
|
9
|
+
COPY package*.json ./
|
|
10
|
+
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
11
|
+
|
|
12
|
+
# Build stage
|
|
13
|
+
FROM base AS builder
|
|
14
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
15
|
+
COPY . .
|
|
16
|
+
{{#if useAuth}}
|
|
17
|
+
# next build loads the auth route, which initializes Better Auth. The build gets
|
|
18
|
+
# a placeholder secret for this command only; the real one is supplied at runtime.
|
|
19
|
+
{{/if}}
|
|
20
|
+
{{#if (eq database 'prisma')}}
|
|
21
|
+
RUN {{#if useAuth}}BETTER_AUTH_SECRET="build-only-placeholder-secret-not-used-at-runtime" {{/if}}DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" npm run build
|
|
22
|
+
{{else if (eq database 'mongoose')}}
|
|
23
|
+
RUN {{#if useAuth}}BETTER_AUTH_SECRET="build-only-placeholder-secret-not-used-at-runtime" {{/if}}MONGODB_URI="mongodb://localhost:27017/build" npm run build
|
|
24
|
+
{{else}}
|
|
25
|
+
RUN {{#if useAuth}}BETTER_AUTH_SECRET="build-only-placeholder-secret-not-used-at-runtime" {{/if}}npm run build
|
|
26
|
+
{{/if}}
|
|
27
|
+
|
|
28
|
+
# Production stage
|
|
29
|
+
FROM node:24-alpine AS runner
|
|
30
|
+
WORKDIR /app
|
|
31
|
+
ENV NODE_ENV=production
|
|
32
|
+
|
|
33
|
+
RUN addgroup --system --gid 1001 nodejs && \
|
|
34
|
+
adduser --system --uid 1001 nextjs
|
|
35
|
+
|
|
36
|
+
COPY --from=builder /app/public ./public
|
|
37
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
38
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
39
|
+
|
|
40
|
+
USER nextjs
|
|
41
|
+
EXPOSE {{port}}
|
|
42
|
+
ENV PORT={{port}} HOSTNAME=0.0.0.0
|
|
43
|
+
|
|
44
|
+
CMD ["node", "server.js"]
|
|
45
|
+
|
|
46
|
+
{{else if (eq framework 'tanstack')}}
|
|
47
|
+
# Build stage
|
|
48
|
+
FROM node:24-alpine AS builder
|
|
49
|
+
WORKDIR /app
|
|
50
|
+
|
|
51
|
+
COPY package*.json ./
|
|
52
|
+
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
53
|
+
|
|
54
|
+
COPY . .
|
|
55
|
+
|
|
56
|
+
{{#if (eq database 'prisma')}}
|
|
57
|
+
RUN DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" npm run build
|
|
58
|
+
{{else if (eq database 'mongoose')}}
|
|
59
|
+
RUN MONGODB_URI="mongodb://localhost:27017/build" npm run build
|
|
60
|
+
{{else}}
|
|
61
|
+
RUN npm run build
|
|
62
|
+
{{/if}}
|
|
63
|
+
|
|
64
|
+
# Production stage
|
|
65
|
+
FROM node:24-alpine
|
|
66
|
+
WORKDIR /app
|
|
67
|
+
ENV NODE_ENV=production
|
|
68
|
+
|
|
69
|
+
# Create non-root user for security
|
|
70
|
+
RUN addgroup --system --gid 1001 nodejs && \
|
|
71
|
+
adduser --system --uid 1001 tanstackuser
|
|
72
|
+
|
|
73
|
+
COPY package*.json ./
|
|
74
|
+
# Clear the npm cache in the same layer so it does not ship in the image
|
|
75
|
+
RUN if [ -f package-lock.json ]; then npm ci --omit=dev; else npm install --omit=dev; fi \
|
|
76
|
+
&& npm cache clean --force
|
|
77
|
+
|
|
78
|
+
# Copy built output (dist/server/server.js + dist/client)
|
|
79
|
+
COPY --from=builder --chown=tanstackuser:nodejs /app/dist ./dist
|
|
80
|
+
|
|
81
|
+
# Include the Prisma client generated during the build stage
|
|
82
|
+
{{#if (eq database 'prisma')}}
|
|
83
|
+
COPY --from=builder --chown=tanstackuser:nodejs /app/generated ./generated
|
|
84
|
+
{{/if}}
|
|
85
|
+
|
|
86
|
+
USER tanstackuser
|
|
87
|
+
EXPOSE {{port}}
|
|
88
|
+
ENV PORT={{port}}
|
|
89
|
+
|
|
90
|
+
CMD ["node_modules/.bin/srvx", "--prod", "-s", "../client", "dist/server/server.js"]
|
|
91
|
+
|
|
92
|
+
{{else}}
|
|
93
|
+
# Build stage
|
|
94
|
+
FROM node:24-alpine AS builder
|
|
95
|
+
WORKDIR /app
|
|
96
|
+
|
|
97
|
+
COPY package*.json ./
|
|
98
|
+
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
99
|
+
|
|
100
|
+
COPY . .
|
|
101
|
+
|
|
102
|
+
{{#if (eq database 'prisma')}}
|
|
103
|
+
RUN DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" npm run build
|
|
104
|
+
{{else if (eq database 'mongoose')}}
|
|
105
|
+
RUN MONGODB_URI="mongodb://localhost:27017/build" npm run build
|
|
106
|
+
{{else}}
|
|
107
|
+
RUN npm run build
|
|
108
|
+
{{/if}}
|
|
109
|
+
|
|
110
|
+
# Production stage
|
|
111
|
+
FROM node:24-alpine
|
|
112
|
+
WORKDIR /app
|
|
113
|
+
ENV NODE_ENV=production
|
|
114
|
+
|
|
115
|
+
# Create non-root user for security
|
|
116
|
+
RUN addgroup --system --gid 1001 nodejs && \
|
|
117
|
+
adduser --system --uid 1001 appuser
|
|
118
|
+
|
|
119
|
+
COPY package*.json ./
|
|
120
|
+
# Clear the npm cache in the same layer so it does not ship in the image
|
|
121
|
+
RUN if [ -f package-lock.json ]; then npm ci --omit=dev; else npm install --omit=dev; fi \
|
|
122
|
+
&& npm cache clean --force
|
|
123
|
+
|
|
124
|
+
# Copy the compiled output
|
|
125
|
+
COPY --from=builder --chown=appuser:nodejs /app/dist ./dist
|
|
126
|
+
|
|
127
|
+
USER appuser
|
|
128
|
+
EXPOSE {{port}}
|
|
129
|
+
ENV PORT={{port}}
|
|
130
|
+
|
|
131
|
+
{{#if (eq framework 'nestjs')}}
|
|
132
|
+
CMD ["node", "dist/main.js"]
|
|
133
|
+
{{else}}
|
|
134
|
+
CMD ["node", "dist/index.js"]
|
|
135
|
+
{{/if}}
|
|
134
136
|
{{/if}}
|
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "docker",
|
|
3
|
-
"name": "Docker",
|
|
4
|
-
"description": "Docker containerization with multi-stage build and docker-compose",
|
|
5
|
-
"type": "feature",
|
|
6
|
-
"dependencies": {},
|
|
7
|
-
"devDependencies": {},
|
|
8
|
-
"scripts": {
|
|
9
|
-
"docker:build": "docker build -t {{projectName}} .",
|
|
10
|
-
"docker:up": "docker compose up -d --build",
|
|
11
|
-
"docker:down": "docker compose down",
|
|
12
|
-
"docker:logs": "docker compose logs -f"
|
|
13
|
-
},
|
|
14
|
-
"envVars": {}
|
|
15
|
-
|
|
16
|
-
"docker-compose.yml.hbs",
|
|
17
|
-
"Dockerfile.hbs"
|
|
18
|
-
]
|
|
19
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"id": "docker",
|
|
3
|
+
"name": "Docker",
|
|
4
|
+
"description": "Docker containerization with multi-stage build and docker-compose",
|
|
5
|
+
"type": "feature",
|
|
6
|
+
"dependencies": {},
|
|
7
|
+
"devDependencies": {},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"docker:build": "docker build -t {{projectName}} .",
|
|
10
|
+
"docker:up": "docker compose up -d --build",
|
|
11
|
+
"docker:down": "docker compose down",
|
|
12
|
+
"docker:logs": "docker compose logs -f"
|
|
13
|
+
},
|
|
14
|
+
"envVars": {}
|
|
15
|
+
}
|
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "drizzle",
|
|
3
|
-
"name": "Drizzle ORM",
|
|
4
|
-
"description": "Drizzle ORM for Express with PostgreSQL",
|
|
5
|
-
"type": "database",
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"drizzle-orm": "^0.45.2",
|
|
8
|
-
"pg": "^8.23.0",
|
|
9
|
-
"dotenv": "^17.4.2"
|
|
10
|
-
},
|
|
11
|
-
"devDependencies": {
|
|
12
|
-
"drizzle-kit": "^0.31.10",
|
|
13
|
-
"@types/pg": "^8.23.1"
|
|
14
|
-
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"db:generate": "drizzle-kit generate",
|
|
17
|
-
"db:migrate": "drizzle-kit migrate",
|
|
18
|
-
"db:studio": "drizzle-kit studio"
|
|
19
|
-
},
|
|
20
|
-
"envVars": {
|
|
21
|
-
"DATABASE_URL": "postgresql://
|
|
22
|
-
},
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"id": "drizzle",
|
|
3
|
+
"name": "Drizzle ORM",
|
|
4
|
+
"description": "Drizzle ORM for Express with PostgreSQL",
|
|
5
|
+
"type": "database",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"drizzle-orm": "^0.45.2",
|
|
8
|
+
"pg": "^8.23.0",
|
|
9
|
+
"dotenv": "^17.4.2"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"drizzle-kit": "^0.31.10",
|
|
13
|
+
"@types/pg": "^8.23.1"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"db:generate": "drizzle-kit generate",
|
|
17
|
+
"db:migrate": "drizzle-kit migrate",
|
|
18
|
+
"db:studio": "drizzle-kit studio"
|
|
19
|
+
},
|
|
20
|
+
"envVars": {
|
|
21
|
+
"DATABASE_URL": "postgresql://postgres:postgres@localhost:5432/postgres"
|
|
22
|
+
},
|
|
23
|
+
"overrides": {
|
|
24
|
+
"esbuild": "^0.28.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "drizzle",
|
|
3
|
+
"name": "Drizzle ORM",
|
|
4
|
+
"description": "Drizzle ORM for NestJS with PostgreSQL",
|
|
5
|
+
"type": "database",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"drizzle-orm": "^0.45.2",
|
|
8
|
+
"pg": "^8.23.0",
|
|
9
|
+
"dotenv": "^17.4.2"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"drizzle-kit": "^0.31.10",
|
|
13
|
+
"@types/pg": "^8.23.1"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"db:generate": "drizzle-kit generate",
|
|
17
|
+
"db:migrate": "drizzle-kit migrate",
|
|
18
|
+
"db:studio": "drizzle-kit studio"
|
|
19
|
+
},
|
|
20
|
+
"envVars": {
|
|
21
|
+
"DATABASE_URL": "postgresql://postgres:postgres@localhost:5432/postgres"
|
|
22
|
+
},
|
|
23
|
+
"overrides": {
|
|
24
|
+
"esbuild": "^0.28.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { config } from "dotenv";
|
|
2
|
+
import { defineConfig } from "drizzle-kit";
|
|
3
|
+
|
|
4
|
+
config({ path: ".env" });
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
schema: "./src/database/schema.ts",
|
|
8
|
+
out: "./migrations",
|
|
9
|
+
dialect: "postgresql",
|
|
10
|
+
dbCredentials: {
|
|
11
|
+
url: process.env.DATABASE_URL!,
|
|
12
|
+
},
|
|
13
|
+
});
|