diff --git a/.changeset/fifty-rocks-try.md b/.changeset/fifty-rocks-try.md new file mode 100644 index 0000000..12b22fa --- /dev/null +++ b/.changeset/fifty-rocks-try.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": patch +--- + +Add postgres support diff --git a/apps/cli/src/helpers/auth-setup.ts b/apps/cli/src/helpers/auth-setup.ts index 28d8566..445a39a 100644 --- a/apps/cli/src/helpers/auth-setup.ts +++ b/apps/cli/src/helpers/auth-setup.ts @@ -52,9 +52,11 @@ export async function configureAuth( const envPath = path.join(serverDir, ".env"); const templateEnvPath = path.join( PKG_ROOT, - options.orm === "drizzle" - ? "template/with-drizzle/packages/server/_env" - : "template/base/packages/server/_env", + getOrmTemplatePath( + options.orm, + options.database, + "packages/server/_env", + ), ); if (!(await fs.pathExists(envPath))) { @@ -108,7 +110,11 @@ ${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""} const prismaAuthPath = path.join(serverDir, "src/lib/auth.ts"); const defaultPrismaAuthPath = path.join( PKG_ROOT, - "template/with-prisma/packages/server/src/lib/auth.ts", + getOrmTemplatePath( + options.orm, + options.database, + "packages/server/src/lib/auth.ts", + ), ); if ( @@ -147,7 +153,11 @@ ${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""} const drizzleAuthPath = path.join(serverDir, "src/lib/auth.ts"); const defaultDrizzleAuthPath = path.join( PKG_ROOT, - "template/with-drizzle/packages/server/src/lib/auth.ts", + getOrmTemplatePath( + options.orm, + options.database, + "packages/server/src/lib/auth.ts", + ), ); if ( @@ -180,6 +190,24 @@ ${options.orm === "prisma" ? 'DATABASE_URL="file:./dev.db"' : ""} } } +function getOrmTemplatePath( + orm: string, + database: string, + relativePath: string, +): string { + if (orm === "drizzle") { + return database === "sqlite" + ? `template/with-drizzle-sqlite/${relativePath}` + : `template/with-drizzle-postgres/${relativePath}`; + } + if (orm === "prisma") { + return database === "sqlite" + ? `template/with-prisma-sqlite/${relativePath}` + : `template/with-prisma-postgres/${relativePath}`; + } + return `template/base/${relativePath}`; +} + function generateAuthSecret(length = 32): string { const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; diff --git a/apps/cli/src/helpers/create-project.ts b/apps/cli/src/helpers/create-project.ts index 9a08569..70c7294 100644 --- a/apps/cli/src/helpers/create-project.ts +++ b/apps/cli/src/helpers/create-project.ts @@ -27,9 +27,7 @@ export async function createProject(options: ProjectConfig): Promise { if (options.orm !== "none" && options.database !== "none") { const ormTemplateDir = path.join( PKG_ROOT, - options.orm === "drizzle" - ? "template/with-drizzle" - : "template/with-prisma", + getOrmTemplateDir(options.orm, options.database), ); if (await fs.pathExists(ormTemplateDir)) { @@ -143,3 +141,19 @@ export async function createProject(options: ProjectConfig): Promise { throw error; } } + +function getOrmTemplateDir(orm: string, database: string): string { + if (orm === "drizzle") { + return database === "sqlite" + ? "template/with-drizzle-sqlite" + : "template/with-drizzle-postgres"; + } + + if (orm === "prisma") { + return database === "sqlite" + ? "template/with-prisma-sqlite" + : "template/with-prisma-postgres"; + } + + return "template/base"; +} diff --git a/apps/cli/src/helpers/db-setup.ts b/apps/cli/src/helpers/db-setup.ts index e3e27a8..679cbb8 100644 --- a/apps/cli/src/helpers/db-setup.ts +++ b/apps/cli/src/helpers/db-setup.ts @@ -21,24 +21,21 @@ export async function setupDatabase( try { if (databaseType === "sqlite") { if (orm === "drizzle") { - await setupDrizzleDependencies(projectDir); - await setupTurso(projectDir, setupTursoDb); + await setupDrizzleDependencies(projectDir, "sqlite"); + if (setupTursoDb) { + await setupTurso(projectDir, true); + } } else if (orm === "prisma") { - await setupPrismaDependencies(projectDir); - await setupTurso(projectDir, setupTursoDb); + await setupPrismaDependencies(projectDir, "sqlite"); + if (setupTursoDb) { + await setupTurso(projectDir, true); + } } } else if (databaseType === "postgres") { - log.info( - pc.blue( - "PostgreSQL setup is coming in a future update. Using SQLite configuration for now.", - ), - ); if (orm === "drizzle") { - await setupDrizzleDependencies(projectDir); - await setupTurso(projectDir, setupTursoDb); + await setupDrizzleDependencies(projectDir, "postgres"); } else if (orm === "prisma") { - await setupPrismaDependencies(projectDir); - await setupTurso(projectDir, setupTursoDb); + await setupPrismaDependencies(projectDir, "postgres"); } } } catch (error) { @@ -50,7 +47,10 @@ export async function setupDatabase( } } -async function setupDrizzleDependencies(projectDir: string): Promise { +async function setupDrizzleDependencies( + projectDir: string, + dbType: string, +): Promise { const serverDir = path.join(projectDir, "packages/server"); const packageJsonPath = path.join(serverDir, "package.json"); @@ -60,9 +60,14 @@ async function setupDrizzleDependencies(projectDir: string): Promise { packageJson.dependencies = { ...packageJson.dependencies, "drizzle-orm": "^0.38.4", - "@libsql/client": "^0.14.0", }; + if (dbType === "sqlite") { + packageJson.dependencies["@libsql/client"] = "^0.14.0"; + } else if (dbType === "postgres") { + packageJson.dependencies.postgres = "^3.4.5"; + } + packageJson.devDependencies = { ...packageJson.devDependencies, "drizzle-kit": "^0.30.4", @@ -79,7 +84,10 @@ async function setupDrizzleDependencies(projectDir: string): Promise { } } -async function setupPrismaDependencies(projectDir: string): Promise { +async function setupPrismaDependencies( + projectDir: string, + dbType: string, +): Promise { const serverDir = path.join(projectDir, "packages/server"); const packageJsonPath = path.join(serverDir, "package.json"); @@ -89,10 +97,15 @@ async function setupPrismaDependencies(projectDir: string): Promise { packageJson.dependencies = { ...packageJson.dependencies, "@prisma/client": "^5.7.1", - "@prisma/adapter-libsql": "^5.7.1", - "@libsql/client": "^0.14.0", }; + if (dbType === "sqlite") { + packageJson.dependencies["@prisma/adapter-libsql"] = "^5.7.1"; + packageJson.dependencies["@libsql/client"] = "^0.14.0"; + } else if (dbType === "postgres") { + // PostgreSQL specific dependencies if needed + } + packageJson.devDependencies = { ...packageJson.devDependencies, prisma: "^5.7.1", @@ -112,7 +125,10 @@ async function setupPrismaDependencies(projectDir: string): Promise { if (await fs.pathExists(envPath)) { const envContent = await fs.readFile(envPath, "utf8"); if (!envContent.includes("DATABASE_URL")) { - const databaseUrlLine = `\nDATABASE_URL="file:./dev.db"`; + const databaseUrlLine = + dbType === "sqlite" + ? `\nDATABASE_URL="file:./dev.db"` + : `\nDATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`; await fs.appendFile(envPath, databaseUrlLine); } } diff --git a/apps/cli/template/with-drizzle/packages/server/_env b/apps/cli/template/with-drizzle-postgres/packages/server/_env similarity index 100% rename from apps/cli/template/with-drizzle/packages/server/_env rename to apps/cli/template/with-drizzle-postgres/packages/server/_env diff --git a/apps/cli/template/with-drizzle-postgres/packages/server/drizzle.config.ts b/apps/cli/template/with-drizzle-postgres/packages/server/drizzle.config.ts new file mode 100644 index 0000000..50fb7f6 --- /dev/null +++ b/apps/cli/template/with-drizzle-postgres/packages/server/drizzle.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + schema: "./src/db/schema.ts", + out: "./migrations", + dialect: "postgresql", + dbCredentials: { + url: process.env.POSTGRES_URL!, + }, +}); diff --git a/apps/cli/template/with-drizzle-postgres/packages/server/src/db/index.ts b/apps/cli/template/with-drizzle-postgres/packages/server/src/db/index.ts new file mode 100644 index 0000000..c1f6717 --- /dev/null +++ b/apps/cli/template/with-drizzle-postgres/packages/server/src/db/index.ts @@ -0,0 +1,5 @@ +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; + +const queryClient = postgres(process.env.DATABASE_URL); +const db = drizzle({ client: queryClient }); diff --git a/apps/cli/template/with-drizzle-postgres/packages/server/src/db/schema.ts b/apps/cli/template/with-drizzle-postgres/packages/server/src/db/schema.ts new file mode 100644 index 0000000..65af47c --- /dev/null +++ b/apps/cli/template/with-drizzle-postgres/packages/server/src/db/schema.ts @@ -0,0 +1,47 @@ +import { pgTable, text, integer, timestamp, boolean } from "drizzle-orm/pg-core"; + +export const user = pgTable("user", { + id: text("id").primaryKey(), + name: text('name').notNull(), + email: text('email').notNull().unique(), + emailVerified: boolean('email_verified').notNull(), + image: text('image'), + createdAt: timestamp('created_at').notNull(), + updatedAt: timestamp('updated_at').notNull() + }); + +export const session = pgTable("session", { + id: text("id").primaryKey(), + expiresAt: timestamp('expires_at').notNull(), + token: text('token').notNull().unique(), + createdAt: timestamp('created_at').notNull(), + updatedAt: timestamp('updated_at').notNull(), + ipAddress: text('ip_address'), + userAgent: text('user_agent'), + userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' }) + }); + +export const account = pgTable("account", { + id: text("id").primaryKey(), + accountId: text('account_id').notNull(), + providerId: text('provider_id').notNull(), + userId: text('user_id').notNull().references(()=> user.id, { onDelete: 'cascade' }), + accessToken: text('access_token'), + refreshToken: text('refresh_token'), + idToken: text('id_token'), + accessTokenExpiresAt: timestamp('access_token_expires_at'), + refreshTokenExpiresAt: timestamp('refresh_token_expires_at'), + scope: text('scope'), + password: text('password'), + createdAt: timestamp('created_at').notNull(), + updatedAt: timestamp('updated_at').notNull() + }); + +export const verification = pgTable("verification", { + id: text("id").primaryKey(), + identifier: text('identifier').notNull(), + value: text('value').notNull(), + expiresAt: timestamp('expires_at').notNull(), + createdAt: timestamp('created_at'), + updatedAt: timestamp('updated_at') + }); diff --git a/apps/cli/template/with-drizzle/packages/server/src/lib/auth.ts b/apps/cli/template/with-drizzle-postgres/packages/server/src/lib/auth.ts similarity index 100% rename from apps/cli/template/with-drizzle/packages/server/src/lib/auth.ts rename to apps/cli/template/with-drizzle-postgres/packages/server/src/lib/auth.ts diff --git a/apps/cli/template/with-drizzle-sqlite/packages/server/_env b/apps/cli/template/with-drizzle-sqlite/packages/server/_env new file mode 100644 index 0000000..aa66a25 --- /dev/null +++ b/apps/cli/template/with-drizzle-sqlite/packages/server/_env @@ -0,0 +1,4 @@ +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/packages/server/drizzle.config.ts b/apps/cli/template/with-drizzle-sqlite/packages/server/drizzle.config.ts similarity index 100% rename from apps/cli/template/with-drizzle/packages/server/drizzle.config.ts rename to apps/cli/template/with-drizzle-sqlite/packages/server/drizzle.config.ts diff --git a/apps/cli/template/with-drizzle/packages/server/src/db/index.ts b/apps/cli/template/with-drizzle-sqlite/packages/server/src/db/index.ts similarity index 100% rename from apps/cli/template/with-drizzle/packages/server/src/db/index.ts rename to apps/cli/template/with-drizzle-sqlite/packages/server/src/db/index.ts diff --git a/apps/cli/template/with-drizzle/packages/server/src/db/schema.ts b/apps/cli/template/with-drizzle-sqlite/packages/server/src/db/schema.ts similarity index 100% rename from apps/cli/template/with-drizzle/packages/server/src/db/schema.ts rename to apps/cli/template/with-drizzle-sqlite/packages/server/src/db/schema.ts diff --git a/apps/cli/template/with-drizzle-sqlite/packages/server/src/lib/auth.ts b/apps/cli/template/with-drizzle-sqlite/packages/server/src/lib/auth.ts new file mode 100644 index 0000000..4531d67 --- /dev/null +++ b/apps/cli/template/with-drizzle-sqlite/packages/server/src/lib/auth.ts @@ -0,0 +1,15 @@ +import { betterAuth } from "better-auth"; +import { drizzleAdapter } from "better-auth/adapters/drizzle"; +import { db } from "../db"; +import * as schema from "../db/schema"; + +export const auth = betterAuth({ + database: drizzleAdapter(db, { + provider: "sqlite", + schema: schema, + }), + trustedOrigins: [process.env.CORS_ORIGIN!], + emailAndPassword: { + enabled: true, + }, +}); diff --git a/apps/cli/template/with-prisma-postgres/packages/server/src/db/index.ts b/apps/cli/template/with-prisma-postgres/packages/server/src/db/index.ts new file mode 100644 index 0000000..34ab1b5 --- /dev/null +++ b/apps/cli/template/with-prisma-postgres/packages/server/src/db/index.ts @@ -0,0 +1,5 @@ +import { PrismaClient } from "@prisma/client"; + +let prisma = new PrismaClient(); + +export default prisma; diff --git a/apps/cli/template/with-prisma-postgres/packages/server/src/db/schema.prisma b/apps/cli/template/with-prisma-postgres/packages/server/src/db/schema.prisma new file mode 100644 index 0000000..05383ce --- /dev/null +++ b/apps/cli/template/with-prisma-postgres/packages/server/src/db/schema.prisma @@ -0,0 +1,74 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgres" + url = env("DATABASE_URL") +} + +model User { + id String @id @map("_id") + name String + email String + emailVerified Boolean + image String? + createdAt DateTime + updatedAt DateTime + sessions Session[] + accounts Account[] + + @@unique([email]) + @@map("user") +} + +model Session { + id String @id @map("_id") + expiresAt DateTime + token String + createdAt DateTime + updatedAt DateTime + ipAddress String? + userAgent String? + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([token]) + @@map("session") +} + +model Account { + id String @id @map("_id") + accountId String + providerId String + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + accessToken String? + refreshToken String? + idToken String? + accessTokenExpiresAt DateTime? + refreshTokenExpiresAt DateTime? + scope String? + password String? + createdAt DateTime + updatedAt DateTime + + @@map("account") +} + +model Verification { + id String @id @map("_id") + identifier String + value String + expiresAt DateTime + createdAt DateTime? + updatedAt DateTime? + + @@map("verification") +} diff --git a/apps/cli/template/with-prisma-postgres/packages/server/src/lib/auth.ts b/apps/cli/template/with-prisma-postgres/packages/server/src/lib/auth.ts new file mode 100644 index 0000000..beb9311 --- /dev/null +++ b/apps/cli/template/with-prisma-postgres/packages/server/src/lib/auth.ts @@ -0,0 +1,9 @@ +import { betterAuth } from "better-auth"; +import { prismaAdapter } from "better-auth/adapters/prisma"; +import prisma from "../db"; + +export const auth = betterAuth({ + database: prismaAdapter(prisma, { + provider: "pg", + }), +}); diff --git a/apps/cli/template/with-prisma/packages/server/src/db/index.ts b/apps/cli/template/with-prisma-sqlite/packages/server/src/db/index.ts similarity index 100% rename from apps/cli/template/with-prisma/packages/server/src/db/index.ts rename to apps/cli/template/with-prisma-sqlite/packages/server/src/db/index.ts diff --git a/apps/cli/template/with-prisma/packages/server/src/db/schema.prisma b/apps/cli/template/with-prisma-sqlite/packages/server/src/db/schema.prisma similarity index 100% rename from apps/cli/template/with-prisma/packages/server/src/db/schema.prisma rename to apps/cli/template/with-prisma-sqlite/packages/server/src/db/schema.prisma diff --git a/apps/cli/template/with-prisma/packages/server/src/lib/auth.ts b/apps/cli/template/with-prisma-sqlite/packages/server/src/lib/auth.ts similarity index 100% rename from apps/cli/template/with-prisma/packages/server/src/lib/auth.ts rename to apps/cli/template/with-prisma-sqlite/packages/server/src/lib/auth.ts diff --git a/bun.lock b/bun.lock index 2210340..0370b2a 100644 --- a/bun.lock +++ b/bun.lock @@ -7,14 +7,14 @@ "@biomejs/biome": "1.9.4", "@changesets/cli": "^2.28.1", "husky": "^9.1.7", - "lint-staged": "^15.4.3", + "lint-staged": "^15.5.0", "turbo": "^2.4.4", "typescript": "5.7.3", }, }, "apps/cli": { "name": "create-better-t-stack", - "version": "0.11.0", + "version": "0.11.1", "bin": { "create-better-t-stack": "dist/index.js", },