Skip to content

Commit 0c26578

Browse files
feat(cli): add prisma create-db setup (#419)
1 parent 2543c53 commit 0c26578

File tree

15 files changed

+139
-71
lines changed

15 files changed

+139
-71
lines changed

.changeset/clear-buses-shave.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"create-better-t-stack": minor
3+
---
4+
5+
feat: Add quick setup option with create-db by Prisma
6+
feat: Make Prisma Postgres available for both Prisma and Drizzle ORMs

apps/cli/src/constants.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const dependencyVersionMap = {
3535
"drizzle-kit": "^0.31.2",
3636

3737
"@libsql/client": "^0.15.9",
38+
3839
"@neondatabase/serverless": "^1.0.1",
3940
pg: "^8.14.1",
4041
"@types/pg": "^8.11.11",
@@ -43,8 +44,9 @@ export const dependencyVersionMap = {
4344

4445
mysql2: "^3.14.0",
4546

46-
"@prisma/client": "^6.9.0",
47-
prisma: "^6.9.0",
47+
"@prisma/client": "^6.12.0",
48+
prisma: "^6.12.0",
49+
"@prisma/extension-accelerate": "^2.0.2",
4850

4951
mongoose: "^8.14.0",
5052

@@ -89,8 +91,6 @@ export const dependencyVersionMap = {
8991
"@ai-sdk/svelte": "^2.1.9",
9092
"@ai-sdk/react": "^1.2.12",
9193

92-
"@prisma/extension-accelerate": "^1.3.0",
93-
9494
"@orpc/server": "^1.5.0",
9595
"@orpc/client": "^1.5.0",
9696
"@orpc/tanstack-query": "^1.5.0",

apps/cli/src/helpers/database-providers/mongodb-atlas-setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type MongoDBConfig = {
1515
connectionString: string;
1616
};
1717

18-
async function checkAtlasCLI(): Promise<boolean> {
18+
async function checkAtlasCLI() {
1919
const s = spinner();
2020
s.start("Checking for MongoDB Atlas CLI...");
2121

apps/cli/src/helpers/database-providers/prisma-postgres-setup.ts

Lines changed: 111 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import path from "node:path";
2-
import { cancel, isCancel, log, password, spinner } from "@clack/prompts";
2+
import { cancel, isCancel, log, select, spinner, text } from "@clack/prompts";
33
import { consola } from "consola";
44
import { execa } from "execa";
55
import fs from "fs-extra";
66
import pc from "picocolors";
7-
import type { PackageManager } from "../../types";
7+
import type { ORM, PackageManager, ProjectConfig } from "../../types";
88
import { addPackageDependency } from "../../utils/add-package-deps";
99
import { getPackageExecutionCommand } from "../../utils/package-runner";
1010
import {
@@ -16,18 +16,77 @@ type PrismaConfig = {
1616
databaseUrl: string;
1717
};
1818

19-
async function initPrismaDatabase(
19+
async function setupWithCreateDb(
2020
serverDir: string,
2121
packageManager: PackageManager,
22-
): Promise<PrismaConfig | null> {
23-
const s = spinner();
22+
orm: ORM,
23+
) {
2424
try {
25-
s.start("Initializing Prisma PostgreSQL...");
25+
log.info(
26+
"Starting Prisma PostgreSQL setup. Please follow the instructions below:",
27+
);
28+
29+
const createDbCommand = getPackageExecutionCommand(
30+
packageManager,
31+
"create-db@latest -i",
32+
);
33+
34+
await execa(createDbCommand, {
35+
cwd: serverDir,
36+
stdio: "inherit",
37+
shell: true,
38+
});
39+
40+
log.info(
41+
orm === "drizzle"
42+
? pc.yellow(
43+
"Please copy the database URL from the output above and append ?sslmode=require for Drizzle.",
44+
)
45+
: pc.yellow(
46+
"Please copy the Prisma Postgres URL from the output above.",
47+
),
48+
);
49+
50+
const databaseUrl = await text({
51+
message:
52+
orm === "drizzle"
53+
? "Paste your database URL (append ?sslmode=require for Drizzle):"
54+
: "Paste your Prisma Postgres database URL:",
55+
validate(value) {
56+
if (!value) return "Please enter a database URL";
57+
if (orm === "drizzle" && !value.includes("?sslmode=require")) {
58+
return "Please append ?sslmode=require to your database URL when using Drizzle";
59+
}
60+
},
61+
});
62+
63+
if (isCancel(databaseUrl)) {
64+
cancel("Database setup cancelled");
65+
return null;
66+
}
67+
68+
return {
69+
databaseUrl: databaseUrl as string,
70+
};
71+
} catch (error) {
72+
if (error instanceof Error) {
73+
consola.error(error.message);
74+
}
75+
return null;
76+
}
77+
}
2678

79+
async function initPrismaDatabase(
80+
serverDir: string,
81+
packageManager: PackageManager,
82+
) {
83+
try {
2784
const prismaDir = path.join(serverDir, "prisma");
2885
await fs.ensureDir(prismaDir);
2986

30-
s.stop("Prisma PostgreSQL initialized. Follow the prompts below:");
87+
log.info(
88+
"Starting Prisma PostgreSQL setup. Please follow the instructions below:",
89+
);
3190

3291
const prismaInitCommand = getPackageExecutionCommand(
3392
packageManager,
@@ -46,7 +105,7 @@ async function initPrismaDatabase(
46105
),
47106
);
48107

49-
const databaseUrl = await password({
108+
const databaseUrl = await text({
50109
message: "Paste your Prisma Postgres database URL:",
51110
validate(value) {
52111
if (!value) return "Please enter a database URL";
@@ -65,7 +124,6 @@ async function initPrismaDatabase(
65124
databaseUrl: databaseUrl as string,
66125
};
67126
} catch (error) {
68-
s.stop(pc.red("Prisma PostgreSQL initialization failed"));
69127
if (error instanceof Error) {
70128
consola.error(error.message);
71129
}
@@ -144,32 +202,61 @@ export default prisma;
144202
}
145203
}
146204

147-
import type { ProjectConfig } from "../../types";
148-
149205
export async function setupPrismaPostgres(config: ProjectConfig) {
150-
const { packageManager, projectDir } = config;
206+
const { packageManager, projectDir, orm } = config;
151207
const serverDir = path.join(projectDir, "apps/server");
152-
const s = spinner();
153-
s.start("Setting up Prisma PostgreSQL...");
154208

155209
try {
156210
await fs.ensureDir(serverDir);
157211

158-
s.stop("Prisma PostgreSQL setup ready");
212+
const setupOptions = [
213+
{
214+
label: "Quick setup with create-db",
215+
value: "create-db",
216+
hint: "Fastest, automated database creation",
217+
},
218+
];
219+
220+
if (orm === "prisma") {
221+
setupOptions.push({
222+
label: "Custom setup with Prisma Console",
223+
value: "custom",
224+
hint: "More control - use existing Prisma account",
225+
});
226+
}
227+
228+
const setupMethod = await select({
229+
message: "Choose your Prisma setup method:",
230+
options: setupOptions,
231+
initialValue: "create-db",
232+
});
233+
234+
if (isCancel(setupMethod)) {
235+
cancel(pc.red("Operation cancelled"));
236+
process.exit(0);
237+
}
238+
239+
let prismaConfig: PrismaConfig | null = null;
159240

160-
const config = await initPrismaDatabase(serverDir, packageManager);
241+
if (setupMethod === "create-db") {
242+
prismaConfig = await setupWithCreateDb(serverDir, packageManager, orm);
243+
} else {
244+
prismaConfig = await initPrismaDatabase(serverDir, packageManager);
245+
}
161246

162-
if (config) {
163-
await writeEnvFile(projectDir, config);
164-
await addPrismaAccelerateExtension(serverDir);
247+
if (prismaConfig) {
248+
await writeEnvFile(projectDir, prismaConfig);
249+
if (orm === "prisma") {
250+
await addPrismaAccelerateExtension(serverDir);
251+
log.info(
252+
pc.cyan(
253+
'NOTE: Make sure to uncomment `import "dotenv/config";` in `apps/server/src/prisma.config.ts` to load environment variables.',
254+
),
255+
);
256+
}
165257
log.success(
166258
pc.green("Prisma PostgreSQL database configured successfully!"),
167259
);
168-
log.info(
169-
pc.cyan(
170-
'NOTE: Make sure to uncomment `import "dotenv/config";` in `apps/server/src/prisma.config.ts` to load environment variables.',
171-
),
172-
);
173260
} else {
174261
const fallbackSpinner = spinner();
175262
fallbackSpinner.start("Setting up fallback configuration...");
@@ -178,7 +265,6 @@ export async function setupPrismaPostgres(config: ProjectConfig) {
178265
displayManualSetupInstructions();
179266
}
180267
} catch (error) {
181-
s.stop(pc.red("Prisma PostgreSQL setup failed"));
182268
consola.error(
183269
pc.red(
184270
`Error during Prisma PostgreSQL setup: ${

apps/cli/src/helpers/database-providers/supabase-setup.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ import {
1111
type EnvVariable,
1212
} from "../project-generation/env-setup";
1313

14-
async function writeSupabaseEnvFile(
15-
projectDir: string,
16-
databaseUrl: string,
17-
): Promise<boolean> {
14+
async function writeSupabaseEnvFile(projectDir: string, databaseUrl: string) {
1815
try {
1916
const envPath = path.join(projectDir, "apps/server", ".env");
2017
const dbUrlToUse =
@@ -54,7 +51,7 @@ function extractDbUrl(output: string): string | null {
5451
async function initializeSupabase(
5552
serverDir: string,
5653
packageManager: PackageManager,
57-
): Promise<boolean> {
54+
) {
5855
log.info("Initializing Supabase project...");
5956
try {
6057
const supabaseInitCommand = getPackageExecutionCommand(
@@ -90,7 +87,7 @@ async function initializeSupabase(
9087
async function startSupabase(
9188
serverDir: string,
9289
packageManager: PackageManager,
93-
): Promise<string | null> {
90+
) {
9491
log.info("Starting Supabase services (this may take a moment)...");
9592
const supabaseStartCommand = getPackageExecutionCommand(
9693
packageManager,

apps/cli/src/helpers/project-generation/detect-project-config.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ export async function detectProjectConfig(
3333
}
3434
}
3535

36-
export async function isBetterTStackProject(
37-
projectDir: string,
38-
): Promise<boolean> {
36+
export async function isBetterTStackProject(projectDir: string) {
3937
try {
4038
return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
4139
} catch (_error) {

apps/cli/src/helpers/setup/db-setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export async function setupDatabase(config: ProjectConfig): Promise<void> {
8484
} else if (database === "sqlite" && dbSetup === "d1") {
8585
await setupCloudflareD1(config);
8686
} else if (database === "postgres") {
87-
if (orm === "prisma" && dbSetup === "prisma-postgres") {
87+
if (dbSetup === "prisma-postgres") {
8888
await setupPrismaPostgres(config);
8989
} else if (dbSetup === "neon") {
9090
await setupNeonPostgres(config);

apps/cli/src/prompts/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export async function getAuthChoice(
77
auth: boolean | undefined,
88
hasDatabase: boolean,
99
backend?: Backend,
10-
): Promise<boolean> {
10+
) {
1111
if (backend === "convex") {
1212
return false;
1313
}

apps/cli/src/prompts/database-setup.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,11 @@ export async function getDBSetupChoice(
5656
label: "Supabase",
5757
hint: "Local Supabase stack (requires Docker)",
5858
},
59-
...(orm === "prisma"
60-
? [
61-
{
62-
value: "prisma-postgres" as const,
63-
label: "Prisma Postgres",
64-
hint: "Instant Postgres for Global Applications",
65-
},
66-
]
67-
: []),
59+
{
60+
value: "prisma-postgres" as const,
61+
label: "Prisma Postgres",
62+
hint: "Instant Postgres for Global Applications",
63+
},
6864
{
6965
value: "docker" as const,
7066
label: "Docker",

apps/cli/src/prompts/git.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { cancel, confirm, isCancel } from "@clack/prompts";
22
import pc from "picocolors";
33
import { DEFAULT_CONFIG } from "../constants";
44

5-
export async function getGitChoice(git?: boolean): Promise<boolean> {
5+
export async function getGitChoice(git?: boolean) {
66
if (git !== undefined) return git;
77

88
const response = await confirm({

0 commit comments

Comments
 (0)