feat(cli): add prisma create-db setup (#419)

This commit is contained in:
Aman Varshney
2025-07-23 23:35:28 +05:30
committed by GitHub
parent 2543c5317b
commit 0c26578e8e
15 changed files with 139 additions and 71 deletions

View File

@@ -0,0 +1,6 @@
---
"create-better-t-stack": minor
---
feat: Add quick setup option with create-db by Prisma
feat: Make Prisma Postgres available for both Prisma and Drizzle ORMs

View File

@@ -35,6 +35,7 @@ export const dependencyVersionMap = {
"drizzle-kit": "^0.31.2",
"@libsql/client": "^0.15.9",
"@neondatabase/serverless": "^1.0.1",
pg: "^8.14.1",
"@types/pg": "^8.11.11",
@@ -43,8 +44,9 @@ export const dependencyVersionMap = {
mysql2: "^3.14.0",
"@prisma/client": "^6.9.0",
prisma: "^6.9.0",
"@prisma/client": "^6.12.0",
prisma: "^6.12.0",
"@prisma/extension-accelerate": "^2.0.2",
mongoose: "^8.14.0",
@@ -89,8 +91,6 @@ export const dependencyVersionMap = {
"@ai-sdk/svelte": "^2.1.9",
"@ai-sdk/react": "^1.2.12",
"@prisma/extension-accelerate": "^1.3.0",
"@orpc/server": "^1.5.0",
"@orpc/client": "^1.5.0",
"@orpc/tanstack-query": "^1.5.0",

View File

@@ -15,7 +15,7 @@ type MongoDBConfig = {
connectionString: string;
};
async function checkAtlasCLI(): Promise<boolean> {
async function checkAtlasCLI() {
const s = spinner();
s.start("Checking for MongoDB Atlas CLI...");

View File

@@ -1,10 +1,10 @@
import path from "node:path";
import { cancel, isCancel, log, password, spinner } from "@clack/prompts";
import { cancel, isCancel, log, select, spinner, text } from "@clack/prompts";
import { consola } from "consola";
import { execa } from "execa";
import fs from "fs-extra";
import pc from "picocolors";
import type { PackageManager } from "../../types";
import type { ORM, PackageManager, ProjectConfig } from "../../types";
import { addPackageDependency } from "../../utils/add-package-deps";
import { getPackageExecutionCommand } from "../../utils/package-runner";
import {
@@ -16,18 +16,77 @@ type PrismaConfig = {
databaseUrl: string;
};
async function setupWithCreateDb(
serverDir: string,
packageManager: PackageManager,
orm: ORM,
) {
try {
log.info(
"Starting Prisma PostgreSQL setup. Please follow the instructions below:",
);
const createDbCommand = getPackageExecutionCommand(
packageManager,
"create-db@latest -i",
);
await execa(createDbCommand, {
cwd: serverDir,
stdio: "inherit",
shell: true,
});
log.info(
orm === "drizzle"
? pc.yellow(
"Please copy the database URL from the output above and append ?sslmode=require for Drizzle.",
)
: pc.yellow(
"Please copy the Prisma Postgres URL from the output above.",
),
);
const databaseUrl = await text({
message:
orm === "drizzle"
? "Paste your database URL (append ?sslmode=require for Drizzle):"
: "Paste your Prisma Postgres database URL:",
validate(value) {
if (!value) return "Please enter a database URL";
if (orm === "drizzle" && !value.includes("?sslmode=require")) {
return "Please append ?sslmode=require to your database URL when using Drizzle";
}
},
});
if (isCancel(databaseUrl)) {
cancel("Database setup cancelled");
return null;
}
return {
databaseUrl: databaseUrl as string,
};
} catch (error) {
if (error instanceof Error) {
consola.error(error.message);
}
return null;
}
}
async function initPrismaDatabase(
serverDir: string,
packageManager: PackageManager,
): Promise<PrismaConfig | null> {
const s = spinner();
) {
try {
s.start("Initializing Prisma PostgreSQL...");
const prismaDir = path.join(serverDir, "prisma");
await fs.ensureDir(prismaDir);
s.stop("Prisma PostgreSQL initialized. Follow the prompts below:");
log.info(
"Starting Prisma PostgreSQL setup. Please follow the instructions below:",
);
const prismaInitCommand = getPackageExecutionCommand(
packageManager,
@@ -46,7 +105,7 @@ async function initPrismaDatabase(
),
);
const databaseUrl = await password({
const databaseUrl = await text({
message: "Paste your Prisma Postgres database URL:",
validate(value) {
if (!value) return "Please enter a database URL";
@@ -65,7 +124,6 @@ async function initPrismaDatabase(
databaseUrl: databaseUrl as string,
};
} catch (error) {
s.stop(pc.red("Prisma PostgreSQL initialization failed"));
if (error instanceof Error) {
consola.error(error.message);
}
@@ -144,32 +202,61 @@ export default prisma;
}
}
import type { ProjectConfig } from "../../types";
export async function setupPrismaPostgres(config: ProjectConfig) {
const { packageManager, projectDir } = config;
const { packageManager, projectDir, orm } = config;
const serverDir = path.join(projectDir, "apps/server");
const s = spinner();
s.start("Setting up Prisma PostgreSQL...");
try {
await fs.ensureDir(serverDir);
s.stop("Prisma PostgreSQL setup ready");
const setupOptions = [
{
label: "Quick setup with create-db",
value: "create-db",
hint: "Fastest, automated database creation",
},
];
const config = await initPrismaDatabase(serverDir, packageManager);
if (orm === "prisma") {
setupOptions.push({
label: "Custom setup with Prisma Console",
value: "custom",
hint: "More control - use existing Prisma account",
});
}
if (config) {
await writeEnvFile(projectDir, config);
await addPrismaAccelerateExtension(serverDir);
const setupMethod = await select({
message: "Choose your Prisma setup method:",
options: setupOptions,
initialValue: "create-db",
});
if (isCancel(setupMethod)) {
cancel(pc.red("Operation cancelled"));
process.exit(0);
}
let prismaConfig: PrismaConfig | null = null;
if (setupMethod === "create-db") {
prismaConfig = await setupWithCreateDb(serverDir, packageManager, orm);
} else {
prismaConfig = await initPrismaDatabase(serverDir, packageManager);
}
if (prismaConfig) {
await writeEnvFile(projectDir, prismaConfig);
if (orm === "prisma") {
await addPrismaAccelerateExtension(serverDir);
log.info(
pc.cyan(
'NOTE: Make sure to uncomment `import "dotenv/config";` in `apps/server/src/prisma.config.ts` to load environment variables.',
),
);
}
log.success(
pc.green("Prisma PostgreSQL database configured successfully!"),
);
log.info(
pc.cyan(
'NOTE: Make sure to uncomment `import "dotenv/config";` in `apps/server/src/prisma.config.ts` to load environment variables.',
),
);
} else {
const fallbackSpinner = spinner();
fallbackSpinner.start("Setting up fallback configuration...");
@@ -178,7 +265,6 @@ export async function setupPrismaPostgres(config: ProjectConfig) {
displayManualSetupInstructions();
}
} catch (error) {
s.stop(pc.red("Prisma PostgreSQL setup failed"));
consola.error(
pc.red(
`Error during Prisma PostgreSQL setup: ${

View File

@@ -11,10 +11,7 @@ import {
type EnvVariable,
} from "../project-generation/env-setup";
async function writeSupabaseEnvFile(
projectDir: string,
databaseUrl: string,
): Promise<boolean> {
async function writeSupabaseEnvFile(projectDir: string, databaseUrl: string) {
try {
const envPath = path.join(projectDir, "apps/server", ".env");
const dbUrlToUse =
@@ -54,7 +51,7 @@ function extractDbUrl(output: string): string | null {
async function initializeSupabase(
serverDir: string,
packageManager: PackageManager,
): Promise<boolean> {
) {
log.info("Initializing Supabase project...");
try {
const supabaseInitCommand = getPackageExecutionCommand(
@@ -90,7 +87,7 @@ async function initializeSupabase(
async function startSupabase(
serverDir: string,
packageManager: PackageManager,
): Promise<string | null> {
) {
log.info("Starting Supabase services (this may take a moment)...");
const supabaseStartCommand = getPackageExecutionCommand(
packageManager,

View File

@@ -33,9 +33,7 @@ export async function detectProjectConfig(
}
}
export async function isBetterTStackProject(
projectDir: string,
): Promise<boolean> {
export async function isBetterTStackProject(projectDir: string) {
try {
return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
} catch (_error) {

View File

@@ -84,7 +84,7 @@ export async function setupDatabase(config: ProjectConfig): Promise<void> {
} else if (database === "sqlite" && dbSetup === "d1") {
await setupCloudflareD1(config);
} else if (database === "postgres") {
if (orm === "prisma" && dbSetup === "prisma-postgres") {
if (dbSetup === "prisma-postgres") {
await setupPrismaPostgres(config);
} else if (dbSetup === "neon") {
await setupNeonPostgres(config);

View File

@@ -7,7 +7,7 @@ export async function getAuthChoice(
auth: boolean | undefined,
hasDatabase: boolean,
backend?: Backend,
): Promise<boolean> {
) {
if (backend === "convex") {
return false;
}

View File

@@ -56,15 +56,11 @@ export async function getDBSetupChoice(
label: "Supabase",
hint: "Local Supabase stack (requires Docker)",
},
...(orm === "prisma"
? [
{
value: "prisma-postgres" as const,
label: "Prisma Postgres",
hint: "Instant Postgres for Global Applications",
},
]
: []),
{
value: "prisma-postgres" as const,
label: "Prisma Postgres",
hint: "Instant Postgres for Global Applications",
},
{
value: "docker" as const,
label: "Docker",

View File

@@ -2,7 +2,7 @@ import { cancel, confirm, isCancel } from "@clack/prompts";
import pc from "picocolors";
import { DEFAULT_CONFIG } from "../constants";
export async function getGitChoice(git?: boolean): Promise<boolean> {
export async function getGitChoice(git?: boolean) {
if (git !== undefined) return git;
const response = await confirm({

View File

@@ -2,7 +2,7 @@ import { cancel, confirm, isCancel } from "@clack/prompts";
import pc from "picocolors";
import { DEFAULT_CONFIG } from "../constants";
export async function getinstallChoice(install?: boolean): Promise<boolean> {
export async function getinstallChoice(install?: boolean) {
if (install !== undefined) return install;
const response = await confirm({

View File

@@ -1,6 +1,6 @@
import { execa } from "execa";
export async function commandExists(command: string): Promise<boolean> {
export async function commandExists(command: string) {
try {
const isWindows = process.platform === "win32";
if (isWindows) {

View File

@@ -3,11 +3,11 @@ import pc from "picocolors";
import type { Database } from "../types";
import { commandExists } from "./command-exists";
export async function isDockerInstalled(): Promise<boolean> {
export async function isDockerInstalled() {
return commandExists("docker");
}
export async function isDockerRunning(): Promise<boolean> {
export async function isDockerRunning() {
try {
const { $ } = await import("execa");
await $`docker info`;

View File

@@ -4,7 +4,7 @@
"type": "module",
"scripts": {
"build": "tsdown",
"check-types": "tsc --noEmit",
"check-types": "tsc -b",
"compile": "bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server"
},
{{#if (eq orm 'prisma')}}

View File

@@ -448,21 +448,6 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
"Database set to 'PostgreSQL' (required by Prisma PostgreSQL setup)",
});
}
if (nextStack.orm !== "prisma") {
notes.dbSetup.notes.push("Requires Prisma ORM. It will be selected.");
notes.orm.notes.push(
"Prisma PostgreSQL setup requires Prisma ORM. It will be selected.",
);
notes.dbSetup.hasIssue = true;
notes.orm.hasIssue = true;
nextStack.orm = "prisma";
changed = true;
changes.push({
category: "dbSetup",
message:
"ORM set to 'Prisma' (required by Prisma PostgreSQL setup)",
});
}
} else if (nextStack.dbSetup === "mongodb-atlas") {
if (nextStack.database !== "mongodb") {
notes.dbSetup.notes.push("Requires MongoDB. It will be selected.");