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",
|
"drizzle-kit": "^0.31.2",
|
||||||
|
|
||||||
"@libsql/client": "^0.15.9",
|
"@libsql/client": "^0.15.9",
|
||||||
|
|
||||||
"@neondatabase/serverless": "^1.0.1",
|
"@neondatabase/serverless": "^1.0.1",
|
||||||
pg: "^8.14.1",
|
pg: "^8.14.1",
|
||||||
"@types/pg": "^8.11.11",
|
"@types/pg": "^8.11.11",
|
||||||
@@ -43,8 +44,9 @@ export const dependencyVersionMap = {
|
|||||||
|
|
||||||
mysql2: "^3.14.0",
|
mysql2: "^3.14.0",
|
||||||
|
|
||||||
"@prisma/client": "^6.9.0",
|
"@prisma/client": "^6.12.0",
|
||||||
prisma: "^6.9.0",
|
prisma: "^6.12.0",
|
||||||
|
"@prisma/extension-accelerate": "^2.0.2",
|
||||||
|
|
||||||
mongoose: "^8.14.0",
|
mongoose: "^8.14.0",
|
||||||
|
|
||||||
@@ -89,8 +91,6 @@ export const dependencyVersionMap = {
|
|||||||
"@ai-sdk/svelte": "^2.1.9",
|
"@ai-sdk/svelte": "^2.1.9",
|
||||||
"@ai-sdk/react": "^1.2.12",
|
"@ai-sdk/react": "^1.2.12",
|
||||||
|
|
||||||
"@prisma/extension-accelerate": "^1.3.0",
|
|
||||||
|
|
||||||
"@orpc/server": "^1.5.0",
|
"@orpc/server": "^1.5.0",
|
||||||
"@orpc/client": "^1.5.0",
|
"@orpc/client": "^1.5.0",
|
||||||
"@orpc/tanstack-query": "^1.5.0",
|
"@orpc/tanstack-query": "^1.5.0",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type MongoDBConfig = {
|
|||||||
connectionString: string;
|
connectionString: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function checkAtlasCLI(): Promise<boolean> {
|
async function checkAtlasCLI() {
|
||||||
const s = spinner();
|
const s = spinner();
|
||||||
s.start("Checking for MongoDB Atlas CLI...");
|
s.start("Checking for MongoDB Atlas CLI...");
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import path from "node:path";
|
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 { consola } from "consola";
|
||||||
import { execa } from "execa";
|
import { execa } from "execa";
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
import type { PackageManager } from "../../types";
|
import type { ORM, PackageManager, ProjectConfig } from "../../types";
|
||||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||||
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
||||||
import {
|
import {
|
||||||
@@ -16,18 +16,77 @@ type PrismaConfig = {
|
|||||||
databaseUrl: string;
|
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(
|
async function initPrismaDatabase(
|
||||||
serverDir: string,
|
serverDir: string,
|
||||||
packageManager: PackageManager,
|
packageManager: PackageManager,
|
||||||
): Promise<PrismaConfig | null> {
|
) {
|
||||||
const s = spinner();
|
|
||||||
try {
|
try {
|
||||||
s.start("Initializing Prisma PostgreSQL...");
|
|
||||||
|
|
||||||
const prismaDir = path.join(serverDir, "prisma");
|
const prismaDir = path.join(serverDir, "prisma");
|
||||||
await fs.ensureDir(prismaDir);
|
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(
|
const prismaInitCommand = getPackageExecutionCommand(
|
||||||
packageManager,
|
packageManager,
|
||||||
@@ -46,7 +105,7 @@ async function initPrismaDatabase(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const databaseUrl = await password({
|
const databaseUrl = await text({
|
||||||
message: "Paste your Prisma Postgres database URL:",
|
message: "Paste your Prisma Postgres database URL:",
|
||||||
validate(value) {
|
validate(value) {
|
||||||
if (!value) return "Please enter a database URL";
|
if (!value) return "Please enter a database URL";
|
||||||
@@ -65,7 +124,6 @@ async function initPrismaDatabase(
|
|||||||
databaseUrl: databaseUrl as string,
|
databaseUrl: databaseUrl as string,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
s.stop(pc.red("Prisma PostgreSQL initialization failed"));
|
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
consola.error(error.message);
|
consola.error(error.message);
|
||||||
}
|
}
|
||||||
@@ -144,32 +202,61 @@ export default prisma;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
import type { ProjectConfig } from "../../types";
|
|
||||||
|
|
||||||
export async function setupPrismaPostgres(config: ProjectConfig) {
|
export async function setupPrismaPostgres(config: ProjectConfig) {
|
||||||
const { packageManager, projectDir } = config;
|
const { packageManager, projectDir, orm } = config;
|
||||||
const serverDir = path.join(projectDir, "apps/server");
|
const serverDir = path.join(projectDir, "apps/server");
|
||||||
const s = spinner();
|
|
||||||
s.start("Setting up Prisma PostgreSQL...");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.ensureDir(serverDir);
|
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) {
|
const setupMethod = await select({
|
||||||
await writeEnvFile(projectDir, config);
|
message: "Choose your Prisma setup method:",
|
||||||
await addPrismaAccelerateExtension(serverDir);
|
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(
|
log.success(
|
||||||
pc.green("Prisma PostgreSQL database configured successfully!"),
|
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 {
|
} else {
|
||||||
const fallbackSpinner = spinner();
|
const fallbackSpinner = spinner();
|
||||||
fallbackSpinner.start("Setting up fallback configuration...");
|
fallbackSpinner.start("Setting up fallback configuration...");
|
||||||
@@ -178,7 +265,6 @@ export async function setupPrismaPostgres(config: ProjectConfig) {
|
|||||||
displayManualSetupInstructions();
|
displayManualSetupInstructions();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
s.stop(pc.red("Prisma PostgreSQL setup failed"));
|
|
||||||
consola.error(
|
consola.error(
|
||||||
pc.red(
|
pc.red(
|
||||||
`Error during Prisma PostgreSQL setup: ${
|
`Error during Prisma PostgreSQL setup: ${
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ import {
|
|||||||
type EnvVariable,
|
type EnvVariable,
|
||||||
} from "../project-generation/env-setup";
|
} from "../project-generation/env-setup";
|
||||||
|
|
||||||
async function writeSupabaseEnvFile(
|
async function writeSupabaseEnvFile(projectDir: string, databaseUrl: string) {
|
||||||
projectDir: string,
|
|
||||||
databaseUrl: string,
|
|
||||||
): Promise<boolean> {
|
|
||||||
try {
|
try {
|
||||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||||
const dbUrlToUse =
|
const dbUrlToUse =
|
||||||
@@ -54,7 +51,7 @@ function extractDbUrl(output: string): string | null {
|
|||||||
async function initializeSupabase(
|
async function initializeSupabase(
|
||||||
serverDir: string,
|
serverDir: string,
|
||||||
packageManager: PackageManager,
|
packageManager: PackageManager,
|
||||||
): Promise<boolean> {
|
) {
|
||||||
log.info("Initializing Supabase project...");
|
log.info("Initializing Supabase project...");
|
||||||
try {
|
try {
|
||||||
const supabaseInitCommand = getPackageExecutionCommand(
|
const supabaseInitCommand = getPackageExecutionCommand(
|
||||||
@@ -90,7 +87,7 @@ async function initializeSupabase(
|
|||||||
async function startSupabase(
|
async function startSupabase(
|
||||||
serverDir: string,
|
serverDir: string,
|
||||||
packageManager: PackageManager,
|
packageManager: PackageManager,
|
||||||
): Promise<string | null> {
|
) {
|
||||||
log.info("Starting Supabase services (this may take a moment)...");
|
log.info("Starting Supabase services (this may take a moment)...");
|
||||||
const supabaseStartCommand = getPackageExecutionCommand(
|
const supabaseStartCommand = getPackageExecutionCommand(
|
||||||
packageManager,
|
packageManager,
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ export async function detectProjectConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isBetterTStackProject(
|
export async function isBetterTStackProject(projectDir: string) {
|
||||||
projectDir: string,
|
|
||||||
): Promise<boolean> {
|
|
||||||
try {
|
try {
|
||||||
return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
|
return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export async function setupDatabase(config: ProjectConfig): Promise<void> {
|
|||||||
} else if (database === "sqlite" && dbSetup === "d1") {
|
} else if (database === "sqlite" && dbSetup === "d1") {
|
||||||
await setupCloudflareD1(config);
|
await setupCloudflareD1(config);
|
||||||
} else if (database === "postgres") {
|
} else if (database === "postgres") {
|
||||||
if (orm === "prisma" && dbSetup === "prisma-postgres") {
|
if (dbSetup === "prisma-postgres") {
|
||||||
await setupPrismaPostgres(config);
|
await setupPrismaPostgres(config);
|
||||||
} else if (dbSetup === "neon") {
|
} else if (dbSetup === "neon") {
|
||||||
await setupNeonPostgres(config);
|
await setupNeonPostgres(config);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export async function getAuthChoice(
|
|||||||
auth: boolean | undefined,
|
auth: boolean | undefined,
|
||||||
hasDatabase: boolean,
|
hasDatabase: boolean,
|
||||||
backend?: Backend,
|
backend?: Backend,
|
||||||
): Promise<boolean> {
|
) {
|
||||||
if (backend === "convex") {
|
if (backend === "convex") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,15 +56,11 @@ export async function getDBSetupChoice(
|
|||||||
label: "Supabase",
|
label: "Supabase",
|
||||||
hint: "Local Supabase stack (requires Docker)",
|
hint: "Local Supabase stack (requires Docker)",
|
||||||
},
|
},
|
||||||
...(orm === "prisma"
|
{
|
||||||
? [
|
value: "prisma-postgres" as const,
|
||||||
{
|
label: "Prisma Postgres",
|
||||||
value: "prisma-postgres" as const,
|
hint: "Instant Postgres for Global Applications",
|
||||||
label: "Prisma Postgres",
|
},
|
||||||
hint: "Instant Postgres for Global Applications",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
{
|
||||||
value: "docker" as const,
|
value: "docker" as const,
|
||||||
label: "Docker",
|
label: "Docker",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { cancel, confirm, isCancel } from "@clack/prompts";
|
|||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
import { DEFAULT_CONFIG } from "../constants";
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
|
|
||||||
export async function getGitChoice(git?: boolean): Promise<boolean> {
|
export async function getGitChoice(git?: boolean) {
|
||||||
if (git !== undefined) return git;
|
if (git !== undefined) return git;
|
||||||
|
|
||||||
const response = await confirm({
|
const response = await confirm({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { cancel, confirm, isCancel } from "@clack/prompts";
|
|||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
import { DEFAULT_CONFIG } from "../constants";
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
|
|
||||||
export async function getinstallChoice(install?: boolean): Promise<boolean> {
|
export async function getinstallChoice(install?: boolean) {
|
||||||
if (install !== undefined) return install;
|
if (install !== undefined) return install;
|
||||||
|
|
||||||
const response = await confirm({
|
const response = await confirm({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { execa } from "execa";
|
import { execa } from "execa";
|
||||||
|
|
||||||
export async function commandExists(command: string): Promise<boolean> {
|
export async function commandExists(command: string) {
|
||||||
try {
|
try {
|
||||||
const isWindows = process.platform === "win32";
|
const isWindows = process.platform === "win32";
|
||||||
if (isWindows) {
|
if (isWindows) {
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import pc from "picocolors";
|
|||||||
import type { Database } from "../types";
|
import type { Database } from "../types";
|
||||||
import { commandExists } from "./command-exists";
|
import { commandExists } from "./command-exists";
|
||||||
|
|
||||||
export async function isDockerInstalled(): Promise<boolean> {
|
export async function isDockerInstalled() {
|
||||||
return commandExists("docker");
|
return commandExists("docker");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isDockerRunning(): Promise<boolean> {
|
export async function isDockerRunning() {
|
||||||
try {
|
try {
|
||||||
const { $ } = await import("execa");
|
const { $ } = await import("execa");
|
||||||
await $`docker info`;
|
await $`docker info`;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsdown",
|
"build": "tsdown",
|
||||||
"check-types": "tsc --noEmit",
|
"check-types": "tsc -b",
|
||||||
"compile": "bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server"
|
"compile": "bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server"
|
||||||
},
|
},
|
||||||
{{#if (eq orm 'prisma')}}
|
{{#if (eq orm 'prisma')}}
|
||||||
|
|||||||
@@ -448,21 +448,6 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
|||||||
"Database set to 'PostgreSQL' (required by Prisma PostgreSQL setup)",
|
"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") {
|
} else if (nextStack.dbSetup === "mongodb-atlas") {
|
||||||
if (nextStack.database !== "mongodb") {
|
if (nextStack.database !== "mongodb") {
|
||||||
notes.dbSetup.notes.push("Requires MongoDB. It will be selected.");
|
notes.dbSetup.notes.push("Requires MongoDB. It will be selected.");
|
||||||
|
|||||||
Reference in New Issue
Block a user