mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
Simplify auth setup, centralize environment variable management and fix
create readme
This commit is contained in:
5
.changeset/flat-hotels-mate.md
Normal file
5
.changeset/flat-hotels-mate.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-better-t-stack": minor
|
||||
---
|
||||
|
||||
Simplify auth setup, centralize environment variable management and fix readme
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> {
|
||||
@@ -49,7 +50,9 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
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<string> {
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
65
apps/cli/src/helpers/env-setup.ts
Normal file
65
apps/cli/src/helpers/env-setup.ts
Normal file
@@ -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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"db:local": {
|
||||
"db:push": {
|
||||
"cache": false
|
||||
},
|
||||
"db:push": {
|
||||
"db:studio": {
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user