From 08119204b015359809fa66265cf6b755d7377874 Mon Sep 17 00:00:00 2001 From: Jose Toscano Date: Fri, 30 Aug 2024 23:02:37 -0600 Subject: [PATCH 1/8] Adds neon as a db provider with required template files --- cli/src/cli/index.ts | 18 ++++- cli/src/installers/dependencyVersionMap.ts | 1 + cli/src/installers/drizzle.ts | 16 ++-- cli/src/installers/index.ts | 2 + cli/src/installers/prisma.ts | 13 +++- .../extras/prisma/schema/base-neon.prisma | 21 ++++++ .../prisma/schema/with-auth-neon.prisma | 74 +++++++++++++++++++ .../extras/src/server/db/db-prisma-neon.ts | 28 +++++++ 8 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 cli/template/extras/prisma/schema/base-neon.prisma create mode 100644 cli/template/extras/prisma/schema/with-auth-neon.prisma create mode 100644 cli/template/extras/src/server/db/db-prisma-neon.ts diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index ce5ef6ca50..05238e0a50 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -44,6 +44,7 @@ interface CliResults { packages: AvailablePackages[]; flags: CliFlags; databaseProvider: DatabaseProvider; + drizzleDatabaseProvider: DatabaseProvider; } const defaultOptions: CliResults = { @@ -64,6 +65,7 @@ const defaultOptions: CliResults = { dbProvider: "sqlite", }, databaseProvider: "sqlite", + drizzleDatabaseProvider: "sqlite", }; export const runCli = async (): Promise => { @@ -296,6 +298,7 @@ export const runCli = async (): Promise => { { value: "mysql", label: "MySQL" }, { value: "postgres", label: "PostgreSQL" }, { value: "planetscale", label: "PlanetScale" }, + { value: "neon", label: "Neon" }, ], initialValue: "sqlite", }); @@ -342,11 +345,23 @@ export const runCli = async (): Promise => { if (project.database === "prisma") packages.push("prisma"); if (project.database === "drizzle") packages.push("drizzle"); + // Preserve the original databaseProvider for Prisma and other logic + const originalDatabaseProvider = project.databaseProvider; + + // Map Neon to postgres and Planetscale to mysql when Drizzle is selected + const drizzleDatabaseProvider = ( + originalDatabaseProvider === "neon" + ? "postgres" + : originalDatabaseProvider === "planetscale" + ? "mysql" + : originalDatabaseProvider + ) as DatabaseProvider; + return { appName: project.name ?? cliResults.appName, packages, databaseProvider: - (project.databaseProvider as DatabaseProvider) || "sqlite", + (originalDatabaseProvider as DatabaseProvider) || "sqlite", flags: { ...cliResults.flags, appRouter: project.appRouter ?? cliResults.flags.appRouter, @@ -354,6 +369,7 @@ export const runCli = async (): Promise => { noInstall: !project.install || cliResults.flags.noInstall, importAlias: project.importAlias ?? cliResults.flags.importAlias, }, + drizzleDatabaseProvider, // Pass this separately to the Drizzle installer }; } catch (err) { // If the user is not calling create-t3-app from an interactive terminal, inquirer will throw an IsTTYError diff --git a/cli/src/installers/dependencyVersionMap.ts b/cli/src/installers/dependencyVersionMap.ts index 062c9e3740..694f9e8b2b 100644 --- a/cli/src/installers/dependencyVersionMap.ts +++ b/cli/src/installers/dependencyVersionMap.ts @@ -21,6 +21,7 @@ export const dependencyVersionMap = { "@planetscale/database": "^1.19.0", postgres: "^3.4.4", "@libsql/client": "^0.9.0", + "@neondatabase/serverless": "^0.9.4", // TailwindCSS tailwindcss: "^3.4.3", diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index d9c5930b98..7d42f441bb 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -11,7 +11,7 @@ export const drizzleInstaller: Installer = ({ projectDir, packages, scopedAppName, - databaseProvider, + drizzleDatabaseProvider, }) => { const devPackages: AvailableDependencies[] = [ "drizzle-kit", @@ -31,10 +31,10 @@ export const drizzleInstaller: Installer = ({ { planetscale: "@planetscale/database", mysql: "mysql2", - postgres: "postgres", + postgres: "postgres", // This will be selected when drizzleDatabaseProvider is "postgres" sqlite: "@libsql/client", } as const - )[databaseProvider], + )[drizzleDatabaseProvider], ], devMode: false, }); @@ -43,9 +43,7 @@ export const drizzleInstaller: Installer = ({ const configFile = path.join( extrasDir, - `config/drizzle-config-${ - databaseProvider === "planetscale" ? "mysql" : databaseProvider - }.ts` + `config/drizzle-config-${drizzleDatabaseProvider}.ts` ); const configDest = path.join(projectDir, "drizzle.config.ts"); @@ -53,8 +51,8 @@ export const drizzleInstaller: Installer = ({ extrasDir, "src/server/db/schema-drizzle", packages?.nextAuth.inUse - ? `with-auth-${databaseProvider}.ts` - : `base-${databaseProvider}.ts` + ? `with-auth-${drizzleDatabaseProvider}.ts` + : `base-${drizzleDatabaseProvider}.ts` ); const schemaDest = path.join(projectDir, "src/server/db/schema.ts"); @@ -71,7 +69,7 @@ export const drizzleInstaller: Installer = ({ const clientSrc = path.join( extrasDir, - `src/server/db/index-drizzle/with-${databaseProvider}.ts` + `src/server/db/index-drizzle/with-${drizzleDatabaseProvider}.ts` ); const clientDest = path.join(projectDir, "src/server/db/index.ts"); diff --git a/cli/src/installers/index.ts b/cli/src/installers/index.ts index 776a16ba1c..08f70ed4fc 100644 --- a/cli/src/installers/index.ts +++ b/cli/src/installers/index.ts @@ -27,6 +27,7 @@ export const databaseProviders = [ "postgres", "sqlite", "planetscale", + "neon", ] as const; export type DatabaseProvider = (typeof databaseProviders)[number]; @@ -39,6 +40,7 @@ export interface InstallerOptions { projectName: string; scopedAppName: string; databaseProvider: DatabaseProvider; + drizzleDatabaseProvider: DatabaseProvider; } export type Installer = (opts: InstallerOptions) => void; diff --git a/cli/src/installers/prisma.ts b/cli/src/installers/prisma.ts index f92da12395..6db345fb10 100644 --- a/cli/src/installers/prisma.ts +++ b/cli/src/installers/prisma.ts @@ -28,13 +28,24 @@ export const prismaInstaller: Installer = ({ devMode: false, }); + if (databaseProvider === "neon") + addPackageDependency({ + projectDir, + dependencies: ["@neondatabase/serverless"], + devMode: false, + }); + const extrasDir = path.join(PKG_ROOT, "template/extras"); const schemaSrc = path.join( extrasDir, "prisma/schema", `${packages?.nextAuth.inUse ? "with-auth" : "base"}${ - databaseProvider === "planetscale" ? "-planetscale" : "" + databaseProvider === "planetscale" + ? "-planetscale" + : databaseProvider === "neon" + ? "-neon" + : "" }.prisma` ); let schemaText = fs.readFileSync(schemaSrc, "utf-8"); diff --git a/cli/template/extras/prisma/schema/base-neon.prisma b/cli/template/extras/prisma/schema/base-neon.prisma new file mode 100644 index 0000000000..87f3de8a61 --- /dev/null +++ b/cli/template/extras/prisma/schema/base-neon.prisma @@ -0,0 +1,21 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" + previewFeatures = ["driverAdapters"] +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Post { + id Int @id @default(autoincrement()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([name]) +} diff --git a/cli/template/extras/prisma/schema/with-auth-neon.prisma b/cli/template/extras/prisma/schema/with-auth-neon.prisma new file mode 100644 index 0000000000..5571e0ca37 --- /dev/null +++ b/cli/template/extras/prisma/schema/with-auth-neon.prisma @@ -0,0 +1,74 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" + previewFeatures = ["driverAdapters"] +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Post { + id Int @id @default(autoincrement()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + createdBy User @relation(fields: [createdById], references: [id]) + createdById String + + @@index([name]) + @@index([createdById]) +} + +// Necessary for NextAuth.js +model Account { + id String @id @default(cuid()) + userId String + type String + provider String + providerAccountId String + refresh_token String? @db.Text + access_token String? @db.Text + expires_at Int? + token_type String? + scope String? + id_token String? @db.Text + session_state String? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerAccountId]) + @@index([userId]) +} + +model Session { + id String @id @default(cuid()) + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId]) +} + +model User { + id String @id @default(cuid()) + name String? + email String? @unique + emailVerified DateTime? + image String? + accounts Account[] + sessions Session[] + posts Post[] +} + +model VerificationToken { + identifier String + token String @unique + expires DateTime + + @@unique([identifier, token]) +} diff --git a/cli/template/extras/src/server/db/db-prisma-neon.ts b/cli/template/extras/src/server/db/db-prisma-neon.ts new file mode 100644 index 0000000000..94e5e16235 --- /dev/null +++ b/cli/template/extras/src/server/db/db-prisma-neon.ts @@ -0,0 +1,28 @@ +import { neon } from "@neondatabase/serverless"; +import { PrismaNeonHTTP } from "@prisma/adapter-neon"; +import { PrismaClient } from "@prisma/client"; +import { env } from "~/env"; + +// Initialize Neon client using the DATABASE_URL from the environment variables +const sql = neon(env.DATABASE_URL); + +// Set up the Prisma adapter for Neon +const adapter = new PrismaNeonHTTP(sql); + +// Create a new Prisma client instance with the Neon adapter +const createPrismaClient = () => + new PrismaClient({ + log: + env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], + adapter, + }); + +// Global variable to store the Prisma client instance across module reloads +const globalForPrisma = globalThis as unknown as { + prisma: ReturnType | undefined; +}; + +// Export the Prisma client instance, reusing it if it already exists +export const db = globalForPrisma.prisma ?? createPrismaClient(); + +if (env.NODE_ENV !== "production") globalForPrisma.prisma = db; From e4a174c97dc54fe1b0973082de0919b18f6e343f Mon Sep 17 00:00:00 2001 From: Jose Toscano Date: Fri, 30 Aug 2024 23:44:43 -0600 Subject: [PATCH 2/8] updates env file config generation --- cli/src/installers/envVars.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cli/src/installers/envVars.ts b/cli/src/installers/envVars.ts index a7377ccf6e..fd9f416479 100644 --- a/cli/src/installers/envVars.ts +++ b/cli/src/installers/envVars.ts @@ -37,7 +37,7 @@ export const envVariablesInstaller: Installer = ({ } else { if (usingAuth) envFile = "with-auth.js"; } - + console.log("using env file:", envFile); if (envFile !== "") { const envSchemaSrc = path.join( PKG_ROOT, @@ -87,6 +87,10 @@ DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'`; content = `# Get the Database URL from the "prisma" dropdown selector in PlanetScale. DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?sslaccept=strict'`; } + } else if (databaseProvider === "neon") { + content += `# Get the database connection details from the Connection Details widget on the Neon Dashboard. +# Select a branch, a compute, a database, and a role. A connection string is constructed for you +DATABASE_URL="postgresql://YOUR_POSTGRES_CONNECTION_STRING_HERE?sslmode=require"`; } else if (databaseProvider === "mysql") { content += `DATABASE_URL="mysql://root:password@localhost:3306/${scopedAppName}"`; } else if (databaseProvider === "postgres") { From 1a6c3171a0fb75c4fd46322b9c3e7f28b175b596 Mon Sep 17 00:00:00 2001 From: Jose Toscano Date: Fri, 30 Aug 2024 23:57:36 -0600 Subject: [PATCH 3/8] updates display name to neon-t3-app --- cli/package.json | 4 ++-- cli/src/consts.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cli/package.json b/cli/package.json index 41b5152d49..86a2351c69 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { - "name": "create-t3-app", - "version": "7.37.0", + "name": "create-neon-t3-app", + "version": "1.0.0", "description": "Create web application with the t3 stack", "license": "MIT", "repository": { diff --git a/cli/src/consts.ts b/cli/src/consts.ts index df474167d2..f382599c74 100644 --- a/cli/src/consts.ts +++ b/cli/src/consts.ts @@ -9,10 +9,10 @@ export const PKG_ROOT = path.join(distPath, "../"); //export const PKG_ROOT = path.dirname(require.main.filename); -export const TITLE_TEXT = ` ___ ___ ___ __ _____ ___ _____ ____ __ ___ ___ - / __| _ \\ __| / \\_ _| __| |_ _|__ / / \\ | _ \\ _ \\ - | (__| / _| / /\\ \\| | | _| | | |_ \\ / /\\ \\| _/ _/ - \\___|_|_\\___|_/‾‾\\_\\_| |___| |_| |___/ /_/‾‾\\_\\_| |_| -`; +export const TITLE_TEXT = ` _ _ ___ ___ _ _ _____ ____ _ ___ ___ + | \\| | __/ _ \\| \\| | |_ _|__ / /_\\ | _ \\ _ \\ + | .\` | _| (_) | .\` | | | |_ \\ / _ \\| _/ _/ + |_|\\_|___\\___/|_|\\_| |_| |___/ /_/ \\_\\_| |_| + `; export const DEFAULT_APP_NAME = "my-t3-app"; export const CREATE_T3_APP = "create-t3-app"; From eabefadf0d64b2210a06b2cdc04efcfe9d12329c Mon Sep 17 00:00:00 2001 From: Jose Toscano Date: Sat, 31 Aug 2024 13:03:32 -0600 Subject: [PATCH 4/8] Using drizzleDatabaseProvider variable within installer --- cli/src/helpers/createProject.ts | 4 ++++ cli/src/index.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/cli/src/helpers/createProject.ts b/cli/src/helpers/createProject.ts index 81b385485f..85c75aeecd 100644 --- a/cli/src/helpers/createProject.ts +++ b/cli/src/helpers/createProject.ts @@ -24,6 +24,7 @@ interface CreateProjectOptions { importAlias: string; appRouter: boolean; databaseProvider: DatabaseProvider; + drizzleDatabaseProvider: DatabaseProvider; } export const createProject = async ({ @@ -33,6 +34,7 @@ export const createProject = async ({ noInstall, appRouter, databaseProvider, + drizzleDatabaseProvider, }: CreateProjectOptions) => { const pkgManager = getUserPkgManager(); const projectDir = path.resolve(process.cwd(), projectName); @@ -46,6 +48,7 @@ export const createProject = async ({ noInstall, appRouter, databaseProvider, + drizzleDatabaseProvider, }); // Install the selected packages @@ -58,6 +61,7 @@ export const createProject = async ({ noInstall, appRouter, databaseProvider, + drizzleDatabaseProvider, }); // Select necessary _app,index / layout,page files diff --git a/cli/src/index.ts b/cli/src/index.ts index 0394f25180..ead9a69115 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -40,6 +40,7 @@ const main = async () => { packages, flags: { noGit, noInstall, importAlias, appRouter }, databaseProvider, + drizzleDatabaseProvider, } = await runCli(); const usePackages = buildPkgInstallerMap(packages, databaseProvider); @@ -52,6 +53,7 @@ const main = async () => { scopedAppName, packages: usePackages, databaseProvider, + drizzleDatabaseProvider, importAlias, noInstall, appRouter, From 3750f518f2ab2d155e5568afe84b0168916a1c16 Mon Sep 17 00:00:00 2001 From: Jose Toscano Date: Sun, 1 Sep 2024 14:12:04 -0600 Subject: [PATCH 5/8] reverts changes to title and npm config --- cli/package.json | 4 ++-- cli/src/consts.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cli/package.json b/cli/package.json index 86a2351c69..41b5152d49 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { - "name": "create-neon-t3-app", - "version": "1.0.0", + "name": "create-t3-app", + "version": "7.37.0", "description": "Create web application with the t3 stack", "license": "MIT", "repository": { diff --git a/cli/src/consts.ts b/cli/src/consts.ts index f382599c74..df474167d2 100644 --- a/cli/src/consts.ts +++ b/cli/src/consts.ts @@ -9,10 +9,10 @@ export const PKG_ROOT = path.join(distPath, "../"); //export const PKG_ROOT = path.dirname(require.main.filename); -export const TITLE_TEXT = ` _ _ ___ ___ _ _ _____ ____ _ ___ ___ - | \\| | __/ _ \\| \\| | |_ _|__ / /_\\ | _ \\ _ \\ - | .\` | _| (_) | .\` | | | |_ \\ / _ \\| _/ _/ - |_|\\_|___\\___/|_|\\_| |_| |___/ /_/ \\_\\_| |_| - `; +export const TITLE_TEXT = ` ___ ___ ___ __ _____ ___ _____ ____ __ ___ ___ + / __| _ \\ __| / \\_ _| __| |_ _|__ / / \\ | _ \\ _ \\ + | (__| / _| / /\\ \\| | | _| | | |_ \\ / /\\ \\| _/ _/ + \\___|_|_\\___|_/‾‾\\_\\_| |___| |_| |___/ /_/‾‾\\_\\_| |_| +`; export const DEFAULT_APP_NAME = "my-t3-app"; export const CREATE_T3_APP = "create-t3-app"; From 11081805eed5b519d7aed1a015aabb2ec2f01b43 Mon Sep 17 00:00:00 2001 From: Jose Toscano Date: Sun, 1 Sep 2024 14:15:44 -0600 Subject: [PATCH 6/8] fix eslint/prettier errors --- cli/src/installers/drizzle.ts | 2 +- cli/template/extras/src/server/db/db-prisma-neon.ts | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index 7d42f441bb..750d20b0df 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -31,7 +31,7 @@ export const drizzleInstaller: Installer = ({ { planetscale: "@planetscale/database", mysql: "mysql2", - postgres: "postgres", // This will be selected when drizzleDatabaseProvider is "postgres" + postgres: "postgres", // This will be selected when drizzleDatabaseProvider is "postgres" sqlite: "@libsql/client", } as const )[drizzleDatabaseProvider], diff --git a/cli/template/extras/src/server/db/db-prisma-neon.ts b/cli/template/extras/src/server/db/db-prisma-neon.ts index 94e5e16235..c6f2f2c42c 100644 --- a/cli/template/extras/src/server/db/db-prisma-neon.ts +++ b/cli/template/extras/src/server/db/db-prisma-neon.ts @@ -1,6 +1,7 @@ import { neon } from "@neondatabase/serverless"; import { PrismaNeonHTTP } from "@prisma/adapter-neon"; import { PrismaClient } from "@prisma/client"; + import { env } from "~/env"; // Initialize Neon client using the DATABASE_URL from the environment variables @@ -11,15 +12,15 @@ const adapter = new PrismaNeonHTTP(sql); // Create a new Prisma client instance with the Neon adapter const createPrismaClient = () => - new PrismaClient({ - log: - env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], - adapter, - }); + new PrismaClient({ + log: + env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], + adapter, + }); // Global variable to store the Prisma client instance across module reloads const globalForPrisma = globalThis as unknown as { - prisma: ReturnType | undefined; + prisma: ReturnType | undefined; }; // Export the Prisma client instance, reusing it if it already exists From 0bc86c2107ee49da8ea4f1d04068c6a0de0af5fd Mon Sep 17 00:00:00 2001 From: Jose Toscano Date: Wed, 23 Oct 2024 06:39:40 -0600 Subject: [PATCH 7/8] Typecheck fix --- cli/src/installers/drizzle.ts | 3 ++- cli/src/installers/prisma.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index 750d20b0df..193c172709 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -31,7 +31,8 @@ export const drizzleInstaller: Installer = ({ { planetscale: "@planetscale/database", mysql: "mysql2", - postgres: "postgres", // This will be selected when drizzleDatabaseProvider is "postgres" + neon: "postgres", + postgres: "postgres", sqlite: "@libsql/client", } as const )[drizzleDatabaseProvider], diff --git a/cli/src/installers/prisma.ts b/cli/src/installers/prisma.ts index 6db345fb10..c4f5c93d76 100644 --- a/cli/src/installers/prisma.ts +++ b/cli/src/installers/prisma.ts @@ -57,6 +57,7 @@ export const prismaInstaller: Installer = ({ mysql: "mysql", postgres: "postgresql", planetscale: "mysql", + neon: "postgresql", }[databaseProvider] }"` ); From 2fbad83016746edb6cdbc64570507789d097d47f Mon Sep 17 00:00:00 2001 From: Jose Toscano Date: Wed, 23 Oct 2024 06:47:06 -0600 Subject: [PATCH 8/8] chore: add changeset --- .changeset/purple-deers-pretend.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/purple-deers-pretend.md diff --git a/.changeset/purple-deers-pretend.md b/.changeset/purple-deers-pretend.md new file mode 100644 index 0000000000..43bcffb674 --- /dev/null +++ b/.changeset/purple-deers-pretend.md @@ -0,0 +1,5 @@ +--- +"create-t3-app": minor +--- + +Add support for Neon as Postgresql Database Provider