Skip to content

Commit 3f71a2d

Browse files
feat: add better-auth support (#2142)
Co-authored-by: Shoubhit Dash <[email protected]>
1 parent 48b6816 commit 3f71a2d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+2817
-80
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-t3-app": minor
3+
---
4+
5+
feat: new better auth setup with drizzle and prisma

.github/scripts/generate-matrix.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Define all possible values
2+
const options = {
3+
trpc: ['true', 'false'],
4+
tailwind: ['true', 'false'],
5+
nextAuth: ['true', 'false'],
6+
betterAuth: ['true', 'false'],
7+
prisma: ['true', 'false'],
8+
drizzle: ['true', 'false'],
9+
appRouter: ['true', 'false'],
10+
dbType: ['planetscale', 'sqlite', 'mysql', 'postgres']
11+
};
12+
13+
// Generate all combinations
14+
function generateCombinations(opts) {
15+
const keys = Object.keys(opts);
16+
const combinations = [];
17+
18+
function recurse(index, current) {
19+
if (index === keys.length) {
20+
combinations.push({...current});
21+
return;
22+
}
23+
24+
const key = keys[index];
25+
for (const value of opts[key]) {
26+
current[key] = value;
27+
recurse(index + 1, current);
28+
}
29+
}
30+
31+
recurse(0, {});
32+
return combinations;
33+
}
34+
35+
// Filter valid combinations based on current validation logic
36+
function isValid(combo) {
37+
const { prisma, drizzle, nextAuth, betterAuth, dbType } = combo;
38+
39+
// Not both auth true
40+
if (nextAuth === 'true' && betterAuth === 'true') return false;
41+
42+
// Not both db true
43+
if (prisma === 'true' && drizzle === 'true') return false;
44+
45+
// If no db selected, only allow sqlite
46+
if (prisma === 'false' && drizzle === 'false' && dbType !== 'sqlite') return false;
47+
48+
return true;
49+
}
50+
51+
const allCombos = generateCombinations(options);
52+
const validCombos = allCombos.filter(isValid);
53+
54+
console.log(`matrix=${JSON.stringify({include: validCombos})}`);

.github/workflows/e2e.yml

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,47 +20,42 @@ concurrency:
2020
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
2121

2222
jobs:
23+
generate-matrix:
24+
runs-on: ubuntu-latest
25+
outputs:
26+
matrix: ${{ steps.generate.outputs.matrix }}
27+
steps:
28+
- uses: actions/checkout@v4
29+
- name: Generate valid matrix combinations
30+
id: generate
31+
run: node .github/scripts/generate-matrix.js >> $GITHUB_OUTPUT
32+
2333
build-t3-app:
34+
needs: generate-matrix
2435
runs-on: ubuntu-latest
2536
# if: |
2637
# contains(github.event.pull_request.labels.*.name, '📌 area: cli') ||
2738
# contains(github.event.pull_request.labels.*.name, '📌 area: t3-app')
2839
strategy:
29-
matrix:
30-
trpc: ["true", "false"]
31-
tailwind: ["true", "false"]
32-
nextAuth: ["true", "false"]
33-
prisma: ["true", "false"]
34-
appRouter: ["true", "false"]
35-
drizzle: ["true", "false"]
36-
dbType: ["planetscale", "sqlite", "mysql", "postgres"]
37-
38-
name: "Build and Start T3 App ${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}-${{ matrix.dbType }}"
40+
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
41+
42+
name: "Build and Start T3 App ${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.betterAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}-${{ matrix.dbType }}"
3943
steps:
4044
- uses: actions/checkout@v4
4145
with:
4246
fetch-depth: 0
4347

44-
- name: Check valid matrix
45-
id: matrix-valid
46-
run: |
47-
echo "continue=${{ (matrix.prisma == 'false' || matrix.drizzle == 'false') && (matrix.drizzle == 'true' || matrix.prisma == 'true' || matrix.dbType == 'sqlite') }}" >> $GITHUB_OUTPUT
48-
4948
- uses: ./.github/actions/setup
50-
if: ${{ steps.matrix-valid.outputs.continue == 'true' }}
5149

5250
- run: pnpm turbo --filter=create-t3-app build
53-
if: ${{ steps.matrix-valid.outputs.continue == 'true' }}
5451

5552
# has to be scaffolded outside the CLI project so that no lint/tsconfig are leaking
5653
# through. this way it ensures that it is the app's configs that are being used
5754
# FIXME: this is a bit hacky, would rather have --packages=trpc,tailwind,... but not sure how to setup the matrix for that
58-
- run: cd cli && pnpm start ../../ci-${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}-${{ matrix.dbType }} --noGit --CI --trpc=${{ matrix.trpc }} --tailwind=${{ matrix.tailwind }} --nextAuth=${{ matrix.nextAuth }} --prisma=${{ matrix.prisma }} --drizzle=${{ matrix.drizzle }} --appRouter=${{ matrix.appRouter }} --dbProvider=${{ matrix.dbType }} --eslint
59-
if: ${{ steps.matrix-valid.outputs.continue == 'true' }}
55+
- run: cd cli && pnpm start ../../ci-${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.betterAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}-${{ matrix.dbType }} --noGit --CI --trpc=${{ matrix.trpc }} --tailwind=${{ matrix.tailwind }} --nextAuth=${{ matrix.nextAuth }} --betterAuth=${{ matrix.betterAuth }} --prisma=${{ matrix.prisma }} --drizzle=${{ matrix.drizzle }} --appRouter=${{ matrix.appRouter }} --dbProvider=${{ matrix.dbType }}
6056
# can't use default mysql string cause t3-env blocks that
6157

62-
- run: cd ../ci-${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}-${{ matrix.dbType }} && pnpm build
63-
if: ${{ steps.matrix-valid.outputs.continue == 'true' }}
58+
- run: cd ../ci-${{ matrix.trpc }}-${{ matrix.tailwind }}-${{ matrix.nextAuth }}-${{ matrix.betterAuth }}-${{ matrix.prisma }}-${{ matrix.drizzle}}-${{ matrix.appRouter }}-${{ matrix.dbType }} && pnpm build
6459
env:
6560
AUTH_SECRET: foo
6661
AUTH_DISCORD_ID: bar
@@ -87,7 +82,7 @@ jobs:
8782
echo "continue=${{ (matrix.eslint == 'false' || matrix.biome == 'false') && (matrix.biome == 'true' || matrix.eslint == 'true') }}" >> $GITHUB_OUTPUT
8883
8984
- uses: ./.github/actions/setup
90-
if: ${{ steps.matrix-valid.outputs.continue == 'true' }}
85+
if: ${{ steps.matrix-valid.outputs.continue == 'true' }}
9186

9287
- run: pnpm turbo --filter=create-t3-app build
9388
if: ${{ steps.matrix-valid.outputs.continue == 'true' }}

cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"@types/fs-extra": "^11.0.4",
7979
"@types/gradient-string": "^1.1.6",
8080
"@types/node": "^20.14.10",
81+
"better-auth": "^1.3",
8182
"drizzle-kit": "^0.30.5",
8283
"drizzle-orm": "^0.41.0",
8384
"mysql2": "^3.11.0",

cli/src/cli/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ interface CliFlags {
3434
/** @internal Used in CI. */
3535
nextAuth: boolean;
3636
/** @internal Used in CI. */
37+
betterAuth: boolean;
38+
/** @internal Used in CI. */
3739
appRouter: boolean;
3840
/** @internal Used in CI. */
3941
dbProvider: DatabaseProvider;
@@ -63,6 +65,7 @@ const defaultOptions: CliResults = {
6365
prisma: false,
6466
drizzle: false,
6567
nextAuth: false,
68+
betterAuth: false,
6669
importAlias: "~/",
6770
appRouter: false,
6871
dbProvider: "sqlite",
@@ -115,6 +118,12 @@ export const runCli = async (): Promise<CliResults> => {
115118
"Experimental: Boolean value if we should install NextAuth.js. Must be used in conjunction with `--CI`.",
116119
(value) => !!value && value !== "false"
117120
)
121+
/** @experimental Used for CI E2E tests. Used in conjunction with `--CI` to skip prompting. */
122+
.option(
123+
"--betterAuth [boolean]",
124+
"Experimental: Boolean value if we should install BetterAuth. Must be used in conjunction with `--CI`.",
125+
(value) => !!value && value !== "false"
126+
)
118127
/** @experimental - Used for CI E2E tests. Used in conjunction with `--CI` to skip prompting. */
119128
.option(
120129
"--prisma [boolean]",
@@ -199,6 +208,7 @@ export const runCli = async (): Promise<CliResults> => {
199208
if (cliResults.flags.prisma) cliResults.packages.push("prisma");
200209
if (cliResults.flags.drizzle) cliResults.packages.push("drizzle");
201210
if (cliResults.flags.nextAuth) cliResults.packages.push("nextAuth");
211+
if (cliResults.flags.betterAuth) cliResults.packages.push("betterAuth");
202212
if (cliResults.flags.eslint) cliResults.packages.push("eslint");
203213
if (cliResults.flags.biome) cliResults.packages.push("biome");
204214
if (cliResults.flags.prisma && cliResults.flags.drizzle) {
@@ -212,6 +222,10 @@ export const runCli = async (): Promise<CliResults> => {
212222
logger.warn("Incompatible combination Biome + ESLint. Exiting.");
213223
process.exit(0);
214224
}
225+
if (cliResults.flags.nextAuth && cliResults.flags.betterAuth) {
226+
logger.warn("Incompatible combination NextAuth + BetterAuth. Exiting.");
227+
process.exit(0);
228+
}
215229
if (databaseProviders.includes(cliResults.flags.dbProvider) === false) {
216230
logger.warn(
217231
`Incompatible database provided. Use: ${databaseProviders.join(", ")}. Exiting.`
@@ -286,6 +300,7 @@ export const runCli = async (): Promise<CliResults> => {
286300
options: [
287301
{ value: "none", label: "None" },
288302
{ value: "next-auth", label: "NextAuth.js" },
303+
{ value: "better-auth", label: "BetterAuth" },
289304
// Maybe later
290305
// { value: "clerk", label: "Clerk" },
291306
],
@@ -372,6 +387,7 @@ export const runCli = async (): Promise<CliResults> => {
372387
if (project.styling) packages.push("tailwind");
373388
if (project.trpc) packages.push("trpc");
374389
if (project.authentication === "next-auth") packages.push("nextAuth");
390+
if (project.authentication === "better-auth") packages.push("betterAuth");
375391
if (project.database === "prisma") packages.push("prisma");
376392
if (project.database === "drizzle") packages.push("drizzle");
377393
if (project.linter === "eslint") packages.push("eslint");

cli/src/helpers/selectBoilerplate.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,27 @@ export const selectAppFile = ({
1616

1717
const usingTw = packages.tailwind.inUse;
1818
const usingTRPC = packages.trpc.inUse;
19-
const usingNextAuth = packages.nextAuth.inUse;
19+
const usingAuth = packages?.nextAuth.inUse ?? packages?.betterAuth.inUse;
20+
const usingBetterAuth = packages?.betterAuth.inUse;
2021

2122
let appFile = "base.tsx";
22-
if (usingTRPC && usingTw && usingNextAuth) {
23+
if (usingTRPC && usingTw && usingBetterAuth) {
24+
appFile = "with-better-auth-trpc-tw.tsx";
25+
} else if (usingTRPC && !usingTw && usingBetterAuth) {
26+
appFile = "with-better-auth-trpc.tsx";
27+
} else if (usingTRPC && usingTw && usingAuth) {
2328
appFile = "with-auth-trpc-tw.tsx";
24-
} else if (usingTRPC && !usingTw && usingNextAuth) {
29+
} else if (usingTRPC && !usingTw && usingAuth) {
2530
appFile = "with-auth-trpc.tsx";
2631
} else if (usingTRPC && usingTw) {
2732
appFile = "with-trpc-tw.tsx";
2833
} else if (usingTRPC && !usingTw) {
2934
appFile = "with-trpc.tsx";
3035
} else if (!usingTRPC && usingTw) {
3136
appFile = "with-tw.tsx";
32-
} else if (usingNextAuth && usingTw) {
37+
} else if (usingAuth && usingTw) {
3338
appFile = "with-auth-tw.tsx";
34-
} else if (usingNextAuth && !usingTw) {
39+
} else if (usingAuth && !usingTw) {
3540
appFile = "with-auth.tsx";
3641
}
3742

@@ -72,10 +77,20 @@ export const selectIndexFile = ({
7277

7378
const usingTRPC = packages.trpc.inUse;
7479
const usingTw = packages.tailwind.inUse;
75-
const usingAuth = packages.nextAuth.inUse;
80+
const usingBetterAuth = packages?.betterAuth.inUse;
81+
const usingNextAuth = packages?.nextAuth.inUse;
82+
const usingAuth = usingNextAuth || usingBetterAuth;
7683

7784
let indexFile = "base.tsx";
78-
if (usingTRPC && usingTw && usingAuth) {
85+
if (usingTRPC && usingTw && usingBetterAuth) {
86+
indexFile = "with-better-auth-trpc-tw.tsx";
87+
} else if (usingTRPC && !usingTw && usingBetterAuth) {
88+
indexFile = "with-better-auth-trpc.tsx";
89+
} else if (!usingTRPC && usingTw && usingBetterAuth) {
90+
indexFile = "with-better-auth-tw.tsx";
91+
} else if (!usingTRPC && !usingTw && usingBetterAuth) {
92+
indexFile = "with-better-auth.tsx";
93+
} else if (usingTRPC && usingTw && usingAuth) {
7994
indexFile = "with-auth-trpc-tw.tsx";
8095
} else if (usingTRPC && !usingTw && usingAuth) {
8196
indexFile = "with-auth-trpc.tsx";
@@ -101,10 +116,19 @@ export const selectPageFile = ({
101116

102117
const usingTRPC = packages.trpc.inUse;
103118
const usingTw = packages.tailwind.inUse;
104-
const usingAuth = packages.nextAuth.inUse;
119+
const usingAuth = packages?.nextAuth.inUse;
120+
const usingBetterAuth = packages?.betterAuth.inUse;
105121

106122
let indexFile = "base.tsx";
107-
if (usingTRPC && usingTw && usingAuth) {
123+
if (usingTRPC && usingTw && usingBetterAuth) {
124+
indexFile = "with-better-auth-trpc-tw.tsx";
125+
} else if (usingTRPC && !usingTw && usingBetterAuth) {
126+
indexFile = "with-better-auth-trpc.tsx";
127+
} else if (!usingTRPC && usingTw && usingBetterAuth) {
128+
indexFile = "with-better-auth-tw.tsx";
129+
} else if (!usingTRPC && !usingTw && usingBetterAuth) {
130+
indexFile = "with-better-auth.tsx";
131+
} else if (usingTRPC && usingTw && usingAuth) {
108132
indexFile = "with-auth-trpc-tw.tsx";
109133
} else if (usingTRPC && !usingTw && usingAuth) {
110134
indexFile = "with-auth-trpc.tsx";

cli/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ const main = async () => {
8585
if (!noInstall) {
8686
await installDependencies({ projectDir });
8787

88+
if (usePackages.prisma.inUse) {
89+
logger.info("Generating Prisma client...");
90+
await execa("npx", ["prisma", "generate"], { cwd: projectDir });
91+
logger.info("Successfully generated Prisma client!");
92+
}
93+
8894
await formatProject({
8995
pkgManager,
9096
projectDir,

0 commit comments

Comments
 (0)