mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(cli): prisma + workers, prisma + turso, planetscale (postgres/mysql) support (#567)
This commit is contained in:
@@ -1,90 +1,88 @@
|
||||
{
|
||||
"name": "create-better-t-stack",
|
||||
"version": "2.40.5",
|
||||
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"author": "Aman Varshney",
|
||||
"bin": {
|
||||
"create-better-t-stack": "dist/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"templates",
|
||||
"dist"
|
||||
],
|
||||
"keywords": [
|
||||
"better-t-stack",
|
||||
"typescript",
|
||||
"boilerplate",
|
||||
"starter",
|
||||
"cli",
|
||||
"turborepo",
|
||||
"trpc",
|
||||
"better-auth",
|
||||
"monorepo",
|
||||
"fullstack",
|
||||
"type-safety",
|
||||
"react",
|
||||
"react-native",
|
||||
"expo",
|
||||
"hono",
|
||||
"elysia",
|
||||
"drizzle",
|
||||
"prisma",
|
||||
"tanstack",
|
||||
"tailwind",
|
||||
"shadcn",
|
||||
"pwa",
|
||||
"tauri",
|
||||
"biome"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
|
||||
"directory": "apps/cli"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"homepage": "https://better-t-stack.dev/",
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"dev": "tsdown --watch",
|
||||
"check-types": "tsc --noEmit",
|
||||
"check": "biome check --write .",
|
||||
"test": "bun run build && vitest run",
|
||||
"test:ui": "bun run build && vitest --ui",
|
||||
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@biomejs/js-api": "^3.0.0",
|
||||
"@biomejs/wasm-nodejs": "^2.2.0",
|
||||
"@clack/prompts": "^1.0.0-alpha.4",
|
||||
"consola": "^3.4.2",
|
||||
"execa": "^9.6.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"gradient-string": "^3.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"picocolors": "^1.1.1",
|
||||
"tinyglobby": "^0.2.14",
|
||||
"trpc-cli": "^0.10.2",
|
||||
"ts-morph": "^26.0.0",
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^24.3.0",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"tsdown": "^0.14.1",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
"name": "create-better-t-stack",
|
||||
"version": "2.40.4",
|
||||
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"author": "Aman Varshney",
|
||||
"bin": {
|
||||
"create-better-t-stack": "dist/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"templates",
|
||||
"dist"
|
||||
],
|
||||
"keywords": [
|
||||
"better-t-stack",
|
||||
"typescript",
|
||||
"boilerplate",
|
||||
"starter",
|
||||
"cli",
|
||||
"turborepo",
|
||||
"trpc",
|
||||
"better-auth",
|
||||
"monorepo",
|
||||
"fullstack",
|
||||
"type-safety",
|
||||
"react",
|
||||
"react-native",
|
||||
"expo",
|
||||
"hono",
|
||||
"elysia",
|
||||
"drizzle",
|
||||
"prisma",
|
||||
"tanstack",
|
||||
"tailwind",
|
||||
"shadcn",
|
||||
"pwa",
|
||||
"tauri",
|
||||
"biome"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
|
||||
"directory": "apps/cli"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"homepage": "https://better-t-stack.dev/",
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"dev": "tsdown --watch",
|
||||
"check-types": "tsc --noEmit",
|
||||
"check": "biome check --write .",
|
||||
"test": "bun run build && vitest run",
|
||||
"test:ui": "bun run build && vitest --ui",
|
||||
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^1.0.0-alpha.4",
|
||||
"consola": "^3.4.2",
|
||||
"execa": "^9.6.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"gradient-string": "^3.0.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"picocolors": "^1.1.1",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"trpc-cli": "^0.10.2",
|
||||
"ts-morph": "^27.0.0",
|
||||
"zod": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^24.3.1",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"tsdown": "^0.14.2",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ export const dependencyVersionMap = {
|
||||
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"drizzle-kit": "^0.31.2",
|
||||
"@planetscale/database": "^1.19.0",
|
||||
|
||||
"@libsql/client": "^0.15.9",
|
||||
|
||||
@@ -63,7 +64,11 @@ export const dependencyVersionMap = {
|
||||
|
||||
"@prisma/client": "^6.15.0",
|
||||
prisma: "^6.15.0",
|
||||
"@prisma/adapter-d1": "^6.15.0",
|
||||
"@prisma/extension-accelerate": "^2.0.2",
|
||||
"@prisma/adapter-libsql": "^6.15.0",
|
||||
|
||||
"@prisma/adapter-planetscale": "^6.15.0",
|
||||
|
||||
mongoose: "^8.14.0",
|
||||
|
||||
@@ -141,12 +146,12 @@ export const dependencyVersionMap = {
|
||||
|
||||
wrangler: "^4.23.0",
|
||||
"@cloudflare/vite-plugin": "^1.9.0",
|
||||
"@opennextjs/cloudflare": "^1.3.0",
|
||||
"@opennextjs/cloudflare": "^1.6.5",
|
||||
"nitro-cloudflare-dev": "^0.2.2",
|
||||
"@sveltejs/adapter-cloudflare": "^7.2.1",
|
||||
"@cloudflare/workers-types": "^4.20250822.0",
|
||||
|
||||
alchemy: "^0.63.0",
|
||||
alchemy: "^0.65.0",
|
||||
// temporary workaround for alchemy + tanstack start
|
||||
nitropack: "^2.12.4",
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import fs from "fs-extra";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { writeBtsConfig } from "../../utils/bts-config";
|
||||
import { exitWithError } from "../../utils/errors";
|
||||
import { formatProjectWithBiome } from "../../utils/format-with-biome";
|
||||
import { setupAddons } from "../addons/addons-setup";
|
||||
import { setupExamples } from "../addons/examples-setup";
|
||||
import { setupApi } from "../core/api-setup";
|
||||
@@ -86,8 +85,6 @@ export async function createProject(options: ProjectConfig) {
|
||||
|
||||
await writeBtsConfig(options);
|
||||
|
||||
await formatProjectWithBiome(projectDir);
|
||||
|
||||
if (isConvex) {
|
||||
await runConvexCodegen(projectDir, options.packageManager);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { setupCloudflareD1 } from "../database-providers/d1-setup";
|
||||
import { setupDockerCompose } from "../database-providers/docker-compose-setup";
|
||||
import { setupMongoDBAtlas } from "../database-providers/mongodb-atlas-setup";
|
||||
import { setupNeonPostgres } from "../database-providers/neon-setup";
|
||||
import { setupPlanetScale } from "../database-providers/planetscale-setup";
|
||||
import { setupPrismaPostgres } from "../database-providers/prisma-postgres-setup";
|
||||
import { setupSupabase } from "../database-providers/supabase-setup";
|
||||
import { setupTurso } from "../database-providers/turso-setup";
|
||||
@@ -36,11 +37,29 @@ export async function setupDatabase(config: ProjectConfig) {
|
||||
|
||||
try {
|
||||
if (orm === "prisma") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@prisma/client"],
|
||||
devDependencies: ["prisma"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
if (database === "mysql" && dbSetup === "planetscale") {
|
||||
await addPackageDependency({
|
||||
dependencies: [
|
||||
"@prisma/client",
|
||||
"@prisma/adapter-planetscale",
|
||||
"@planetscale/database",
|
||||
],
|
||||
devDependencies: ["prisma"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else if (database === "sqlite" && dbSetup === "turso") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@prisma/client", "@prisma/adapter-libsql"],
|
||||
devDependencies: ["prisma"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else {
|
||||
await addPackageDependency({
|
||||
dependencies: ["@prisma/client"],
|
||||
devDependencies: ["prisma"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
} else if (orm === "drizzle") {
|
||||
if (database === "sqlite") {
|
||||
await addPackageDependency({
|
||||
@@ -55,6 +74,12 @@ export async function setupDatabase(config: ProjectConfig) {
|
||||
devDependencies: ["drizzle-kit", "@types/ws"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else if (dbSetup === "planetscale") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["drizzle-orm", "pg"],
|
||||
devDependencies: ["drizzle-kit", "@types/pg"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else {
|
||||
await addPackageDependency({
|
||||
dependencies: ["drizzle-orm", "pg"],
|
||||
@@ -63,11 +88,19 @@ export async function setupDatabase(config: ProjectConfig) {
|
||||
});
|
||||
}
|
||||
} else if (database === "mysql") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["drizzle-orm", "mysql2"],
|
||||
devDependencies: ["drizzle-kit"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
if (dbSetup === "planetscale") {
|
||||
await addPackageDependency({
|
||||
dependencies: ["drizzle-orm", "@planetscale/database"],
|
||||
devDependencies: ["drizzle-kit"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
} else {
|
||||
await addPackageDependency({
|
||||
dependencies: ["drizzle-orm", "mysql2"],
|
||||
devDependencies: ["drizzle-kit"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (orm === "mongoose") {
|
||||
await addPackageDependency({
|
||||
@@ -88,9 +121,15 @@ export async function setupDatabase(config: ProjectConfig) {
|
||||
await setupPrismaPostgres(config);
|
||||
} else if (dbSetup === "neon") {
|
||||
await setupNeonPostgres(config);
|
||||
} else if (dbSetup === "planetscale") {
|
||||
await setupPlanetScale(config);
|
||||
} else if (dbSetup === "supabase") {
|
||||
await setupSupabase(config);
|
||||
}
|
||||
} else if (database === "mysql") {
|
||||
if (dbSetup === "planetscale") {
|
||||
await setupPlanetScale(config);
|
||||
}
|
||||
} else if (database === "mongodb" && dbSetup === "mongodb-atlas") {
|
||||
await setupMongoDBAtlas(config);
|
||||
}
|
||||
|
||||
@@ -232,16 +232,8 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
|
||||
}
|
||||
|
||||
let databaseUrl: string | null = null;
|
||||
const specializedSetup =
|
||||
dbSetup === "turso" ||
|
||||
dbSetup === "prisma-postgres" ||
|
||||
dbSetup === "mongodb-atlas" ||
|
||||
dbSetup === "neon" ||
|
||||
dbSetup === "supabase" ||
|
||||
dbSetup === "d1" ||
|
||||
dbSetup === "docker";
|
||||
|
||||
if (database !== "none" && !specializedSetup) {
|
||||
if (database !== "none" && dbSetup === "none") {
|
||||
switch (database) {
|
||||
case "postgres":
|
||||
databaseUrl = "postgresql://postgres:password@localhost:5432/postgres";
|
||||
@@ -281,7 +273,7 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
|
||||
{
|
||||
key: "DATABASE_URL",
|
||||
value: databaseUrl,
|
||||
condition: database !== "none" && !specializedSetup,
|
||||
condition: database !== "none" && dbSetup === "none",
|
||||
},
|
||||
{
|
||||
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
||||
|
||||
@@ -287,22 +287,40 @@ async function getDatabaseInstructions(
|
||||
}
|
||||
|
||||
if (dbSetup === "d1" && serverDeploy === "alchemy") {
|
||||
instructions.push(
|
||||
`${pc.yellow(
|
||||
"NOTE:",
|
||||
)} D1 migrations are automatically handled by Alchemy`,
|
||||
);
|
||||
}
|
||||
|
||||
if (orm === "prisma") {
|
||||
if (dbSetup === "turso") {
|
||||
if (orm === "drizzle") {
|
||||
instructions.push(
|
||||
`${pc.yellow(
|
||||
"NOTE:",
|
||||
)} Turso support with Prisma is in Early Access and requires\n additional setup. Learn more at:\n https://www.prisma.io/docs/orm/overview/databases/turso`,
|
||||
)} D1 migrations are automatically handled by Alchemy`,
|
||||
);
|
||||
} else if (orm === "prisma") {
|
||||
instructions.push(
|
||||
`${pc.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`,
|
||||
);
|
||||
instructions.push(
|
||||
`${pc.cyan("•")} Apply migrations: ${`${runCmd} db:migrate`}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (dbSetup === "planetscale") {
|
||||
if (database === "mysql" && orm === "drizzle") {
|
||||
instructions.push(
|
||||
`${pc.yellow(
|
||||
"NOTE:",
|
||||
)} Enable foreign key constraints in PlanetScale database settings`,
|
||||
);
|
||||
}
|
||||
if (database === "mysql" && orm === "prisma") {
|
||||
instructions.push(
|
||||
`${pc.yellow(
|
||||
"NOTE:",
|
||||
)} How to handle Prisma migrations with PlanetScale:\n https://github.com/prisma/prisma/issues/7292`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (orm === "prisma") {
|
||||
if (database === "mongodb" && dbSetup === "docker") {
|
||||
instructions.push(
|
||||
`${pc.yellow(
|
||||
|
||||
@@ -76,9 +76,7 @@ async function updateRootPackageJson(
|
||||
}
|
||||
if (options.orm === "prisma") {
|
||||
scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
|
||||
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) {
|
||||
scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
|
||||
}
|
||||
scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
|
||||
} else if (options.orm === "drizzle") {
|
||||
scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
|
||||
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) {
|
||||
@@ -110,10 +108,8 @@ async function updateRootPackageJson(
|
||||
if (options.orm === "prisma") {
|
||||
scripts["db:generate"] =
|
||||
`pnpm --filter ${backendPackageName} db:generate`;
|
||||
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) {
|
||||
scripts["db:migrate"] =
|
||||
`pnpm --filter ${backendPackageName} db:migrate`;
|
||||
}
|
||||
scripts["db:migrate"] =
|
||||
`pnpm --filter ${backendPackageName} db:migrate`;
|
||||
} else if (options.orm === "drizzle") {
|
||||
scripts["db:generate"] =
|
||||
`pnpm --filter ${backendPackageName} db:generate`;
|
||||
@@ -149,10 +145,8 @@ async function updateRootPackageJson(
|
||||
if (options.orm === "prisma") {
|
||||
scripts["db:generate"] =
|
||||
`npm run db:generate --workspace ${backendPackageName}`;
|
||||
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) {
|
||||
scripts["db:migrate"] =
|
||||
`npm run db:migrate --workspace ${backendPackageName}`;
|
||||
}
|
||||
scripts["db:migrate"] =
|
||||
`npm run db:migrate --workspace ${backendPackageName}`;
|
||||
} else if (options.orm === "drizzle") {
|
||||
scripts["db:generate"] =
|
||||
`npm run db:generate --workspace ${backendPackageName}`;
|
||||
@@ -189,10 +183,8 @@ async function updateRootPackageJson(
|
||||
if (options.orm === "prisma") {
|
||||
scripts["db:generate"] =
|
||||
`bun run --filter ${backendPackageName} db:generate`;
|
||||
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) {
|
||||
scripts["db:migrate"] =
|
||||
`bun run --filter ${backendPackageName} db:migrate`;
|
||||
}
|
||||
scripts["db:migrate"] =
|
||||
`bun run --filter ${backendPackageName} db:migrate`;
|
||||
} else if (options.orm === "drizzle") {
|
||||
scripts["db:generate"] =
|
||||
`bun run --filter ${backendPackageName} db:generate`;
|
||||
@@ -278,9 +270,7 @@ async function updateServerPackageJson(
|
||||
scripts["db:studio"] = "prisma studio";
|
||||
}
|
||||
scripts["db:generate"] = "prisma generate";
|
||||
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) {
|
||||
scripts["db:migrate"] = "prisma migrate dev";
|
||||
}
|
||||
scripts["db:migrate"] = "prisma migrate dev";
|
||||
} else if (options.orm === "drizzle") {
|
||||
scripts["db:push"] = "drizzle-kit push";
|
||||
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import path from "node:path";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
import { addEnvVariablesToFile, type EnvVariable } from "../core/env-setup";
|
||||
|
||||
export async function setupCloudflareD1(config: ProjectConfig) {
|
||||
const { projectDir, serverDeploy } = config;
|
||||
const { projectDir, serverDeploy, orm } = config;
|
||||
|
||||
if (serverDeploy === "wrangler") {
|
||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||
@@ -30,4 +31,28 @@ export async function setupCloudflareD1(config: ProjectConfig) {
|
||||
await addEnvVariablesToFile(envPath, variables);
|
||||
} catch (_err) {}
|
||||
}
|
||||
|
||||
if (
|
||||
(serverDeploy === "wrangler" || serverDeploy === "alchemy") &&
|
||||
orm === "prisma"
|
||||
) {
|
||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||
const variables: EnvVariable[] = [
|
||||
{
|
||||
key: "DATABASE_URL",
|
||||
value: "file:./local.db",
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
|
||||
try {
|
||||
await addEnvVariablesToFile(envPath, variables);
|
||||
} catch (_err) {}
|
||||
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
await addPackageDependency({
|
||||
dependencies: ["@prisma/adapter-d1"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import path from "node:path";
|
||||
import { cancel, isCancel, log, spinner, text } 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 { ProjectConfig } from "../../types";
|
||||
import { commandExists } from "../../utils/command-exists";
|
||||
import { exitCancelled } from "../../utils/errors";
|
||||
import { addEnvVariablesToFile, type EnvVariable } from "../core/env-setup";
|
||||
|
||||
type MongoDBConfig = {
|
||||
@@ -130,6 +131,32 @@ export async function setupMongoDBAtlas(config: ProjectConfig) {
|
||||
try {
|
||||
await fs.ensureDir(serverDir);
|
||||
|
||||
const mode = await select({
|
||||
message: "MongoDB Atlas setup: choose mode",
|
||||
options: [
|
||||
{
|
||||
label: "Automatic",
|
||||
value: "auto",
|
||||
hint: "Automated setup with provider CLI, sets .env",
|
||||
},
|
||||
{
|
||||
label: "Manual",
|
||||
value: "manual",
|
||||
hint: "Manual setup, add env vars yourself",
|
||||
},
|
||||
],
|
||||
initialValue: "auto",
|
||||
});
|
||||
|
||||
if (isCancel(mode)) return exitCancelled("Operation cancelled");
|
||||
|
||||
if (mode === "manual") {
|
||||
mainSpinner.stop("MongoDB Atlas manual setup selected");
|
||||
await writeEnvFile(projectDir);
|
||||
displayManualSetupInstructions();
|
||||
return;
|
||||
}
|
||||
|
||||
mainSpinner.stop("MongoDB Atlas setup ready");
|
||||
|
||||
const config = await initMongoDBAtlas(serverDir);
|
||||
|
||||
@@ -158,6 +158,31 @@ export async function setupNeonPostgres(config: ProjectConfig) {
|
||||
const { packageManager, projectDir } = config;
|
||||
|
||||
try {
|
||||
const mode = await select({
|
||||
message: "Neon setup: choose mode",
|
||||
options: [
|
||||
{
|
||||
label: "Automatic",
|
||||
value: "auto",
|
||||
hint: "Automated setup with provider CLI, sets .env",
|
||||
},
|
||||
{
|
||||
label: "Manual",
|
||||
value: "manual",
|
||||
hint: "Manual setup, add env vars yourself",
|
||||
},
|
||||
],
|
||||
initialValue: "auto",
|
||||
});
|
||||
|
||||
if (isCancel(mode)) return exitCancelled("Operation cancelled");
|
||||
|
||||
if (mode === "manual") {
|
||||
await writeEnvFile(projectDir);
|
||||
displayManualSetupInstructions();
|
||||
return;
|
||||
}
|
||||
|
||||
const setupMethod = await select({
|
||||
message: "Choose your Neon setup method:",
|
||||
options: [
|
||||
|
||||
79
apps/cli/src/helpers/database-providers/planetscale-setup.ts
Normal file
79
apps/cli/src/helpers/database-providers/planetscale-setup.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { ProjectConfig } from "../../types";
|
||||
import { addEnvVariablesToFile, type EnvVariable } from "../core/env-setup";
|
||||
|
||||
export async function setupPlanetScale(config: ProjectConfig) {
|
||||
const { projectDir, database, orm } = config;
|
||||
|
||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||
|
||||
if (database === "mysql" && orm === "drizzle") {
|
||||
const variables: EnvVariable[] = [
|
||||
{
|
||||
key: "DATABASE_URL",
|
||||
value:
|
||||
'mysql://username:password@host/database?ssl={"rejectUnauthorized":true}',
|
||||
condition: true,
|
||||
},
|
||||
{
|
||||
key: "DATABASE_HOST",
|
||||
value: "",
|
||||
condition: true,
|
||||
},
|
||||
{
|
||||
key: "DATABASE_USERNAME",
|
||||
value: "",
|
||||
condition: true,
|
||||
},
|
||||
{
|
||||
key: "DATABASE_PASSWORD",
|
||||
value: "",
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
|
||||
await fs.ensureDir(path.join(projectDir, "apps/server"));
|
||||
await addEnvVariablesToFile(envPath, variables);
|
||||
}
|
||||
|
||||
if (database === "postgres" && orm === "prisma") {
|
||||
const variables: EnvVariable[] = [
|
||||
{
|
||||
key: "DATABASE_URL",
|
||||
value: "postgresql://username:password@host/database?sslaccept=strict",
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
|
||||
await fs.ensureDir(path.join(projectDir, "apps/server"));
|
||||
await addEnvVariablesToFile(envPath, variables);
|
||||
}
|
||||
|
||||
if (database === "postgres" && orm === "drizzle") {
|
||||
const variables: EnvVariable[] = [
|
||||
{
|
||||
key: "DATABASE_URL",
|
||||
value:
|
||||
"postgresql://username:password@host/database?sslmode=verify-full",
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
|
||||
await fs.ensureDir(path.join(projectDir, "apps/server"));
|
||||
await addEnvVariablesToFile(envPath, variables);
|
||||
}
|
||||
|
||||
if (database === "mysql" && orm === "prisma") {
|
||||
const variables: EnvVariable[] = [
|
||||
{
|
||||
key: "DATABASE_URL",
|
||||
value: "mysql://username:password@host/database?sslaccept=strict",
|
||||
condition: true,
|
||||
},
|
||||
];
|
||||
|
||||
await fs.ensureDir(path.join(projectDir, "apps/server"));
|
||||
await addEnvVariablesToFile(envPath, variables);
|
||||
}
|
||||
}
|
||||
@@ -245,6 +245,31 @@ export async function setupPrismaPostgres(config: ProjectConfig) {
|
||||
try {
|
||||
await fs.ensureDir(serverDir);
|
||||
|
||||
const mode = await select({
|
||||
message: "Prisma Postgres setup: choose mode",
|
||||
options: [
|
||||
{
|
||||
label: "Automatic",
|
||||
value: "auto",
|
||||
hint: "Automated setup with provider CLI, sets .env",
|
||||
},
|
||||
{
|
||||
label: "Manual",
|
||||
value: "manual",
|
||||
hint: "Manual setup, add env vars yourself",
|
||||
},
|
||||
],
|
||||
initialValue: "auto",
|
||||
});
|
||||
|
||||
if (isCancel(mode)) return exitCancelled("Operation cancelled");
|
||||
|
||||
if (mode === "manual") {
|
||||
await writeEnvFile(projectDir);
|
||||
displayManualSetupInstructions();
|
||||
return;
|
||||
}
|
||||
|
||||
const setupOptions = [
|
||||
{
|
||||
label: "Quick setup with create-db",
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import path from "node:path";
|
||||
import { log } from "@clack/prompts";
|
||||
import { isCancel, log, select } from "@clack/prompts";
|
||||
import { consola } from "consola";
|
||||
import { type ExecaError, execa } from "execa";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import type { PackageManager, ProjectConfig } from "../../types";
|
||||
import { exitCancelled } from "../../utils/errors";
|
||||
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
||||
import { addEnvVariablesToFile, type EnvVariable } from "../core/env-setup";
|
||||
|
||||
@@ -159,6 +160,31 @@ export async function setupSupabase(config: ProjectConfig) {
|
||||
try {
|
||||
await fs.ensureDir(serverDir);
|
||||
|
||||
const mode = await select({
|
||||
message: "Supabase setup: choose mode",
|
||||
options: [
|
||||
{
|
||||
label: "Automatic",
|
||||
value: "auto",
|
||||
hint: "Automated setup with provider CLI, sets .env",
|
||||
},
|
||||
{
|
||||
label: "Manual",
|
||||
value: "manual",
|
||||
hint: "Manual setup, add env vars yourself",
|
||||
},
|
||||
],
|
||||
initialValue: "auto",
|
||||
});
|
||||
|
||||
if (isCancel(mode)) return exitCancelled("Operation cancelled");
|
||||
|
||||
if (mode === "manual") {
|
||||
displayManualSupabaseInstructions();
|
||||
await writeSupabaseEnvFile(projectDir, "");
|
||||
return;
|
||||
}
|
||||
|
||||
const initialized = await initializeSupabase(serverDir, packageManager);
|
||||
if (!initialized) {
|
||||
displayManualSupabaseInstructions();
|
||||
|
||||
@@ -197,23 +197,49 @@ export async function setupTurso(config: ProjectConfig) {
|
||||
const { orm, projectDir } = config;
|
||||
const _isDrizzle = orm === "drizzle";
|
||||
const setupSpinner = spinner();
|
||||
setupSpinner.start("Checking Turso CLI availability...");
|
||||
|
||||
try {
|
||||
const mode = await select({
|
||||
message: "Turso setup: choose mode",
|
||||
options: [
|
||||
{
|
||||
label: "Automatic",
|
||||
value: "auto",
|
||||
hint: "Automated setup with provider CLI, sets .env",
|
||||
},
|
||||
{
|
||||
label: "Manual",
|
||||
value: "manual",
|
||||
hint: "Manual setup, add env vars yourself",
|
||||
},
|
||||
],
|
||||
initialValue: "auto",
|
||||
});
|
||||
|
||||
if (isCancel(mode)) return exitCancelled("Operation cancelled");
|
||||
|
||||
if (mode === "manual") {
|
||||
await writeEnvFile(projectDir);
|
||||
displayManualSetupInstructions();
|
||||
return;
|
||||
}
|
||||
|
||||
setupSpinner.start("Checking Turso CLI availability...");
|
||||
const platform = os.platform();
|
||||
const isMac = platform === "darwin";
|
||||
const _isLinux = platform === "linux";
|
||||
const isWindows = platform === "win32";
|
||||
|
||||
if (isWindows) {
|
||||
setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
|
||||
if (setupSpinner)
|
||||
setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
|
||||
log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
|
||||
await writeEnvFile(projectDir);
|
||||
displayManualSetupInstructions();
|
||||
return;
|
||||
}
|
||||
|
||||
setupSpinner.stop("Turso CLI availability checked");
|
||||
if (setupSpinner) setupSpinner.stop("Turso CLI availability checked");
|
||||
|
||||
const isCliInstalled = await isTursoInstalled();
|
||||
|
||||
@@ -273,7 +299,8 @@ export async function setupTurso(config: ProjectConfig) {
|
||||
|
||||
log.success("Turso database setup completed successfully!");
|
||||
} catch (error) {
|
||||
setupSpinner.stop(pc.red("Turso CLI availability check failed"));
|
||||
if (setupSpinner)
|
||||
setupSpinner.stop(pc.red("Turso CLI availability check failed"));
|
||||
consola.error(
|
||||
pc.red(
|
||||
`Error during Turso setup: ${
|
||||
|
||||
@@ -12,7 +12,8 @@ export async function setupNextAlchemyDeploy(
|
||||
if (!(await fs.pathExists(webAppDir))) return;
|
||||
|
||||
await addPackageDependency({
|
||||
devDependencies: ["alchemy", "dotenv"],
|
||||
dependencies: ["@opennextjs/cloudflare"],
|
||||
devDependencies: ["alchemy", "dotenv", "wrangler"],
|
||||
projectDir: webAppDir,
|
||||
});
|
||||
|
||||
@@ -29,4 +30,22 @@ export async function setupNextAlchemyDeploy(
|
||||
}
|
||||
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||
}
|
||||
|
||||
const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
|
||||
const openNextConfigContent = `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
|
||||
|
||||
export default defineCloudflareConfig({});
|
||||
`;
|
||||
|
||||
await fs.writeFile(openNextConfigPath, openNextConfigContent);
|
||||
|
||||
const gitignorePath = path.join(webAppDir, ".gitignore");
|
||||
if (await fs.pathExists(gitignorePath)) {
|
||||
const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
|
||||
if (!gitignoreContent.includes("wrangler.jsonc")) {
|
||||
await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
|
||||
}
|
||||
} else {
|
||||
await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import type { PackageManager, ProjectConfig } from "../../types";
|
||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
||||
|
||||
export async function setupServerDeploy(config: ProjectConfig) {
|
||||
const { serverDeploy, webDeploy, projectDir } = config;
|
||||
@@ -64,8 +65,11 @@ async function generateCloudflareWorkerTypes({
|
||||
const s = spinner();
|
||||
try {
|
||||
s.start("Generating Cloudflare Workers types...");
|
||||
const runCmd = packageManager === "npm" ? "npm" : packageManager;
|
||||
await execa(runCmd, ["run", "cf-typegen"], { cwd: serverDir });
|
||||
const runCmd = getPackageExecutionCommand(
|
||||
packageManager,
|
||||
"wrangler types --env-interface CloudflareBindings",
|
||||
);
|
||||
await execa(runCmd, { cwd: serverDir, shell: true });
|
||||
s.stop("Cloudflare Workers types generated successfully!");
|
||||
} catch {
|
||||
s.stop(pc.yellow("Failed to generate Cloudflare Workers types"));
|
||||
|
||||
@@ -5,7 +5,7 @@ import { exitCancelled } from "../utils/errors";
|
||||
export async function getDBSetupChoice(
|
||||
databaseType: string,
|
||||
dbSetup: DatabaseSetup | undefined,
|
||||
orm?: ORM,
|
||||
_orm?: ORM,
|
||||
backend?: Backend,
|
||||
runtime?: Runtime,
|
||||
): Promise<DatabaseSetup> {
|
||||
@@ -19,10 +19,6 @@ export async function getDBSetupChoice(
|
||||
return "none";
|
||||
}
|
||||
|
||||
if (databaseType === "sqlite" && orm === "prisma") {
|
||||
return "none";
|
||||
}
|
||||
|
||||
let options: Array<{ value: DatabaseSetup; label: string; hint: string }> =
|
||||
[];
|
||||
|
||||
@@ -51,6 +47,11 @@ export async function getDBSetupChoice(
|
||||
label: "Neon Postgres",
|
||||
hint: "Serverless Postgres with branching capability",
|
||||
},
|
||||
{
|
||||
value: "planetscale" as const,
|
||||
label: "PlanetScale",
|
||||
hint: "Serverless MySQL platform with branching (Postgres compatible)",
|
||||
},
|
||||
{
|
||||
value: "supabase" as const,
|
||||
label: "Supabase",
|
||||
@@ -70,6 +71,11 @@ export async function getDBSetupChoice(
|
||||
];
|
||||
} else if (databaseType === "mysql") {
|
||||
options = [
|
||||
{
|
||||
value: "planetscale" as const,
|
||||
label: "PlanetScale",
|
||||
hint: "Serverless MySQL platform with branching",
|
||||
},
|
||||
{
|
||||
value: "docker" as const,
|
||||
label: "Docker",
|
||||
|
||||
@@ -35,10 +35,6 @@ export async function getORMChoice(
|
||||
if (!hasDatabase) return "none";
|
||||
if (orm !== undefined) return orm;
|
||||
|
||||
if (runtime === "workers") {
|
||||
return "drizzle";
|
||||
}
|
||||
|
||||
const options = [
|
||||
...(database === "mongodb"
|
||||
? [ormOptions.prisma, ormOptions.mongoose]
|
||||
@@ -48,7 +44,12 @@ export async function getORMChoice(
|
||||
const response = await select<ORM>({
|
||||
message: "Select ORM",
|
||||
options,
|
||||
initialValue: database === "mongodb" ? "prisma" : DEFAULT_CONFIG.orm,
|
||||
initialValue:
|
||||
database === "mongodb"
|
||||
? "prisma"
|
||||
: runtime === "workers"
|
||||
? "drizzle"
|
||||
: DEFAULT_CONFIG.orm,
|
||||
});
|
||||
|
||||
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
||||
|
||||
@@ -47,10 +47,7 @@ export async function getDeploymentChoice(
|
||||
return "none";
|
||||
}
|
||||
|
||||
const hasIncompatibleFrontend = frontend.some((f) => f === "next");
|
||||
const availableDeployments = hasIncompatibleFrontend
|
||||
? ["wrangler", "none"]
|
||||
: ["wrangler", "alchemy", "none"];
|
||||
const availableDeployments = ["wrangler", "alchemy", "none"];
|
||||
|
||||
const options: DeploymentOption[] = availableDeployments.map((deploy) => {
|
||||
const { label, hint } = getDeploymentDisplay(deploy as WebDeploy);
|
||||
@@ -64,9 +61,7 @@ export async function getDeploymentChoice(
|
||||
const response = await select<WebDeploy>({
|
||||
message: "Select web deployment",
|
||||
options,
|
||||
initialValue: hasIncompatibleFrontend
|
||||
? "wrangler"
|
||||
: DEFAULT_CONFIG.webDeploy,
|
||||
initialValue: DEFAULT_CONFIG.webDeploy,
|
||||
});
|
||||
|
||||
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
||||
@@ -82,8 +77,6 @@ export async function getDeploymentToAdd(
|
||||
return "none";
|
||||
}
|
||||
|
||||
const hasIncompatibleFrontend = frontend.some((f) => f === "next");
|
||||
|
||||
const options: DeploymentOption[] = [];
|
||||
|
||||
if (existingDeployment !== "wrangler") {
|
||||
@@ -95,7 +88,7 @@ export async function getDeploymentToAdd(
|
||||
});
|
||||
}
|
||||
|
||||
if (existingDeployment !== "alchemy" && !hasIncompatibleFrontend) {
|
||||
if (existingDeployment !== "alchemy") {
|
||||
const { label, hint } = getDeploymentDisplay("alchemy");
|
||||
options.push({
|
||||
value: "alchemy",
|
||||
@@ -123,9 +116,7 @@ export async function getDeploymentToAdd(
|
||||
const response = await select<WebDeploy>({
|
||||
message: "Select web deployment",
|
||||
options,
|
||||
initialValue: hasIncompatibleFrontend
|
||||
? "wrangler"
|
||||
: DEFAULT_CONFIG.webDeploy,
|
||||
initialValue: DEFAULT_CONFIG.webDeploy,
|
||||
});
|
||||
|
||||
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
||||
|
||||
@@ -68,6 +68,7 @@ export const DatabaseSetupSchema = z
|
||||
"turso",
|
||||
"neon",
|
||||
"prisma-postgres",
|
||||
"planetscale",
|
||||
"mongodb-atlas",
|
||||
"supabase",
|
||||
"d1",
|
||||
|
||||
@@ -68,37 +68,13 @@ export function validateWorkersCompatibility(
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
providedFlags.has("runtime") &&
|
||||
options.runtime === "workers" &&
|
||||
config.orm &&
|
||||
config.orm !== "drizzle" &&
|
||||
config.orm !== "none"
|
||||
) {
|
||||
exitWithError(
|
||||
`Cloudflare Workers runtime (--runtime workers) is only supported with Drizzle ORM (--orm drizzle) or no ORM (--orm none). Current ORM: ${config.orm}. Please use '--orm drizzle', '--orm none', or choose a different runtime.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
providedFlags.has("orm") &&
|
||||
config.orm &&
|
||||
config.orm !== "drizzle" &&
|
||||
config.orm !== "none" &&
|
||||
config.runtime === "workers"
|
||||
) {
|
||||
exitWithError(
|
||||
`ORM '${config.orm}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Drizzle ORM or no ORM. Please use '--orm drizzle', '--orm none', or choose a different runtime.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
providedFlags.has("runtime") &&
|
||||
options.runtime === "workers" &&
|
||||
config.database === "mongodb"
|
||||
) {
|
||||
exitWithError(
|
||||
"Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.",
|
||||
"Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,7 +94,7 @@ export function validateWorkersCompatibility(
|
||||
config.runtime === "workers"
|
||||
) {
|
||||
exitWithError(
|
||||
"MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.",
|
||||
"MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -261,29 +237,3 @@ export function validateExamplesCompatibility(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function validateAlchemyCompatibility(
|
||||
webDeploy: WebDeploy | undefined,
|
||||
serverDeploy: ServerDeploy | undefined,
|
||||
frontends: Frontend[] = [],
|
||||
) {
|
||||
const isAlchemyWebDeploy = webDeploy === "alchemy";
|
||||
const isAlchemyServerDeploy = serverDeploy === "alchemy";
|
||||
|
||||
if (isAlchemyWebDeploy || isAlchemyServerDeploy) {
|
||||
const incompatibleFrontends = frontends.filter((f) => f === "next");
|
||||
|
||||
if (incompatibleFrontends.length > 0) {
|
||||
const deployType =
|
||||
isAlchemyWebDeploy && isAlchemyServerDeploy
|
||||
? "web and server deployment"
|
||||
: isAlchemyWebDeploy
|
||||
? "web deployment"
|
||||
: "server deployment";
|
||||
|
||||
exitWithError(
|
||||
`Alchemy ${deployType} is temporarily not compatible with ${incompatibleFrontends.join(" and ")} frontend(s). Please choose a different frontend or deployment option.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
ensureSingleWebAndNative,
|
||||
isWebFrontend,
|
||||
validateAddonsAgainstFrontends,
|
||||
validateAlchemyCompatibility,
|
||||
validateApiFrontendCompatibility,
|
||||
validateExamplesCompatibility,
|
||||
validateServerDeployRequiresBackend,
|
||||
@@ -126,6 +125,10 @@ export function validateDatabaseSetup(
|
||||
errorMessage:
|
||||
"Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.",
|
||||
},
|
||||
planetscale: {
|
||||
errorMessage:
|
||||
"PlanetScale setup requires PostgreSQL or MySQL database. Please use '--database postgres' or '--database mysql' or choose a different setup.",
|
||||
},
|
||||
"mongodb-atlas": {
|
||||
database: "mongodb",
|
||||
errorMessage:
|
||||
@@ -152,8 +155,15 @@ export function validateDatabaseSetup(
|
||||
if (dbSetup && dbSetup !== "none") {
|
||||
const validation = setupValidations[dbSetup];
|
||||
|
||||
if (validation.database && database !== validation.database) {
|
||||
exitWithError(validation.errorMessage);
|
||||
// Special handling for PlanetScale - supports both postgres and mysql
|
||||
if (dbSetup === "planetscale") {
|
||||
if (database !== "postgres" && database !== "mysql") {
|
||||
exitWithError(validation.errorMessage);
|
||||
}
|
||||
} else {
|
||||
if (validation.database && database !== validation.database) {
|
||||
exitWithError(validation.errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (validation.runtime && runtime !== validation.runtime) {
|
||||
@@ -416,12 +426,6 @@ export function validateFullConfig(
|
||||
config.database,
|
||||
config.frontend ?? [],
|
||||
);
|
||||
|
||||
validateAlchemyCompatibility(
|
||||
config.webDeploy,
|
||||
config.serverDeploy,
|
||||
config.frontend ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
export function validateConfigForProgrammaticUse(
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import path from "node:path";
|
||||
import { Biome } from "@biomejs/js-api/nodejs";
|
||||
import fs from "fs-extra";
|
||||
import { glob } from "tinyglobby";
|
||||
|
||||
export async function formatProjectWithBiome(projectDir: string) {
|
||||
const biome = new Biome();
|
||||
const { projectKey } = biome.openProject(projectDir);
|
||||
|
||||
biome.applyConfiguration(projectKey, {
|
||||
formatter: {
|
||||
enabled: true,
|
||||
indentStyle: "tab",
|
||||
},
|
||||
javascript: {
|
||||
formatter: {
|
||||
quoteStyle: "double",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const files = await glob("**/*", {
|
||||
cwd: projectDir,
|
||||
dot: true,
|
||||
absolute: true,
|
||||
onlyFiles: true,
|
||||
});
|
||||
|
||||
for (const filePath of files) {
|
||||
try {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const supported = new Set([
|
||||
".ts",
|
||||
".tsx",
|
||||
".js",
|
||||
".jsx",
|
||||
".cjs",
|
||||
".mjs",
|
||||
".cts",
|
||||
".mts",
|
||||
".json",
|
||||
".jsonc",
|
||||
".md",
|
||||
".mdx",
|
||||
".css",
|
||||
".scss",
|
||||
".html",
|
||||
]);
|
||||
if (!supported.has(ext)) continue;
|
||||
|
||||
const original = await fs.readFile(filePath, "utf8");
|
||||
const result = biome.formatContent(projectKey, original, { filePath });
|
||||
const content = result?.content;
|
||||
if (typeof content !== "string") continue;
|
||||
if (content.length === 0 && original.length > 0) continue;
|
||||
if (content !== original) {
|
||||
await fs.writeFile(filePath, content);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
|
||||
import { expo } from "@better-auth/expo";
|
||||
{{/if}}
|
||||
import prisma from "../../prisma";
|
||||
import prisma from "../db";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: prismaAdapter(prisma, {
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
{{#if (or (eq runtime "bun") (eq runtime "node"))}}
|
||||
{{#if (eq dbSetup "planetscale")}}
|
||||
import { drizzle } from "drizzle-orm/planetscale-serverless";
|
||||
|
||||
export const db = drizzle({
|
||||
connection: {
|
||||
host: process.env.DATABASE_HOST,
|
||||
username: process.env.DATABASE_USERNAME,
|
||||
password: process.env.DATABASE_PASSWORD,
|
||||
},
|
||||
});
|
||||
{{else}}
|
||||
import { drizzle } from "drizzle-orm/mysql2";
|
||||
|
||||
export const db = drizzle({
|
||||
@@ -7,8 +18,21 @@ export const db = drizzle({
|
||||
},
|
||||
});
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq runtime "workers")}}
|
||||
{{#if (eq dbSetup "planetscale")}}
|
||||
import { drizzle } from "drizzle-orm/planetscale-serverless";
|
||||
import { env } from "cloudflare:workers";
|
||||
|
||||
export const db = drizzle({
|
||||
connection: {
|
||||
host: env.DATABASE_HOST,
|
||||
username: env.DATABASE_USERNAME,
|
||||
password: env.DATABASE_PASSWORD,
|
||||
},
|
||||
});
|
||||
{{else}}
|
||||
import { drizzle } from "drizzle-orm/mysql2";
|
||||
import { env } from "cloudflare:workers";
|
||||
|
||||
@@ -18,3 +42,4 @@ export const db = drizzle({
|
||||
},
|
||||
});
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { PrismaClient } from "./generated/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
5
apps/cli/templates/db/prisma/mongodb/src/db/index.ts.hbs
Normal file
5
apps/cli/templates/db/prisma/mongodb/src/db/index.ts.hbs
Normal file
@@ -0,0 +1,5 @@
|
||||
import { PrismaClient } from "../../prisma/generated/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
@@ -1,5 +0,0 @@
|
||||
import { PrismaClient } from "./generated/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
@@ -11,9 +11,15 @@ generator client {
|
||||
{{#if (eq runtime "workers")}}
|
||||
runtime = "workerd"
|
||||
{{/if}}
|
||||
{{#if (eq dbSetup "planetscale")}}
|
||||
previewFeatures = ["driverAdapters"]
|
||||
{{/if}}
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
{{#if (eq dbSetup "planetscale")}}
|
||||
relationMode = "prisma"
|
||||
{{/if}}
|
||||
}
|
||||
|
||||
12
apps/cli/templates/db/prisma/mysql/src/db/index.ts.hbs
Normal file
12
apps/cli/templates/db/prisma/mysql/src/db/index.ts.hbs
Normal file
@@ -0,0 +1,12 @@
|
||||
import { PrismaClient } from "../../prisma/generated/client";
|
||||
{{#if (eq dbSetup "planetscale")}}
|
||||
import { PrismaPlanetScale } from '@prisma/adapter-planetscale'
|
||||
|
||||
const adapter = new PrismaPlanetScale({ url: process.env.DATABASE_URL })
|
||||
|
||||
const prisma = new PrismaClient({adapter});
|
||||
{{else}}
|
||||
const prisma = new PrismaClient();
|
||||
{{/if}}
|
||||
|
||||
export default prisma;
|
||||
@@ -1,5 +0,0 @@
|
||||
import { PrismaClient } from "./generated/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
@@ -19,4 +19,7 @@ datasource db {
|
||||
{{#if (eq dbSetup "supabase")}}
|
||||
directUrl = env("DIRECT_URL")
|
||||
{{/if}}
|
||||
{{#if (eq dbSetup "planetscale")}}
|
||||
relationMode = "prisma"
|
||||
{{/if}}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PrismaClient } from "../../prisma/generated/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
@@ -1,10 +1,38 @@
|
||||
import "dotenv/config";
|
||||
import path from "node:path";
|
||||
import type { PrismaConfig } from "prisma";
|
||||
{{#if (eq dbSetup "d1")}}
|
||||
import { PrismaD1 } from "@prisma/adapter-d1";
|
||||
{{/if}}
|
||||
{{#if (eq dbSetup "turso")}}
|
||||
import { PrismaLibSQL } from "@prisma/adapter-libsql";
|
||||
{{/if}}
|
||||
|
||||
export default {
|
||||
{{#if (or (eq dbSetup "d1") (eq dbSetup "turso"))}}
|
||||
experimental: {
|
||||
adapter: true
|
||||
},
|
||||
{{/if}}
|
||||
schema: path.join("prisma", "schema"),
|
||||
migrations: {
|
||||
path: path.join("prisma", "migrations"),
|
||||
}
|
||||
},
|
||||
{{#if (eq dbSetup "d1")}}
|
||||
async adapter() {
|
||||
return new PrismaD1({
|
||||
CLOUDFLARE_D1_TOKEN: process.env.CLOUDFLARE_D1_TOKEN,
|
||||
CLOUDFLARE_ACCOUNT_ID: process.env.CLOUDFLARE_ACCOUNT_ID,
|
||||
CLOUDFLARE_DATABASE_ID: process.env.CLOUDFLARE_DATABASE_ID,
|
||||
});
|
||||
},
|
||||
{{/if}}
|
||||
{{#if (eq dbSetup "turso")}}
|
||||
async adapter() {
|
||||
return new PrismaLibSQL({
|
||||
url: process.env.DATABASE_URL || "",
|
||||
authToken: process.env.DATABASE_AUTH_TOKEN,
|
||||
});
|
||||
},
|
||||
{{/if}}
|
||||
} satisfies PrismaConfig;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { PrismaClient } from "./generated/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
@@ -10,10 +10,20 @@ generator client {
|
||||
{{/if}}
|
||||
{{#if (eq runtime "workers")}}
|
||||
runtime = "workerd"
|
||||
{{#if (eq dbSetup "d1")}}
|
||||
previewFeatures = ["driverAdapters"]
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if (eq dbSetup "turso")}}
|
||||
previewFeatures = ["driverAdapters"]
|
||||
{{/if}}
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
{{#if (eq dbSetup "turso")}}
|
||||
url = "file:./local.db"
|
||||
{{else}}
|
||||
url = env("DATABASE_URL")
|
||||
{{/if}}
|
||||
}
|
||||
|
||||
28
apps/cli/templates/db/prisma/sqlite/src/db/index.ts.hbs
Normal file
28
apps/cli/templates/db/prisma/sqlite/src/db/index.ts.hbs
Normal file
@@ -0,0 +1,28 @@
|
||||
{{#if (eq dbSetup "d1")}}
|
||||
import { env } from "cloudflare:workers";
|
||||
import { PrismaD1 } from "@prisma/adapter-d1";
|
||||
import { PrismaClient } from "../../prisma/generated/client";
|
||||
|
||||
const adapter = new PrismaD1(env.DB);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
export default prisma;
|
||||
{{else if (eq dbSetup "turso")}}
|
||||
import { PrismaLibSQL } from "@prisma/adapter-libsql";
|
||||
import { PrismaClient } from "../../prisma/generated/client";
|
||||
|
||||
const adapter = new PrismaLibSQL({
|
||||
url: process.env.DATABASE_URL || "",
|
||||
authToken: process.env.DATABASE_AUTH_TOKEN,
|
||||
});
|
||||
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
export default prisma;
|
||||
{{else}}
|
||||
import { PrismaClient } from "../../prisma/generated/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
{{/if}}
|
||||
@@ -1,7 +1,7 @@
|
||||
import alchemy from "alchemy";
|
||||
{{#if (eq webDeploy "alchemy")}}
|
||||
{{#if (includes frontend "next")}}
|
||||
import { Next } from "alchemy/cloudflare";
|
||||
import { Nextjs } from "alchemy/cloudflare";
|
||||
{{else if (includes frontend "nuxt")}}
|
||||
import { Nuxt } from "alchemy/cloudflare";
|
||||
{{else if (includes frontend "svelte")}}
|
||||
@@ -44,13 +44,17 @@ await Exec("db-generate", {
|
||||
});
|
||||
|
||||
const db = await D1Database("database", {
|
||||
{{#if (eq orm "prisma")}}
|
||||
migrationsDir: "apps/server/prisma/migrations",
|
||||
{{else if (eq orm "drizzle")}}
|
||||
migrationsDir: "apps/server/src/db/migrations",
|
||||
{{/if}}
|
||||
});
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq webDeploy "alchemy")}}
|
||||
{{#if (includes frontend "next")}}
|
||||
export const web = await Next("web", {
|
||||
export const web = await Nextjs("web", {
|
||||
{{#if (eq serverDeploy "alchemy")}}cwd: "apps/web",{{/if}}
|
||||
bindings: {
|
||||
{{#if (eq backend "convex")}}
|
||||
|
||||
@@ -27,7 +27,12 @@
|
||||
"database_name": "YOUR_DB_NAME",
|
||||
"database_id": "YOUR_DB_ID",
|
||||
"preview_database_id": "local-test-db",
|
||||
{{#if (eq orm "drizzle")}}
|
||||
"migrations_dir": "./src/db/migrations"
|
||||
{{/if}}
|
||||
{{#if (eq orm "prisma")}}
|
||||
"migrations_dir": "./prisma/migrations"
|
||||
{{/if}}
|
||||
}
|
||||
]
|
||||
{{/if}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{{#if (eq api "orpc")}}
|
||||
import z from "zod";
|
||||
import prisma from "../../prisma";
|
||||
import prisma from "../db";
|
||||
import { publicProcedure } from "../lib/orpc";
|
||||
|
||||
export const todoRouter = {
|
||||
@@ -52,7 +52,7 @@ export const todoRouter = {
|
||||
{{#if (eq api "trpc")}}
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import z from "zod";
|
||||
import prisma from "../../prisma";
|
||||
import prisma from "../db";
|
||||
import { publicProcedure, router } from "../lib/trpc";
|
||||
|
||||
export const todoRouter = router({
|
||||
|
||||
@@ -1,7 +1,2 @@
|
||||
[install]
|
||||
{{#if (or (or (includes frontend "nuxt") (includes frontend "native-nativewind")) (includes frontend
|
||||
"native-unistyles"))}}
|
||||
# linker = "isolated"
|
||||
{{else}}
|
||||
linker = "isolated"
|
||||
{{/if}}
|
||||
@@ -1,3 +1,6 @@
|
||||
{{#if (or (eq webDeploy "alchemy") (eq webDeploy "wrangler"))}}
|
||||
import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
|
||||
{{/if}}
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
@@ -5,3 +8,7 @@ const nextConfig: NextConfig = {
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
{{#if (or (eq webDeploy "alchemy") (eq webDeploy "wrangler"))}}
|
||||
initOpenNextCloudflareForDev();
|
||||
{{/if}}
|
||||
@@ -2936,6 +2936,190 @@ describe("create-better-t-stack smoke", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("scaffolds with MySQL + Drizzle + PlanetScale", async () => {
|
||||
const projectName = "app-mysql-drizzle-planetscale";
|
||||
await runCli(
|
||||
[
|
||||
projectName,
|
||||
|
||||
"--frontend",
|
||||
"tanstack-router",
|
||||
"--backend",
|
||||
"hono",
|
||||
"--runtime",
|
||||
"bun",
|
||||
"--database",
|
||||
"mysql",
|
||||
"--orm",
|
||||
"drizzle",
|
||||
"--api",
|
||||
"trpc",
|
||||
"--auth",
|
||||
"none",
|
||||
"--addons",
|
||||
"none",
|
||||
"--examples",
|
||||
"none",
|
||||
"--db-setup",
|
||||
"planetscale",
|
||||
"--web-deploy",
|
||||
"none",
|
||||
"--server-deploy",
|
||||
"none",
|
||||
"--package-manager",
|
||||
"bun",
|
||||
"--no-install",
|
||||
"--no-git",
|
||||
],
|
||||
workdir,
|
||||
);
|
||||
|
||||
const projectDir = join(workdir, projectName);
|
||||
assertScaffoldedProject(projectDir);
|
||||
assertBtsConfig(projectDir, {
|
||||
database: "mysql",
|
||||
orm: "drizzle",
|
||||
});
|
||||
});
|
||||
|
||||
it("scaffolds with MySQL + Prisma + PlanetScale", async () => {
|
||||
const projectName = "app-mysql-prisma-planetscale";
|
||||
await runCli(
|
||||
[
|
||||
projectName,
|
||||
|
||||
"--frontend",
|
||||
"tanstack-router",
|
||||
"--backend",
|
||||
"hono",
|
||||
"--runtime",
|
||||
"bun",
|
||||
"--database",
|
||||
"mysql",
|
||||
"--orm",
|
||||
"prisma",
|
||||
"--api",
|
||||
"trpc",
|
||||
"--auth",
|
||||
"none",
|
||||
"--addons",
|
||||
"none",
|
||||
"--examples",
|
||||
"none",
|
||||
"--db-setup",
|
||||
"planetscale",
|
||||
"--web-deploy",
|
||||
"none",
|
||||
"--server-deploy",
|
||||
"none",
|
||||
"--package-manager",
|
||||
"bun",
|
||||
"--no-install",
|
||||
"--no-git",
|
||||
],
|
||||
workdir,
|
||||
);
|
||||
|
||||
const projectDir = join(workdir, projectName);
|
||||
assertScaffoldedProject(projectDir);
|
||||
assertBtsConfig(projectDir, {
|
||||
database: "mysql",
|
||||
orm: "prisma",
|
||||
});
|
||||
});
|
||||
|
||||
it("scaffolds with PostgreSQL + Drizzle + PlanetScale", async () => {
|
||||
const projectName = "app-postgres-drizzle-planetscale";
|
||||
await runCli(
|
||||
[
|
||||
projectName,
|
||||
|
||||
"--frontend",
|
||||
"tanstack-router",
|
||||
"--backend",
|
||||
"hono",
|
||||
"--runtime",
|
||||
"bun",
|
||||
"--database",
|
||||
"postgres",
|
||||
"--orm",
|
||||
"drizzle",
|
||||
"--api",
|
||||
"trpc",
|
||||
"--auth",
|
||||
"none",
|
||||
"--addons",
|
||||
"none",
|
||||
"--examples",
|
||||
"none",
|
||||
"--db-setup",
|
||||
"planetscale",
|
||||
"--web-deploy",
|
||||
"none",
|
||||
"--server-deploy",
|
||||
"none",
|
||||
"--package-manager",
|
||||
"bun",
|
||||
"--no-install",
|
||||
"--no-git",
|
||||
],
|
||||
workdir,
|
||||
);
|
||||
|
||||
const projectDir = join(workdir, projectName);
|
||||
await assertScaffoldedProject(projectDir);
|
||||
await assertBtsConfig(projectDir, {
|
||||
database: "postgres",
|
||||
orm: "drizzle",
|
||||
});
|
||||
});
|
||||
|
||||
it("scaffolds with PostgreSQL + Prisma + PlanetScale", async () => {
|
||||
const projectName = "app-postgres-prisma-planetscale";
|
||||
await runCli(
|
||||
[
|
||||
projectName,
|
||||
|
||||
"--frontend",
|
||||
"tanstack-router",
|
||||
"--backend",
|
||||
"hono",
|
||||
"--runtime",
|
||||
"bun",
|
||||
"--database",
|
||||
"postgres",
|
||||
"--orm",
|
||||
"prisma",
|
||||
"--api",
|
||||
"trpc",
|
||||
"--auth",
|
||||
"none",
|
||||
"--addons",
|
||||
"none",
|
||||
"--examples",
|
||||
"none",
|
||||
"--db-setup",
|
||||
"planetscale",
|
||||
"--web-deploy",
|
||||
"none",
|
||||
"--server-deploy",
|
||||
"none",
|
||||
"--package-manager",
|
||||
"bun",
|
||||
"--no-install",
|
||||
"--no-git",
|
||||
],
|
||||
workdir,
|
||||
);
|
||||
|
||||
const projectDir = join(workdir, projectName);
|
||||
assertScaffoldedProject(projectDir);
|
||||
assertBtsConfig(projectDir, {
|
||||
database: "postgres",
|
||||
orm: "prisma",
|
||||
});
|
||||
});
|
||||
|
||||
it("scaffolds oRPC with Next.js", async () => {
|
||||
const projectName = "app-orpc-next";
|
||||
await runCli(
|
||||
@@ -3210,6 +3394,100 @@ describe("create-better-t-stack smoke", () => {
|
||||
runtime: "node",
|
||||
});
|
||||
});
|
||||
|
||||
it("scaffolds with MySQL + Drizzle + PlanetScale + Node runtime", async () => {
|
||||
const projectName = "app-mysql-drizzle-planetscale-node";
|
||||
await runCli(
|
||||
[
|
||||
projectName,
|
||||
|
||||
"--frontend",
|
||||
"tanstack-router",
|
||||
"--backend",
|
||||
"hono",
|
||||
"--runtime",
|
||||
"node",
|
||||
"--database",
|
||||
"mysql",
|
||||
"--orm",
|
||||
"drizzle",
|
||||
"--api",
|
||||
"trpc",
|
||||
"--auth",
|
||||
"none",
|
||||
"--addons",
|
||||
"none",
|
||||
"--examples",
|
||||
"none",
|
||||
"--db-setup",
|
||||
"planetscale",
|
||||
"--web-deploy",
|
||||
"none",
|
||||
"--server-deploy",
|
||||
"none",
|
||||
"--package-manager",
|
||||
"bun",
|
||||
"--no-install",
|
||||
"--no-git",
|
||||
],
|
||||
workdir,
|
||||
);
|
||||
|
||||
const projectDir = join(workdir, projectName);
|
||||
await assertScaffoldedProject(projectDir);
|
||||
await assertBtsConfig(projectDir, {
|
||||
database: "mysql",
|
||||
orm: "drizzle",
|
||||
runtime: "node",
|
||||
});
|
||||
});
|
||||
|
||||
it("scaffolds with MySQL + Prisma + PlanetScale + Workers runtime", async () => {
|
||||
const projectName = "app-mysql-prisma-planetscale-workers";
|
||||
await runCli(
|
||||
[
|
||||
projectName,
|
||||
|
||||
"--frontend",
|
||||
"tanstack-router",
|
||||
"--backend",
|
||||
"hono",
|
||||
"--runtime",
|
||||
"workers",
|
||||
"--database",
|
||||
"mysql",
|
||||
"--orm",
|
||||
"prisma",
|
||||
"--api",
|
||||
"trpc",
|
||||
"--auth",
|
||||
"none",
|
||||
"--addons",
|
||||
"none",
|
||||
"--examples",
|
||||
"none",
|
||||
"--db-setup",
|
||||
"planetscale",
|
||||
"--web-deploy",
|
||||
"none",
|
||||
"--server-deploy",
|
||||
"wrangler",
|
||||
"--package-manager",
|
||||
"bun",
|
||||
"--no-install",
|
||||
"--no-git",
|
||||
],
|
||||
workdir,
|
||||
);
|
||||
|
||||
const projectDir = join(workdir, projectName);
|
||||
assertScaffoldedProject(projectDir);
|
||||
assertBtsConfig(projectDir, {
|
||||
database: "mysql",
|
||||
orm: "prisma",
|
||||
runtime: "workers",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
(process.env.WITH_BUILD === "1" ? describe : describe.skip)(
|
||||
@@ -3296,12 +3574,18 @@ describe("create-better-t-stack smoke", () => {
|
||||
"app-with-auth",
|
||||
"app-mysql-prisma",
|
||||
"app-mysql-drizzle",
|
||||
"app-mysql-drizzle-planetscale",
|
||||
"app-mysql-prisma-planetscale",
|
||||
"app-postgres-drizzle-planetscale",
|
||||
"app-postgres-prisma-planetscale",
|
||||
"app-orpc-next",
|
||||
"app-orpc-nuxt",
|
||||
"app-orpc-svelte",
|
||||
"app-orpc-solid",
|
||||
"app-backend-next",
|
||||
"app-node-runtime",
|
||||
"app-mysql-drizzle-planetscale-node",
|
||||
"app-mysql-prisma-planetscale-workers",
|
||||
].forEach((n) => {
|
||||
projectNames.add(n);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user