mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(cli): add prisma create-db setup (#419)
This commit is contained in:
6
.changeset/clear-buses-shave.md
Normal file
6
.changeset/clear-buses-shave.md
Normal 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
|
||||
@@ -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",
|
||||
|
||||
@@ -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...");
|
||||
|
||||
|
||||
@@ -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: ${
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -7,7 +7,7 @@ export async function getAuthChoice(
|
||||
auth: boolean | undefined,
|
||||
hasDatabase: boolean,
|
||||
backend?: Backend,
|
||||
): Promise<boolean> {
|
||||
) {
|
||||
if (backend === "convex") {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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')}}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
Reference in New Issue
Block a user