From 03d9559e55d7a322878a0146f62d34077663bf38 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Wed, 19 Mar 2025 13:33:51 +0530 Subject: [PATCH] Simplify auth setup, centralize environment variable management and fix create readme --- .changeset/flat-hotels-mate.md | 5 + apps/cli/src/helpers/auth-setup.ts | 139 ++++-------------- apps/cli/src/helpers/create-project.ts | 65 ++++---- apps/cli/src/helpers/create-readme.ts | 60 ++++---- apps/cli/src/helpers/db-setup.ts | 37 ----- apps/cli/src/helpers/env-setup.ts | 65 ++++++++ apps/cli/template/base/package.json | 4 +- .../base/packages/server/package.json | 5 - apps/cli/template/base/turbo.json | 4 +- .../packages/server/_env | 4 - .../with-drizzle-sqlite/packages/server/_env | 4 - 11 files changed, 170 insertions(+), 222 deletions(-) create mode 100644 .changeset/flat-hotels-mate.md create mode 100644 apps/cli/src/helpers/env-setup.ts delete mode 100644 apps/cli/template/with-drizzle-postgres/packages/server/_env delete mode 100644 apps/cli/template/with-drizzle-sqlite/packages/server/_env diff --git a/.changeset/flat-hotels-mate.md b/.changeset/flat-hotels-mate.md new file mode 100644 index 0000000..85efaed --- /dev/null +++ b/.changeset/flat-hotels-mate.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": minor +--- + +Simplify auth setup, centralize environment variable management and fix readme diff --git a/apps/cli/src/helpers/auth-setup.ts b/apps/cli/src/helpers/auth-setup.ts index db40e63..45eb1c4 100644 --- a/apps/cli/src/helpers/auth-setup.ts +++ b/apps/cli/src/helpers/auth-setup.ts @@ -1,116 +1,9 @@ import path from "node:path"; import { log } from "@clack/prompts"; -import fs from "fs-extra"; import pc from "picocolors"; -import type { ProjectConfig } from "../types"; import { addPackageDependency } from "../utils/add-package-deps"; -export async function setupAuth( - projectDir: string, - enableAuth: boolean, - options: ProjectConfig, -): Promise { - const serverDir = path.join(projectDir, "packages/server"); - const clientDir = path.join(projectDir, "packages/client"); - - try { - if (!enableAuth) { - return; - } - - addPackageDependency({ - dependencies: ["better-auth"], - devDependencies: false, - projectDir: serverDir, - }); - - const envPath = path.join(serverDir, ".env"); - - // Create or update the .env file directly with required variables - let envContent = ""; - - if (await fs.pathExists(envPath)) { - envContent = await fs.readFile(envPath, "utf8"); - } - - // Only add variables that don't already exist - if (!envContent.includes("BETTER_AUTH_SECRET")) { - envContent += `\nBETTER_AUTH_SECRET=${generateAuthSecret()}`; - } - - if (!envContent.includes("BETTER_AUTH_URL")) { - envContent += "\nBETTER_AUTH_URL=http://localhost:3000"; - } - - if (!envContent.includes("CORS_ORIGIN")) { - envContent += "\nCORS_ORIGIN=http://localhost:3001"; - } - - if ( - options.database === "sqlite" && - !envContent.includes("TURSO_CONNECTION_URL") - ) { - envContent += "\nTURSO_CONNECTION_URL=http://127.0.0.1:8080"; - } - - if (options.orm === "prisma" && !envContent.includes("DATABASE_URL")) { - if (options.database === "sqlite") { - envContent += '\nDATABASE_URL="file:./dev.db"'; - } else if (options.database === "postgres") { - envContent += - '\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"'; - } - } - - // Write the updated content - await fs.writeFile(envPath, envContent.trim()); - - // Create client .env file if it doesn't exist - const clientEnvPath = path.join(clientDir, ".env"); - if (!(await fs.pathExists(clientEnvPath))) { - const clientEnvContent = "VITE_SERVER_URL=http://localhost:3000\n"; - await fs.writeFile(clientEnvPath, clientEnvContent); - } - - if (options.orm === "prisma") { - const packageJsonPath = path.join(projectDir, "package.json"); - if (await fs.pathExists(packageJsonPath)) { - const packageJson = await fs.readJson(packageJsonPath); - - packageJson.scripts["prisma:generate"] = - "cd packages/server && npx prisma generate"; - packageJson.scripts["prisma:push"] = - "cd packages/server && npx prisma db push"; - packageJson.scripts["prisma:studio"] = - "cd packages/server && npx prisma studio"; - packageJson.scripts["db:setup"] = - "npm run auth:generate && npm run prisma:generate && npm run prisma:push"; - - await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); - } - } else if (options.orm === "drizzle") { - const packageJsonPath = path.join(projectDir, "package.json"); - if (await fs.pathExists(packageJsonPath)) { - const packageJson = await fs.readJson(packageJsonPath); - - packageJson.scripts["db:push"] = - "cd packages/server && npx @better-auth/cli migrate"; - packageJson.scripts["db:setup"] = - "npm run auth:generate && npm run db:push"; - - await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); - } - } - } catch (error) { - log.error(pc.red("Failed to configure authentication")); - if (error instanceof Error) { - log.error(pc.red(error.message)); - } - throw error; - } -} - -function generateAuthSecret(length = 32): string { +export function generateAuthSecret(length = 32): string { const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; @@ -120,3 +13,33 @@ function generateAuthSecret(length = 32): string { } return result; } + +export async function setupAuth( + projectDir: string, + enableAuth: boolean, +): Promise { + const serverDir = path.join(projectDir, "packages/server"); + const clientDir = path.join(projectDir, "packages/client"); + + try { + if (!enableAuth) { + return; + } + addPackageDependency({ + dependencies: ["better-auth"], + devDependencies: false, + projectDir: serverDir, + }); + addPackageDependency({ + dependencies: ["better-auth"], + devDependencies: false, + projectDir: clientDir, + }); + } catch (error) { + log.error(pc.red("Failed to configure authentication")); + if (error instanceof Error) { + log.error(pc.red(error.message)); + } + throw error; + } +} diff --git a/apps/cli/src/helpers/create-project.ts b/apps/cli/src/helpers/create-project.ts index bf97e4a..71bbff1 100644 --- a/apps/cli/src/helpers/create-project.ts +++ b/apps/cli/src/helpers/create-project.ts @@ -9,6 +9,7 @@ import { setupAddons } from "./addons-setup"; import { setupAuth } from "./auth-setup"; import { createReadme } from "./create-readme"; import { setupDatabase } from "./db-setup"; +import { setupEnvironmentVariables } from "./env-setup"; import { displayPostInstallInstructions } from "./post-installation"; export async function createProject(options: ProjectConfig): Promise { @@ -49,7 +50,9 @@ export async function createProject(options: ProjectConfig): Promise { options.turso ?? options.database === "sqlite", ); - await setupAuth(projectDir, options.auth, options); + await setupAuth(projectDir, options.auth); + + await setupEnvironmentVariables(projectDir, options); if (options.git) { await $({ cwd: projectDir })`git init`; @@ -59,51 +62,63 @@ export async function createProject(options: ProjectConfig): Promise { await setupAddons(projectDir, options.addons); } - const packageJsonPath = path.join(projectDir, "package.json"); - if (await fs.pathExists(packageJsonPath)) { - const packageJson = await fs.readJson(packageJsonPath); + const rootPackageJsonPath = path.join(projectDir, "package.json"); + if (await fs.pathExists(rootPackageJsonPath)) { + const packageJson = await fs.readJson(rootPackageJsonPath); packageJson.name = options.projectName; if (options.packageManager !== "bun") { packageJson.packageManager = options.packageManager === "npm" - ? "npm@10.2.4" + ? "npm@10.9.2" : options.packageManager === "pnpm" - ? "pnpm@8.15.4" + ? "pnpm@10.6.4" : options.packageManager === "yarn" ? "yarn@4.1.0" - : "bun@1.2.4"; + : "bun@1.2.5"; } + await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 }); + } + + const serverPackageJsonPath = path.join( + projectDir, + "packages/server/package.json", + ); + if (await fs.pathExists(serverPackageJsonPath)) { + const serverPackageJson = await fs.readJson(serverPackageJsonPath); + if (options.database !== "none") { - if (options.database === "sqlite") { - packageJson.scripts["db:local"] = - "cd packages/server && turso dev --db-file local.db"; + if (options.database === "sqlite" && options.turso) { + serverPackageJson.scripts["db:local"] = + "turso dev --db-file local.db"; } if (options.auth) { - packageJson.scripts["auth:generate"] = - "cd packages/server && npx @better-auth/cli generate --output ./src/db/auth-schema.ts"; + serverPackageJson.scripts["auth:generate"] = + "npx @better-auth/cli generate --output ./src/db/auth-schema.ts"; if (options.orm === "prisma") { - packageJson.scripts["prisma:generate"] = - "cd packages/server && npx prisma generate"; - packageJson.scripts["prisma:push"] = - "cd packages/server && npx prisma db push"; - packageJson.scripts["prisma:studio"] = - "cd packages/server && npx prisma studio"; - packageJson.scripts["db:setup"] = - "npm run auth:generate && npm run prisma:generate && npm run prisma:push"; + serverPackageJson.scripts["db:push"] = "npx prisma db push"; + serverPackageJson.scripts["db:studio"] = "npx prisma studio"; } else if (options.orm === "drizzle") { - packageJson.scripts["drizzle:migrate"] = - "cd packages/server && npx @better-auth/cli migrate"; - packageJson.scripts["db:setup"] = - "npm run auth:generate && npm run drizzle:migrate"; + serverPackageJson.scripts["db:push"] = "npx drizzle-kit push"; + serverPackageJson.scripts["db:studio"] = "npx drizzle-kit studio"; + } + } else { + if (options.orm === "prisma") { + serverPackageJson.scripts["db:push"] = "npx prisma db push"; + serverPackageJson.scripts["db:studio"] = "npx prisma studio"; + } else if (options.orm === "drizzle") { + serverPackageJson.scripts["db:push"] = "npx drizzle-kit push"; + serverPackageJson.scripts["db:studio"] = "npx drizzle-kit studio"; } } } - await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); + await fs.writeJson(serverPackageJsonPath, serverPackageJson, { + spaces: 2, + }); } await createReadme(projectDir, options); diff --git a/apps/cli/src/helpers/create-readme.ts b/apps/cli/src/helpers/create-readme.ts index 01a42a8..ec9f4e1 100644 --- a/apps/cli/src/helpers/create-readme.ts +++ b/apps/cli/src/helpers/create-readme.ts @@ -126,10 +126,10 @@ function generateDatabaseSetup( 1. Start the local SQLite database: \`\`\`bash -${packageManagerRunCmd} db:local +cd packages/server && ${packageManagerRunCmd} db:local \`\`\` -2. Update your \`.env\` file with the appropriate connection details if needed. +2. Update your \`.env\` file in the \`packages/server\` directory with the appropriate connection details if needed. `; } else if (database === "postgres") { setup += `This project uses PostgreSQL${orm === "drizzle" ? " with Drizzle ORM" : " with Prisma"}. @@ -143,24 +143,25 @@ ${packageManagerRunCmd} db:local setup += ` 3. Generate the authentication schema: \`\`\`bash -${packageManagerRunCmd} auth:generate +cd packages/server && ${packageManagerRunCmd} auth:generate \`\`\` - -4. ${ - orm === "prisma" - ? `Generate the Prisma client and push the schema: -\`\`\`bash -${packageManagerRunCmd} prisma:generate -${packageManagerRunCmd} db:push -\`\`\`` - : `Apply the Drizzle migrations: -\`\`\`bash -${packageManagerRunCmd} db:push -\`\`\`` - } `; } + setup += ` +${auth ? "4" : "3"}. ${ + orm === "prisma" + ? `Generate the Prisma client and push the schema: +\`\`\`bash +${packageManagerRunCmd} db:push +\`\`\`` + : `Apply the schema to your database: +\`\`\`bash +${packageManagerRunCmd} db:push +\`\`\`` + } +`; + return setup; } @@ -173,32 +174,21 @@ function generateScriptsList( let scripts = `- \`${packageManagerRunCmd} dev\`: Start both client and server in development mode - \`${packageManagerRunCmd} build\`: Build both client and server - \`${packageManagerRunCmd} dev:client\`: Start only the client -- \`${packageManagerRunCmd} dev:server\`: Start only the server`; +- \`${packageManagerRunCmd} dev:server\`: Start only the server +- \`${packageManagerRunCmd} check-types\`: Check TypeScript types across all packages`; if (database !== "none") { - if (database === "sqlite") { - scripts += `\n- \`${packageManagerRunCmd} db:local\`: Start the local SQLite database`; - } + scripts += ` +- \`${packageManagerRunCmd} db:push\`: Push schema changes to database +- \`${packageManagerRunCmd} db:studio\`: Open database studio UI`; - if (orm === "prisma") { - scripts += ` -- \`${packageManagerRunCmd} prisma:generate\`: Generate Prisma client -- \`${packageManagerRunCmd} db:push\`: Push schema changes to database -- \`${packageManagerRunCmd} prisma:studio\`: Open Prisma Studio`; - } else if (orm === "drizzle") { - scripts += ` -- \`${packageManagerRunCmd} db:generate\`: Generate database schema -- \`${packageManagerRunCmd} db:push\`: Push schema changes to database -- \`${packageManagerRunCmd} db:studio\`: Open Drizzle Studio`; + if (database === "sqlite" && orm === "drizzle") { + scripts += `\n- \`cd packages/server && ${packageManagerRunCmd} db:local\`: Start the local SQLite database`; } } if (auth) { - scripts += `\n- \`${packageManagerRunCmd} auth:generate\`: Generate authentication schema`; - } - - if (auth && database !== "none") { - scripts += `\n- \`${packageManagerRunCmd} db:setup\`: Complete database setup for auth`; + scripts += `\n- \`cd packages/server && ${packageManagerRunCmd} auth:generate\`: Generate authentication schema`; } return scripts; diff --git a/apps/cli/src/helpers/db-setup.ts b/apps/cli/src/helpers/db-setup.ts index de6269e..83f8f22 100644 --- a/apps/cli/src/helpers/db-setup.ts +++ b/apps/cli/src/helpers/db-setup.ts @@ -74,43 +74,6 @@ export async function setupDatabase( }); } } - - const packageJsonPath = path.join(serverDir, "package.json"); - if (await fs.pathExists(packageJsonPath)) { - const packageJson = await fs.readJSON(packageJsonPath); - - if (orm === "drizzle") { - packageJson.scripts = { - ...packageJson.scripts, - "db:generate": "drizzle-kit generate", - "db:migrate": "drizzle-kit push", - "db:studio": "drizzle-kit studio", - }; - } else if (orm === "prisma") { - packageJson.scripts = { - ...packageJson.scripts, - "prisma:generate": "prisma generate", - "prisma:push": "prisma db push", - "prisma:studio": "prisma studio", - }; - } - - await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 }); - } - - if (orm === "prisma") { - const envPath = path.join(serverDir, ".env"); - if (await fs.pathExists(envPath)) { - const envContent = await fs.readFile(envPath, "utf8"); - if (!envContent.includes("DATABASE_URL")) { - const databaseUrlLine = - databaseType === "sqlite" - ? `\nDATABASE_URL="file:./dev.db"` - : `\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`; - await fs.appendFile(envPath, databaseUrlLine); - } - } - } } catch (error) { s.stop(pc.red("Failed to set up database")); if (error instanceof Error) { diff --git a/apps/cli/src/helpers/env-setup.ts b/apps/cli/src/helpers/env-setup.ts new file mode 100644 index 0000000..1bef3ca --- /dev/null +++ b/apps/cli/src/helpers/env-setup.ts @@ -0,0 +1,65 @@ +import path from "node:path"; +import fs from "fs-extra"; +import type { ProjectConfig } from "../types"; +import { generateAuthSecret } from "./auth-setup"; + +export async function setupEnvironmentVariables( + projectDir: string, + options: ProjectConfig, +): Promise { + const serverDir = path.join(projectDir, "packages/server"); + const clientDir = path.join(projectDir, "packages/client"); + + // Set up server env variables + const envPath = path.join(serverDir, ".env"); + let envContent = ""; + + if (await fs.pathExists(envPath)) { + envContent = await fs.readFile(envPath, "utf8"); + } + + // Auth environment variables + if (options.auth) { + if (!envContent.includes("BETTER_AUTH_SECRET")) { + envContent += `\nBETTER_AUTH_SECRET=${generateAuthSecret()}`; + } + + if (!envContent.includes("BETTER_AUTH_URL")) { + envContent += "\nBETTER_AUTH_URL=http://localhost:3000"; + } + + if (!envContent.includes("CORS_ORIGIN")) { + envContent += "\nCORS_ORIGIN=http://localhost:3001"; + } + } + + // Database environment variables + if (options.database !== "none") { + if (options.orm === "prisma" && !envContent.includes("DATABASE_URL")) { + const databaseUrlLine = + options.database === "sqlite" + ? `\nDATABASE_URL="file:./dev.db"` + : `\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`; + envContent += databaseUrlLine; + } + + if ( + options.database === "sqlite" && + options.turso && + !envContent.includes("TURSO_CONNECTION_URL") + ) { + envContent += "\nTURSO_CONNECTION_URL=http://127.0.0.1:8080"; + } + } + + await fs.writeFile(envPath, envContent.trim()); + + // Set up client env variables + if (options.auth) { + const clientEnvPath = path.join(clientDir, ".env"); + if (!(await fs.pathExists(clientEnvPath))) { + const clientEnvContent = "VITE_SERVER_URL=http://localhost:3000\n"; + await fs.writeFile(clientEnvPath, clientEnvContent); + } + } +} diff --git a/apps/cli/template/base/package.json b/apps/cli/template/base/package.json index 8c45085..aa0c067 100644 --- a/apps/cli/template/base/package.json +++ b/apps/cli/template/base/package.json @@ -10,8 +10,8 @@ "check-types": "turbo check-types", "dev:client": "turbo -F @better-t/client dev", "dev:server": "turbo -F @better-t/server dev", - "db:local": "turbo -F @better-t/server db:local", - "db:push": "turbo -F @better-t/server db:push" + "db:push": "turbo -F @better-t/server db:push", + "db:studio": "turbo -F @better-t/server db:studio" }, "packageManager": "bun@1.2.4", "devDependencies": { diff --git a/apps/cli/template/base/packages/server/package.json b/apps/cli/template/base/packages/server/package.json index 68bb9c5..8c67dd8 100644 --- a/apps/cli/template/base/packages/server/package.json +++ b/apps/cli/template/base/packages/server/package.json @@ -6,12 +6,7 @@ "dev": "tsx watch src/index.ts", "build": "tsc", "dev:bun": "bun run --hot src/index.ts", - "db:local": "turso dev --db-file local.db", - "db:push": "drizzle-kit push", - "db:studio": "drizzle-kit studio", "check-types": "tsc --noEmit", - "wrangler:dev": "wrangler dev", - "wrangler:deploy": "wrangler deploy --minify", "compile": "bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server" }, "dependencies": { diff --git a/apps/cli/template/base/turbo.json b/apps/cli/template/base/turbo.json index 67bf60f..36ac4c7 100644 --- a/apps/cli/template/base/turbo.json +++ b/apps/cli/template/base/turbo.json @@ -17,10 +17,10 @@ "cache": false, "persistent": true }, - "db:local": { + "db:push": { "cache": false }, - "db:push": { + "db:studio": { "cache": false } } diff --git a/apps/cli/template/with-drizzle-postgres/packages/server/_env b/apps/cli/template/with-drizzle-postgres/packages/server/_env deleted file mode 100644 index aa66a25..0000000 --- a/apps/cli/template/with-drizzle-postgres/packages/server/_env +++ /dev/null @@ -1,4 +0,0 @@ -BETTER_AUTH_SECRET=jdUstNuiIZLVh897KOMMS8EmTP0QkD32 -BETTER_AUTH_URL=http://localhost:3000 -TURSO_CONNECTION_URL=http://127.0.0.1:8080 -CORS_ORIGIN=http://localhost:3001 diff --git a/apps/cli/template/with-drizzle-sqlite/packages/server/_env b/apps/cli/template/with-drizzle-sqlite/packages/server/_env deleted file mode 100644 index aa66a25..0000000 --- a/apps/cli/template/with-drizzle-sqlite/packages/server/_env +++ /dev/null @@ -1,4 +0,0 @@ -BETTER_AUTH_SECRET=jdUstNuiIZLVh897KOMMS8EmTP0QkD32 -BETTER_AUTH_URL=http://localhost:3000 -TURSO_CONNECTION_URL=http://127.0.0.1:8080 -CORS_ORIGIN=http://localhost:3001